From 09b0d8c349d413a17ba0593c53443689ed9c097e Mon Sep 17 00:00:00 2001 From: golovin Date: Sun, 17 Dec 2023 07:55:25 +0300 Subject: [PATCH] 2023-06-30 --- .gitattributes | 2 + .gitignore | 3 + DIRECTORY-TREE.md | 74 +++++ README.en.md | 16 + README.md | 16 + build.sh | 56 ++++ jekyll_site/Gemfile_color | 3 + jekyll_site/Gemfile_older | 3 + jekyll_site/_config_color.yml | 20 ++ jekyll_site/_config_older.yml | 20 ++ .../_includes/classes-point-cube-en.md | 148 ++++++++++ .../_includes/classes-point-cube-ru.md | 146 ++++++++++ jekyll_site/_includes/counters_body.html | 2 + jekyll_site/_includes/counters_head.html | 16 + .../_includes/volumetric-tetris-en.html | 111 +++++++ .../_includes/volumetric-tetris-ru.html | 111 +++++++ jekyll_site/css/pomodoro1.css | 34 +++ .../en/2023/01/06/spinning-square-on-plane.md | 161 ++++++++++ .../en/2023/01/11/spinning-cube-in-space.md | 229 +++++++++++++++ .../en/2023/01/16/spinning-spatial-cross.md | 275 ++++++++++++++++++ .../en/2023/01/22/volumetric-tetris.md | 53 ++++ jekyll_site/en/index.md | 48 +++ jekyll_site/img/central-projection.svg | 75 +++++ jekyll_site/img/column-vector2d.svg | 80 +++++ jekyll_site/img/column-vector3dx.svg | 86 ++++++ jekyll_site/img/column-vector3dy.svg | 86 ++++++ jekyll_site/img/column-vector3dz.svg | 86 ++++++ jekyll_site/img/euclidean-distance.svg | 60 ++++ jekyll_site/img/linear-equation.svg | 46 +++ jekyll_site/img/oblique-projection.svg | 40 +++ jekyll_site/js/classes-point-cube.js | 135 +++++++++ jekyll_site/js/spinning-cube.js | 57 ++++ jekyll_site/js/spinning-cube2.js | 55 ++++ jekyll_site/js/spinning-spatial-cross.js | 116 ++++++++ jekyll_site/js/spinning-spatial-cross2.js | 96 ++++++ jekyll_site/js/spinning-square.js | 52 ++++ jekyll_site/js/spinning-square2.js | 26 ++ jekyll_site/js/tetris-controller.js | 177 +++++++++++ jekyll_site/js/tetris-figures.js | 42 +++ jekyll_site/js/tetris-model.js | 245 ++++++++++++++++ jekyll_site/js/tetris-view.js | 235 +++++++++++++++ jekyll_site/robots.txt | 7 + .../ru/2023/01/05/spinning-square-on-plane.md | 160 ++++++++++ .../ru/2023/01/10/spinning-cube-in-space.md | 226 ++++++++++++++ .../ru/2023/01/15/spinning-spatial-cross.md | 271 +++++++++++++++++ .../ru/2023/01/21/volumetric-tetris.md | 50 ++++ jekyll_site/ru/index.md | 46 +++ package.sh | 5 + serve.sh | 4 + 49 files changed, 4111 insertions(+) create mode 100644 DIRECTORY-TREE.md create mode 100644 README.en.md create mode 100644 README.md create mode 100755 build.sh create mode 100644 jekyll_site/Gemfile_color create mode 100644 jekyll_site/Gemfile_older create mode 100644 jekyll_site/_config_color.yml create mode 100644 jekyll_site/_config_older.yml create mode 100644 jekyll_site/_includes/classes-point-cube-en.md create mode 100644 jekyll_site/_includes/classes-point-cube-ru.md create mode 100644 jekyll_site/_includes/counters_body.html create mode 100644 jekyll_site/_includes/counters_head.html create mode 100644 jekyll_site/_includes/volumetric-tetris-en.html create mode 100644 jekyll_site/_includes/volumetric-tetris-ru.html create mode 100644 jekyll_site/css/pomodoro1.css create mode 100644 jekyll_site/en/2023/01/06/spinning-square-on-plane.md create mode 100644 jekyll_site/en/2023/01/11/spinning-cube-in-space.md create mode 100644 jekyll_site/en/2023/01/16/spinning-spatial-cross.md create mode 100644 jekyll_site/en/2023/01/22/volumetric-tetris.md create mode 100644 jekyll_site/en/index.md create mode 100644 jekyll_site/img/central-projection.svg create mode 100644 jekyll_site/img/column-vector2d.svg create mode 100644 jekyll_site/img/column-vector3dx.svg create mode 100644 jekyll_site/img/column-vector3dy.svg create mode 100644 jekyll_site/img/column-vector3dz.svg create mode 100644 jekyll_site/img/euclidean-distance.svg create mode 100644 jekyll_site/img/linear-equation.svg create mode 100644 jekyll_site/img/oblique-projection.svg create mode 100644 jekyll_site/js/classes-point-cube.js create mode 100644 jekyll_site/js/spinning-cube.js create mode 100644 jekyll_site/js/spinning-cube2.js create mode 100644 jekyll_site/js/spinning-spatial-cross.js create mode 100644 jekyll_site/js/spinning-spatial-cross2.js create mode 100644 jekyll_site/js/spinning-square.js create mode 100644 jekyll_site/js/spinning-square2.js create mode 100644 jekyll_site/js/tetris-controller.js create mode 100644 jekyll_site/js/tetris-figures.js create mode 100644 jekyll_site/js/tetris-model.js create mode 100644 jekyll_site/js/tetris-view.js create mode 100644 jekyll_site/robots.txt create mode 100644 jekyll_site/ru/2023/01/05/spinning-square-on-plane.md create mode 100644 jekyll_site/ru/2023/01/10/spinning-cube-in-space.md create mode 100644 jekyll_site/ru/2023/01/15/spinning-spatial-cross.md create mode 100644 jekyll_site/ru/2023/01/21/volumetric-tetris.md create mode 100644 jekyll_site/ru/index.md create mode 100755 package.sh create mode 100755 serve.sh diff --git a/.gitattributes b/.gitattributes index e69de29..d787998 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,2 @@ +jekyll_site/ru/** linguist-language=JavaScript +jekyll_site/en/** linguist-language=JavaScript diff --git a/.gitignore b/.gitignore index c38fa4e..42e9bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .idea *.iml +*.zip +_site* +.repo_*.sh diff --git a/DIRECTORY-TREE.md b/DIRECTORY-TREE.md new file mode 100644 index 0000000..32a7034 --- /dev/null +++ b/DIRECTORY-TREE.md @@ -0,0 +1,74 @@ +## Дерево каталогов + +
+.
+├─ jekyll_site
+│  ├─ _includes
+│  │  ├─ classes-point-cube-en.md
+│  │  ├─ classes-point-cube-ru.md
+│  │  ├─ counters_body.html
+│  │  ├─ counters_head.html
+│  │  ├─ volumetric-tetris-en.html
+│  │  └─ volumetric-tetris-ru.html
+│  ├─ css
+│  │  └─ pomodoro1.css
+│  ├─ en
+│  │  ├─ 2023
+│  │  │  └─ 01
+│  │  │     ├─ 06
+│  │  │     │  └─ spinning-square-on-plane.md
+│  │  │     ├─ 11
+│  │  │     │  └─ spinning-cube-in-space.md
+│  │  │     ├─ 16
+│  │  │     │  └─ spinning-spatial-cross.md
+│  │  │     └─ 22
+│  │  │        └─ volumetric-tetris.md
+│  │  └─ index.md
+│  ├─ img
+│  │  ├─ central-projection.svg
+│  │  ├─ column-vector2d.svg
+│  │  ├─ column-vector3dx.svg
+│  │  ├─ column-vector3dy.svg
+│  │  ├─ column-vector3dz.svg
+│  │  ├─ euclidean-distance.svg
+│  │  ├─ linear-equation.svg
+│  │  └─ oblique-projection.svg
+│  ├─ js
+│  │  ├─ classes-point-cube.js
+│  │  ├─ spinning-cube.js
+│  │  ├─ spinning-cube2.js
+│  │  ├─ spinning-spatial-cross.js
+│  │  ├─ spinning-spatial-cross2.js
+│  │  ├─ spinning-square.js
+│  │  ├─ spinning-square2.js
+│  │  ├─ tetris-controller.js
+│  │  ├─ tetris-figures.js
+│  │  ├─ tetris-model.js
+│  │  └─ tetris-view.js
+│  ├─ ru
+│  │  ├─ 2023
+│  │  │  └─ 01
+│  │  │     ├─ 05
+│  │  │     │  └─ spinning-square-on-plane.md
+│  │  │     ├─ 10
+│  │  │     │  └─ spinning-cube-in-space.md
+│  │  │     ├─ 15
+│  │  │     │  └─ spinning-spatial-cross.md
+│  │  │     └─ 21
+│  │  │        └─ volumetric-tetris.md
+│  │  └─ index.md
+│  ├─ Gemfile_color
+│  ├─ Gemfile_older
+│  ├─ _config_color.yml
+│  ├─ _config_older.yml
+│  └─ robots.txt
+├─ CONTRIBUTING.md
+├─ DIRECTORY-TREE.md
+├─ LICENSE.md
+├─ OPEN_LICENSE.txt
+├─ README.en.md
+├─ README.md
+├─ build.sh
+├─ package.sh
+└─ serve.sh
+
diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..834ee9e --- /dev/null +++ b/README.en.md @@ -0,0 +1,16 @@ +## Website pages + +- [Volumetric tetris](https://pomodoro1.mircloud.ru/en/2023/01/22/volumetric-tetris.html) — 22.01.2023. +- [Spinning spatial cross](https://pomodoro1.mircloud.ru/en/2023/01/16/spinning-spatial-cross.html) — 16.01.2023. +- [Spinning cube in space](https://pomodoro1.mircloud.ru/en/2023/01/11/spinning-cube-in-space.html) — 11.01.2023. +- [Spinning square on plane](https://pomodoro1.mircloud.ru/en/2023/01/06/spinning-square-on-plane.html) — 06.01.2023. + +## [Source texts](README.md) + +- Series of the static websites [«Pomodori»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.en.md). +- Used formats — Markdown, Liquid, YAML. +- Build tool — Jekyll with tomato design themes. +- Automation of processes — Bash scripts for command line. +- [build.sh](build.sh) — Building a site in two tomato themes and optimizing the results. +- [serve.sh](serve.sh) — Local deployment to verify the correctness of the build. +- [package.sh](package.sh) — Preparing an archive for subsequent deployment. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c1371a --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +## Страницы вёб-сайта + +- [Объёмный тетрис](https://pomodoro1.mircloud.ru/ru/2023/01/21/volumetric-tetris.html) — 21.01.2023. +- [Вращаем пространственный крест](https://pomodoro1.mircloud.ru/ru/2023/01/15/spinning-spatial-cross.html) — 15.01.2023. +- [Вращаем куб в пространстве](https://pomodoro1.mircloud.ru/ru/2023/01/10/spinning-cube-in-space.html) — 10.01.2023. +- [Вращаем квадрат на плоскости](https://pomodoro1.mircloud.ru/ru/2023/01/05/spinning-square-on-plane.html) — 05.01.2023. + +## [Исходные тексты](README.en.md) + +- Серия статических вёб-сайтов [«Помидоры»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.md). +- Используемые форматы — Markdown, Liquid, YAML. +- Инструмент сборки — Jekyll с помидорными темами оформления. +- Автоматизация процессов — Bash скрипты для командной строки. +- [build.sh](build.sh) — Сборка сайта в двух помидорных темах и оптимизация результатов. +- [serve.sh](serve.sh) — Локальное развёртывание для проверки корректности сборки. +- [package.sh](package.sh) — Подготовка архива для последующего развёртывания. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..bb1ba6a --- /dev/null +++ b/build.sh @@ -0,0 +1,56 @@ +#!/bin/bash +echo "Сборка сайта в двух помидорных темах и оптимизация результатов." +milliseconds=$(date '+%s%3N') +rm -rf _site +rm -rf _site_older +rm -rf _site_color +echo "Сборка старого помидора." +mkdir -p _site_older +cp -r jekyll_site/_includes _site_older +cp -r jekyll_site/ru _site_older +cp -r jekyll_site/en _site_older +cp -r jekyll_site/ru/index.md _site_older +cp -r jekyll_site/_config_older.yml _site_older/_config.yml +cp -r jekyll_site/Gemfile_older _site_older/Gemfile +cd _site_older || exit +jekyll build +cp -r _site .. +cd .. +echo "Сборка цветного помидора." +mkdir -p _site_color +cp -r jekyll_site/_includes _site_color +cp -r jekyll_site/ru _site_color +cp -r jekyll_site/en _site_color +cp -r jekyll_site/ru/index.md _site_color +cp -r jekyll_site/_config_color.yml _site_color/_config.yml +cp -r jekyll_site/Gemfile_color _site_color/Gemfile +cd _site_color || exit +jekyll build +cp -r _site ../_site/color +cd .. +echo "Копирование без сборки." +cp -r jekyll_site/css _site +cp -r jekyll_site/img _site +cp -r jekyll_site/js _site +cp -r jekyll_site/robots.txt _site +echo "Оптимизация собранного контента." +cd _site || exit +cp -r assets/* . +rm -r assets +rm -r color/assets/favicon.ico +cp -r color/assets/* . +rm -r color/assets +rm -r color/404.html +find . -type f -name '*.html' | sort -r | while read -r file; do + sed -i 's/layout-padding=""/layout-padding/g' "$file" + sed -i 's/ class="language-plaintext highlighter-rouge"//g' "$file" + sed -i 's/ class="language-java highlighter-rouge"//g' "$file" + sed -i 's/ class="language-html highlighter-rouge"//g' "$file" + sed -i 's/ class="language-js highlighter-rouge"//g' "$file" + sed -i 's/
/
/g' "$file"
+  sed -i 's/<\/code><\/pre><\/div><\/div>/<\/code><\/pre><\/div>/g' "$file"
+  sed -i 's/
/
/g' "$file" + sed -i -r 's///g' "$file" + sed -i -r 's///g' "$file" +done +echo "Время выполнения сборки: $(("$(date '+%s%3N')" - "$milliseconds")) мс." diff --git a/jekyll_site/Gemfile_color b/jekyll_site/Gemfile_color new file mode 100644 index 0000000..136ccb7 --- /dev/null +++ b/jekyll_site/Gemfile_color @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "color-tomato-theme" diff --git a/jekyll_site/Gemfile_older b/jekyll_site/Gemfile_older new file mode 100644 index 0000000..5e19e6f --- /dev/null +++ b/jekyll_site/Gemfile_older @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "older-tomato-theme" diff --git a/jekyll_site/_config_color.yml b/jekyll_site/_config_color.yml new file mode 100644 index 0000000..a0e8fca --- /dev/null +++ b/jekyll_site/_config_color.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro1.mircloud.ru" +baseurl: "/color" +homepage_url: "https://git.org.ru/pomodoro/1" +homepage_name: "GIT.ORG.RU" +older_tomato_baseurl: "" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: color-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_config_older.yml b/jekyll_site/_config_older.yml new file mode 100644 index 0000000..2072ba5 --- /dev/null +++ b/jekyll_site/_config_older.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro1.mircloud.ru" +baseurl: "" +homepage_url: "https://git.org.ru/pomodoro/1" +homepage_name: "GIT.ORG.RU" +color_tomato_baseurl: "/color" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: older-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_includes/classes-point-cube-en.md b/jekyll_site/_includes/classes-point-cube-en.md new file mode 100644 index 0000000..921a951 --- /dev/null +++ b/jekyll_site/_includes/classes-point-cube-en.md @@ -0,0 +1,148 @@ +The Point class of the three-dimensional space contains methods for rotations by an angle and for +obtaining projections onto a plane. When obtaining projections, the distance from the point to the +projection center is calculated. Point also contains a static method to compare two projections +of points. + +{% capture collapsed_md %} +```js +class Point { + // point coordinates + constructor(x,y,z) { + this.x=x; + this.y=y; + this.z=z; + } + // rotate this point by an angle (deg) along + // axes (x,y,z) relative to the point (t0) + rotate(deg, t0) { + // functions to obtain sine and cosine of angle in radians + const sin = (deg) => Math.sin((Math.PI/180)*deg); + const cos = (deg) => Math.cos((Math.PI/180)*deg); + // calculate new coordinates of point using the formulas + // of the rotation matrix for three-dimensional space + let x,y,z; + // rotation along 'x' axis + y = t0.y+(this.y-t0.y)*cos(deg.x)-(this.z-t0.z)*sin(deg.x); + z = t0.z+(this.y-t0.y)*sin(deg.x)+(this.z-t0.z)*cos(deg.x); + this.y=y; this.z=z; + // rotation along 'y' axis + x = t0.x+(this.x-t0.x)*cos(deg.y)-(this.z-t0.z)*sin(deg.y); + z = t0.z+(this.x-t0.x)*sin(deg.y)+(this.z-t0.z)*cos(deg.y); + this.x=x; this.z=z; + // rotation along 'z' axis + x = t0.x+(this.x-t0.x)*cos(deg.z)-(this.y-t0.y)*sin(deg.z); + y = t0.y+(this.x-t0.x)*sin(deg.z)+(this.y-t0.y)*cos(deg.z); + this.x=x; this.y=y; + } + // get a projection of (type) from a distance (d) + // onto the plane of the observer screen (tv) + projection(type, tv, d) { + let proj = {}; + // obtain a projection using experimental formulas + switch (type) { + case 'parallel': { + proj.x = this.x; + proj.y = this.y+(tv.y-this.z)/4; + break; + } + case 'perspective': { + proj.x = tv.x+d*(this.x-tv.x)/(this.z-tv.z+d); + proj.y = tv.y+d*(this.y-tv.y)/(this.z-tv.z+d); + break; + } + } + // calculate distance to projection center + proj.dist = Math.sqrt((this.x-tv.x)*(this.x-tv.x) + +(this.y-tv.y)*(this.y-tv.y) + +(this.z-tv.z+d)*(this.z-tv.z+d)); + return proj; + } + // compare two projections of points (p1,p2), + // coordinates (x,y) should match + static pEquals(p1, p2) { + return Math.abs(p1.x-p2.x)<0.0001 + && Math.abs(p1.y-p2.y)<0.0001; + } +}; +``` +{% endcapture %} +{%- include collapsed_block.html summary="class Point" content=collapsed_md -%} + +The Cube class contains a collection of vertices of the Point class and an array of faces. Each face is an +array of 4 vertices, coming from the same point and going clockwise. The Cube contains methods for rotating +all vertices by an angle and for obtaining projections of all faces onto a plane. When obtaining projections, +the tilt of the face is calculated — this is the remoteness from the projection plane. The cube also contains +two static methods for comparing two face projections: for defining the equidistant faces from the projection +center and adjacent walls between neighboring cubes. + +{% capture collapsed_md %} +```js +class Cube { + // left upper near coordinate and size + constructor(x,y,z,size) { + // right lower distant coordinate + let xs=x+size,ys=y+size,zs=z+size; + let v={ // vertices + t000: new Point(x,y,z), // top + t001: new Point(x,y,zs), // top + t010: new Point(x,ys,z), // bottom + t011: new Point(x,ys,zs), // bottom + t100: new Point(xs,y,z), // top + t101: new Point(xs,y,zs), // top + t110: new Point(xs,ys,z), // bottom + t111: new Point(xs,ys,zs)};// bottom + this.vertices=v; + this.faces=[ // faces + [v.t000,v.t100,v.t110,v.t010], // front + [v.t000,v.t010,v.t011,v.t001], // left + [v.t000,v.t001,v.t101,v.t100], // upper + [v.t001,v.t011,v.t111,v.t101], // rear + [v.t100,v.t101,v.t111,v.t110], // right + [v.t010,v.t110,v.t111,v.t011]];// lower + } + // rotate vertices of the cube by an angle (deg) + // along axes (x,y,z) relative to the point (t0) + rotate(deg, t0) { + for (let vertex in this.vertices) + this.vertices[vertex].rotate(deg, t0); + } + // get projections of (type) from a distance (d) + // onto the plane of the observer screen (tv) + projection(type, tv, d) { + let proj = []; + for (let face of this.faces) { + // face projection, array of vertices + let p = []; + // cumulative remoteness of vertices + p.dist = 0; + // bypass the vertices of the face + for (let vertex of face) { + // obtain the projections of the vertices + let proj = vertex.projection(type, tv, d); + // accumulate the remoteness of vertices + p.dist+=proj.dist; + // add to array of vertices + p.push(proj); + } + // calculate face tilt, remoteness from the projection plane + p.clock = ((p[1].x-p[0].x)*(p[2].y-p[0].y) + -(p[1].y-p[0].y)*(p[2].x-p[0].x))<0; + proj.push(p); + } + return proj; + } + // compare two projections of faces (f1,f2), vertices + // should be equidistant from the center of projection + static pEquidistant(f1, f2) { + return Math.abs(f1.dist-f2.dist)<0.0001; + } + // compare two projections of faces (f1,f2), coordinates + // of points along the main diagonal (p0,p2) should match + static pAdjacent(f1, f2) { + return Point.pEquals(f1[0],f2[0]) + && Point.pEquals(f1[2],f2[2]); + } +}; +``` +{% endcapture %} +{%- include collapsed_block.html summary="class Cube" content=collapsed_md -%} diff --git a/jekyll_site/_includes/classes-point-cube-ru.md b/jekyll_site/_includes/classes-point-cube-ru.md new file mode 100644 index 0000000..318e96d --- /dev/null +++ b/jekyll_site/_includes/classes-point-cube-ru.md @@ -0,0 +1,146 @@ +Класс Точка трёхмерного пространства содержит методы для поворотов на угол и для получения проекций +на плоскость. При получении проекций, вычисляется расстояние от точки до центра проекции. Точка также +содержит статический метод для сравнения двух проекций точек. + +{% capture collapsed_md %} +```js +class Point { + // координаты точки + constructor(x,y,z) { + this.x=x; + this.y=y; + this.z=z; + } + // поворачиваем эту точку на угол (deg) + // по осям (x,y,z) относительно точки (t0) + rotate(deg, t0) { + // функции для получения синуса и косинуса угла в радианах + const sin = (deg) => Math.sin((Math.PI/180)*deg); + const cos = (deg) => Math.cos((Math.PI/180)*deg); + // получаем новые координаты точки по формулам + // матрицы поворота для трёхмерного пространства + let x,y,z; + // поворот по оси 'x' + y = t0.y+(this.y-t0.y)*cos(deg.x)-(this.z-t0.z)*sin(deg.x); + z = t0.z+(this.y-t0.y)*sin(deg.x)+(this.z-t0.z)*cos(deg.x); + this.y=y; this.z=z; + // поворот по оси 'y' + x = t0.x+(this.x-t0.x)*cos(deg.y)-(this.z-t0.z)*sin(deg.y); + z = t0.z+(this.x-t0.x)*sin(deg.y)+(this.z-t0.z)*cos(deg.y); + this.x=x; this.z=z; + // поворот по оси 'z' + x = t0.x+(this.x-t0.x)*cos(deg.z)-(this.y-t0.y)*sin(deg.z); + y = t0.y+(this.x-t0.x)*sin(deg.z)+(this.y-t0.y)*cos(deg.z); + this.x=x; this.y=y; + } + // получаем проекцию типа (type) с расстояния (d) + // на плоскость экрана наблюдателя (tv) + projection(type, tv, d) { + let proj = {}; + // получаем проекцию по экспериментальным формулам + switch (type) { + case 'parallel': { + proj.x = this.x; + proj.y = this.y+(tv.y-this.z)/4; + break; + } + case 'perspective': { + proj.x = tv.x+d*(this.x-tv.x)/(this.z-tv.z+d); + proj.y = tv.y+d*(this.y-tv.y)/(this.z-tv.z+d); + break; + } + } + // вычисляем расстояние до центра проекции + proj.dist = Math.sqrt((this.x-tv.x)*(this.x-tv.x) + +(this.y-tv.y)*(this.y-tv.y) + +(this.z-tv.z+d)*(this.z-tv.z+d)); + return proj; + } + // сравниваем две проекции точек (p1,p2), + // координаты (x,y) должны совпадать + static pEquals(p1, p2) { + return Math.abs(p1.x-p2.x)<0.0001 + && Math.abs(p1.y-p2.y)<0.0001; + } +}; +``` +{% endcapture %} +{%- include collapsed_block.html summary="class Point" content=collapsed_md -%} + +Класс Куб содержит коллекцию вершин класса Точка и массив граней. Каждая грань — это массив из 4 вершин, +выходящих из одной точки и идущих по часовой стрелке. Куб содержит методы для поворота всех вершин на угол +и для получения проекций всех граней на плоскость. При получении проекций, вычисляется наклон грани — это +удалённость от плоскости проекции. Куб также содержит два статических метода для сравнения двух проекций +граней: для определения равноудалённых граней от центра проекции и смежных стенок между соседними кубиками. + +{% capture collapsed_md %} +```js +class Cube { + // левая верхняя ближняя координата и размер + constructor(x,y,z,size) { + // правая нижняя дальняя координата + let xs=x+size,ys=y+size,zs=z+size; + let v={ // вершины + t000: new Point(x,y,z), // верх + t001: new Point(x,y,zs), // верх + t010: new Point(x,ys,z), // низ + t011: new Point(x,ys,zs), // низ + t100: new Point(xs,y,z), // верх + t101: new Point(xs,y,zs), // верх + t110: new Point(xs,ys,z), // низ + t111: new Point(xs,ys,zs)};// низ + this.vertices=v; + this.faces=[ // грани + [v.t000,v.t100,v.t110,v.t010], // передняя + [v.t000,v.t010,v.t011,v.t001], // левая + [v.t000,v.t001,v.t101,v.t100], // верхняя + [v.t001,v.t011,v.t111,v.t101], // задняя + [v.t100,v.t101,v.t111,v.t110], // правая + [v.t010,v.t110,v.t111,v.t011]];// нижняя + } + // поворачиваем вершины куба на угол (deg) + // по осям (x,y,z) относительно точки (t0) + rotate(deg, t0) { + for (let vertex in this.vertices) + this.vertices[vertex].rotate(deg, t0); + } + // получаем проекции типа (type) с расстояния (d) + // на плоскость экрана наблюдателя (tv) + projection(type, tv, d) { + let proj = []; + for (let face of this.faces) { + // проекция грани, массив вершин + let p = []; + // кумулятивная удалённость вершин + p.dist = 0; + // обходим вершины грани + for (let vertex of face) { + // получаем проекции вершин + let proj = vertex.projection(type, tv, d); + // накапливаем удалённость вершин + p.dist+=proj.dist; + // добавляем в массив вершин + p.push(proj); + } + // вычисляем наклон грани, удалённость от плоскости проекции + p.clock = ((p[1].x-p[0].x)*(p[2].y-p[0].y) + -(p[1].y-p[0].y)*(p[2].x-p[0].x))<0; + proj.push(p); + } + return proj; + } + // сравниваем две проекции граней (f1,f2), вершины + // должны быть равноудалены от центра проекции + static pEquidistant(f1, f2) { + return Math.abs(f1.dist-f2.dist)<0.0001; + } + // сравниваем две проекции граней (f1,f2), координаты + // точек по главной диагонали (p0,p2) должны совпадать + static pAdjacent(f1, f2) { + return Point.pEquals(f1[0],f2[0]) + && Point.pEquals(f1[2],f2[2]); + } +}; +``` +{% endcapture %} +{%- include collapsed_block.html summary="class Cube" content=collapsed_md -%} diff --git a/jekyll_site/_includes/counters_body.html b/jekyll_site/_includes/counters_body.html new file mode 100644 index 0000000..d559e92 --- /dev/null +++ b/jekyll_site/_includes/counters_body.html @@ -0,0 +1,2 @@ + + diff --git a/jekyll_site/_includes/counters_head.html b/jekyll_site/_includes/counters_head.html new file mode 100644 index 0000000..6cf845f --- /dev/null +++ b/jekyll_site/_includes/counters_head.html @@ -0,0 +1,16 @@ + + + + + diff --git a/jekyll_site/_includes/volumetric-tetris-en.html b/jekyll_site/_includes/volumetric-tetris-en.html new file mode 100644 index 0000000..a63dfbb --- /dev/null +++ b/jekyll_site/_includes/volumetric-tetris-en.html @@ -0,0 +1,111 @@ +
+ + + + + + + + + +
+ +
+Level: , next level: , score: +
+ +
+
+ +

Your browser does not support Canvas

+
+
+
+ +

Your browser does not support Canvas

+
+
+
+
+
+ + +
+ Transparency of figures: +
+ + + 0% +
+ Rotation of the playing field: +
+ + + -1° +
+
+ + + +
+
+ + + +
+
+
+
+ + +
+ Vertical adjustment: +
+ + + 64 +
+
+
+
+ + +
+ Center onto observer screen: +
+ + + 150 +
+
+ + + 300 +
+
+ + + 64 +
+ Remoteness of projection center: +
+ + + 640 +
+ + +
+
+
+
+
diff --git a/jekyll_site/_includes/volumetric-tetris-ru.html b/jekyll_site/_includes/volumetric-tetris-ru.html new file mode 100644 index 0000000..f425b58 --- /dev/null +++ b/jekyll_site/_includes/volumetric-tetris-ru.html @@ -0,0 +1,111 @@ +
+ + + + + + + + + +
+ +
+Уровень: , следующий уровень: , счёт: +
+ +
+
+ +

Ваш браузер не поддерживает Canvas

+
+
+
+ +

Ваш браузер не поддерживает Canvas

+
+
+
+
+
+ + +
+ Прозрачность фигур: +
+ + + 0% +
+ Поворот игрового поля: +
+ + + -1° +
+
+ + + +
+
+ + + +
+
+
+
+ + +
+ Вертикальная корректировка: +
+ + + 64 +
+
+
+
+ + +
+ Центр на экране наблюдателя: +
+ + + 150 +
+
+ + + 300 +
+
+ + + 64 +
+ Удалённость центра проекции: +
+ + + 640 +
+ + +
+
+
+
+
diff --git a/jekyll_site/css/pomodoro1.css b/jekyll_site/css/pomodoro1.css new file mode 100644 index 0000000..a65b3fb --- /dev/null +++ b/jekyll_site/css/pomodoro1.css @@ -0,0 +1,34 @@ +input { + accent-color: #888; + font-size: 100%; +} + +input[type="number"] { + border: 1px solid #888; + border-radius: 4px; + max-width: 50px; +} + +input[type="radio"] { + scale: 140%; +} + +input[type="checkbox"] { + scale: 120%; +} + +input:not([disabled]), +input[type="radio"] + label { + cursor: pointer; +} + +md-content input { + color: #1b5e20; +} + +@media (max-width: 949px) { + .disabled-sm { + pointer-events: none; + color: #888; + } +} diff --git a/jekyll_site/en/2023/01/06/spinning-square-on-plane.md b/jekyll_site/en/2023/01/06/spinning-square-on-plane.md new file mode 100644 index 0000000..0b29512 --- /dev/null +++ b/jekyll_site/en/2023/01/06/spinning-square-on-plane.md @@ -0,0 +1,161 @@ +--- +title: Spinning square on plane +description: Let's write an algorithm in JavaScript to rotate a square by an angle around its center, repeat the high school program. We will use the Math class for... +sections: [Linear algebra,Rotation matrix] +tags: [javascript,canvas,geometry,graphics,image,picture,square] +scripts: [/js/spinning-square.js,/js/spinning-square2.js] +canonical_url: /en/2023/01/06/spinning-square-on-plane.html +url_translated: /ru/2023/01/05/spinning-square-on-plane.html +title_translated: Вращаем квадрат на плоскости +date: 2023.01.06 +lang: en +--- + +Let's write an algorithm in JavaScript to rotate a square by an angle around its center, repeat the high +school program. We will use the `Math` class for calculations, and Canvas for displaying the results. + +Development of thought, volumetric model: [Spinning cube in space]({{ '/en/2023/01/11/spinning-cube-in-space.html' | relative_url }}). + +### Point rotation on plane {#point-rotation-on-plane} + +We calculate the coordinates of the new point using the formulas of the rotation matrix for +two-dimensional space. We rotate the point `t` relative to the point `t0` — we get the point `t'`. + +{% include image_svg.html src="/img/column-vector2d.svg" style="width: 246.793pt; height: 37.2836pt;" +alt="&x'=x_0+(x-x_0)cos\varphi-(y-y_0)sin\varphi,&\\&y'=y_0+(x-x_0)sin\varphi+(y-y_0)cos\varphi.&\\" %} + +### Algorithm description {#algorithm-description} + +The origin of the coordinates is in the upper left corner, the coordinate axes are directed to the right and +down. The central point for rotations `t0` is located in the center of the figure. A square is an array of +four points-vertices. We bypass the array of points, rotate each of them by an angle, then link the points +with lines and draw lines on the canvas. We renew the image at a frequency of 20 frames per second. + +### Implementation {#implementation} + +
+ +

Your browser does not support Canvas

+
+
+ +### HTML {#html1} + +```html + +

Your browser does not support Canvas

+
+``` + +### JavaScript {#javascript1} + +```js +'use strict'; +let canvas = document.getElementById('canvas'); +// original array of points-vertices of square +let square = [{x:50,y:50},{x:50,y:250},{x:250,y:250},{x:250,y:50}]; +// figure center, we'll perform a rotation around it +let t0 = {x:150, y:150}; +// rotation angle in degrees +let deg = 1; +``` +```js +// figure rotation and image update +function repaint() { + // rotate the original array of points by an angle + for (let i = 0; i < square.length; i++) + square[i] = rotateOnDegree(t0, square[i], deg); + // draw the current array of points + drawFigure(canvas, square); +} +``` +```js +// rotate the point (t) by an angle (deg) relative to the point (t0) +function rotateOnDegree(t0, t, deg) { + let t_new = {}; + // convert angle of rotation from degrees to radians + let rad = (Math.PI / 180) * deg; + // calculate the coordinates of the new point using the formula + t_new.x = t0.x+(t.x-t0.x)*Math.cos(rad)-(t.y-t0.y)*Math.sin(rad); + t_new.y = t0.y+(t.x-t0.x)*Math.sin(rad)+(t.y-t0.y)*Math.cos(rad); + // return new point + return t_new; +} +``` +```js +// draw a figure by points from an array +function drawFigure(canvas, arr) { + let context = canvas.getContext('2d'); + // clear the entire canvas + context.clearRect(0, 0, canvas.width, canvas.height); + // bypass the array of points and link them with lines + context.beginPath(); + for (let i = 0; i < arr.length; i++) + if (i == 0) + context.moveTo(arr[i].x, arr[i].y); + else + context.lineTo(arr[i].x, arr[i].y); + context.closePath(); + // draw lines on the canvas + context.lineWidth = 2.2; + context.strokeStyle = '#222'; + context.stroke(); +} +``` +```js +// after loading the page, set the image refresh interval +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); +``` + +## Spinning backwards {#spinning-backwards} + +Let's add one more point, which we'll rotate backwards. The point is distant from the center of the +figure by a quarter of the length of the side of the square. let's shift the center of the square to +this point — shift the array of its vertices. We will rotate the square itself clockwise, and its +central point — counterclockwise. This code works in conjunction with the previous one. + +
+ +

Your browser does not support Canvas

+
+
+ +### HTML {#html2} + +```html + +

Your browser does not support Canvas

+
+``` + +### JavaScript {#javascript2} + +```js +'use strict'; +let canvas2 = document.getElementById('canvas2'); +// current array of points +let square2 = []; +// spinning point +let t2 = {x:100, y:100}; +``` +```js +// figure rotation and image update +function repaint2() { + // rotate the point in the opposite direction + t2 = rotateOnDegree(t0, t2, -deg); + // bypass the points of the original array and shift + for (let i = 0; i < square.length; i++) { + // current point + square2[i] = {}; + // shifting the point of the original array + square2[i].x = square[i].x - t0.x + t2.x; + square2[i].y = square[i].y - t0.y + t2.y; + } + // draw the current array of points + drawFigure(canvas2, square2); +} +``` +```js +// after loading the page, set the image refresh interval +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint2,50)); +``` diff --git a/jekyll_site/en/2023/01/11/spinning-cube-in-space.md b/jekyll_site/en/2023/01/11/spinning-cube-in-space.md new file mode 100644 index 0000000..0216bf7 --- /dev/null +++ b/jekyll_site/en/2023/01/11/spinning-cube-in-space.md @@ -0,0 +1,229 @@ +--- +title: Spinning cube in space +description: We consider the difference between parallel and perspective projection. Both are widely used in practice for various purposes. In the previous example, we... +sections: [Linear perspective,Rotation matrix,Experimental model] +tags: [javascript,canvas,geometry,graphics,image,picture,square,cube] +scripts: [/js/classes-point-cube.js,/js/spinning-cube.js,/js/spinning-cube2.js] +styles: [/css/pomodoro1.css] +canonical_url: /en/2023/01/11/spinning-cube-in-space.html +url_translated: /ru/2023/01/10/spinning-cube-in-space.html +title_translated: Вращаем куб в пространстве +date: 2023.01.11 +lang: en +--- + +We consider the difference between parallel and perspective projection. +Both are widely used in practice for various purposes. In the previous example, we +[rotated square on plane]({{ '/en/2023/01/06/spinning-square-on-plane.html' | relative_url }}) +— we pass into three-dimensional space. Now, to display the rotation of a three-dimensional object +on the screen plane, we first need to create a three-dimensional object, rotate it by an angle, +draw a projection from it and display already the projection on the screen. + +Complicated model, many cubes: [Spinning spatial cross]({{ '/en/2023/01/16/spinning-spatial-cross.html' | relative_url }}). + +
+
+ Parallel projection + +

Your browser does not support Canvas

+
+
+
+ Perspective projection + +

Your browser does not support Canvas

+
+
+
+ +*Parallel projection* — the projection center is infinitely distant from the plane of the observer screen, +dimensions of the objects look the same. + +*Perspective projection* — parallel lines converge in the center of the perspective, +objects appear to shrink in the distance. + +## Experimental model {#experimental-model} + +Cube size 200, canvas size 300, origin of coordinates is in the upper left corner. The center of the figure +is in the middle of the canvas. The `X` axis is directed to the right, the `Y` axis is directed downwards, +the `Z` axis is directed to the distance. The rotation is performed sequentially around all three axes: first +around the `X` axis, then around the `Y` axis and then around the `Z` axis. Model settings can be controlled, +for example, you can switch off redundant rotation around the axes and change the position of the projection +center onto the observer screen. + +
+ +

Your browser does not support Canvas

+
+
+
+
+ Rotation around axes: + + + + + + +
+Center onto observer screen: +
+ + + 150 +
+
+ + + 150 +
+
+ + + 60 +
+Remoteness of projection center: +
+ + + 300 +
+
+ + +
+
+ +## Point rotation in space {#point-rotation-in-space} + +We calculate the new coordinates of the point using the formulas of the rotation matrix for +three-dimensional space. We rotate the point `t` relative to the point `t0` — we get the point `t'`. + +*Rotation along `X` axis.* + +{% include image_svg.html src="/img/column-vector3dx.svg" style="width: 242.619pt; height: 59.0768pt;" +alt="&x'=x,&\\&y'=y_0+(y-y_0)cos\varphi-(z-z_0)sin\varphi,&\\&z'=z_0+(y-y_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} + +*Rotation along `Y` axis.* + +{% include image_svg.html src="/img/column-vector3dy.svg" style="width: 246.251pt; height: 59.0768pt;" +alt="&x'=x_0+(x-x_0)cos\varphi-(z-z_0)sin\varphi,&\\&y'=y,&\\&z'=z_0+(x-x_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} + +*Rotation along `Z` axis.* + +{% include image_svg.html src="/img/column-vector3dz.svg" style="width: 246.793pt; height: 55.4753pt;" +alt="&x'=x_0+(x-x_0)cos\varphi-(y-y_0)sin\varphi,&\\&y'=y_0+(x-x_0)sin\varphi+(y-y_0)cos\varphi,&\\&z'=z.&\\" %} + +## Point projection {#point-projection} + +Experimental formulas with the possibility of shifting the projection center `d0` on the observer +screen `tv`. We map the point of space `t` to the plane of the screen — we get the point `t'`. + +*Parallel projection.* + +{% include image_svg.html src="/img/oblique-projection.svg" style="width: 123.97pt; height: 37.2836pt;" +alt="&x'=x,&\\&y'=y+(y_v-z)/4.&\\" %} + +*Perspective projection.* + +{% include image_svg.html src="/img/central-projection.svg" style="width: 231.924pt; height: 37.2836pt;" +alt="&x'=x_v+d_0\cdot(x-x_v)/(z-z_v+d_0),&\\&y'=y_v+d_0\cdot(y-y_v)/(z-z_v+d_0).&\\" %} + +*Distance from the point to the projection center.* + +{% include image_svg.html src="/img/euclidean-distance.svg" style="width: 319.911pt; height: 17.9328pt;" +alt="d(t,d_0)=\sqrt{(x-x_v)^2+(y-y_v)^2+(z-z_v+d_0)^2}." %} + +## Face sorting {#face-sorting} + +When creating a cube, we set the vertices of each face clockwise. When obtaining a projection, we +substitute three consecutive vertices into the equation of a line, to determine the tilt of the +face and its remoteness from the projection plane. + +*Equation of the line, that passes through two points.* + +{% include image_svg.html src="/img/linear-equation.svg" style="width: 137.171pt; height: 35.3194pt;" +alt="{(x-x_1)\over(y-y_1)}={(x_2-x_1)\over(y_2-y_1)}." %} + +## Algorithm description {#algorithm-description} + +First, we bypass the vertices of the cube and rotate them by an angle relative to the center point. Then +we bypass the faces of the cube and get projections of the vertices included in them. After that, we sort +the projections of the faces by remoteness. Then we draw projections on the plane — we link the points +with lines. We draw with a translucent color first the far faces and atop them the near ones, so that +the far faces can be seen through the near ones. + +At each step of displaying the figure, we repeat the sorting of the faces by remoteness, since +with a change in the angle of rotation, the coordinates shift, and the near faces become far. + +## Implementation in JavaScript {#implementation-in-javascript} + +{% include classes-point-cube-en.md -%} + +Create an object and draw two projections on the plane. + +```js +'use strict'; +// we will draw two pictures at once, there will be +// one object, and there will be many projections +const canvas1 = document.getElementById('canvas1'); +const canvas2 = document.getElementById('canvas2'); +// create an object +const cube = new Cube(50,50,50,200); +// figure center, we'll perform a rotation around it +const t0 = new Point(150,150,150); +// remoteness of the projection center +const d = 300; +// observer screen position +const tv = new Point(150,150,80); +// rotation angle in degrees +const deg = {x:0,y:1,z:0}; +``` +```js +// figure rotation and image update +function repaint() { + cube.rotate(deg, t0); + // draw parallel projection + drawFigure(canvas1, cube.projection('parallel', tv)); + // draw perspective projection + drawFigure(canvas2, cube.projection('perspective', tv, d)); +} +``` +```js +// draw a figure by points from an array +function drawFigure(canvas, proj) { + let context = canvas.getContext('2d'); + // sort the faces by their tilt + proj.sort((a,b) => b.clock-a.clock); + // clear the entire canvas + context.clearRect(0, 0, canvas.width, canvas.height); + // bypass the array of cube faces + for (let i = 0; i < proj.length; i++) { + // bypass the array of points and link them with lines + context.beginPath(); + for (let j = 0; j < proj[i].length; j++) { + if (j == 0) { + context.moveTo(proj[i][j].x, proj[i][j].y); + } else { + context.lineTo(proj[i][j].x, proj[i][j].y); + } + } + context.closePath(); + // draw the face of the cube along with the edges + context.lineWidth = 2.2; + context.lineJoin = 'round'; + context.fillStyle = '#fff9'; + context.strokeStyle = '#222'; + context.fill(); + context.stroke(); + } +} +``` +```js +// after loading the page, set the image refresh interval +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); +``` diff --git a/jekyll_site/en/2023/01/16/spinning-spatial-cross.md b/jekyll_site/en/2023/01/16/spinning-spatial-cross.md new file mode 100644 index 0000000..9d6a20c --- /dev/null +++ b/jekyll_site/en/2023/01/16/spinning-spatial-cross.md @@ -0,0 +1,275 @@ +--- +title: Spinning spatial cross +description: We are writing an algorithm for rotating a three-dimensional figure by an angle around its center along all three axes at once. In the previous example... +sections: [Volumetric figures,Rotation matrix,Experimental model] +tags: [javascript,canvas,geometry,matrix,graphics,image,picture,square,cube] +scripts: [/js/classes-point-cube.js,/js/spinning-spatial-cross.js,/js/spinning-spatial-cross2.js] +styles: [/css/pomodoro1.css] +canonical_url: /en/2023/01/16/spinning-spatial-cross.html +url_translated: /ru/2023/01/15/spinning-spatial-cross.html +title_translated: Вращаем пространственный крест +date: 2023.01.16 +lang: en +--- + +We are writing an algorithm for rotating a three-dimensional figure by an angle +around its center along all three axes at once. In the previous example, we +[rotated cube in space]({{ '/en/2023/01/11/spinning-cube-in-space.html' | relative_url }}) +— now there are a lot of cubes, the algorithm is almost the same and we use the same formulas. +We draw two variants of the figure: *spatial cross* and *cross-cube* in two types of projections, +consider the difference. + +Testing the experimental interface: [Volumetric tetris]({{ '/en/2023/01/22/volumetric-tetris.html' | relative_url }}). + +## Spatial cross {#spatial-cross} + +
+
+ Parallel projection + +

Your browser does not support Canvas

+
+
+
+ Perspective projection + +

Your browser does not support Canvas

+
+
+
+ +## Cross-cube {#cross-cube} + +
+
+ Parallel projection + +

Your browser does not support Canvas

+
+
+
+ Perspective projection + +

Your browser does not support Canvas

+
+
+
+ +*Parallel projection* — all cubes are the same size. + +*Perspective projection* — the cubes look shrinking in the distance. + +## Experimental model {#experimental-model} + +Slightly complicated version from the previous example — now there are a lot of cubes. In addition to the +previous settings there can be changed: figure variant — *spatial cross* or *cross-cube*, face sorting +direction — *linear perspective* or *reverse perspective* and transparency of the cube walls. + +
+ +

Your browser does not support Canvas

+
+
+
+
+
+ Rotation around axes: + + + + + + +
+ Center onto observer screen: +
+ + + 150 +
+
+ + + 150 +
+
+ + + 125 +
+ Remoteness of projection center: +
+ + + 300 +
+
+ + +
+ Transparency of cubes: +
+ + + 20% +
+
+Variant of the figure: +
+ + + + +
+Perspective projection: +
+ + + + +
+
+ +## Algorithm description {#algorithm-description} + +We prepare a matrix of zeros and ones, where one means a cube in a certain place of the figure. Then we +bypass this matrix and fill in the array of cubes with the corresponding coordinates of the vertices. After +that, we start the rotation along all three axes at once. At each step, we bypass the array of cubes and get +projections of their faces. Then we sort the array of faces by remoteness from the projection center, bypass +this array and throw away the same pairs from it — these are the adjacent walls between neighboring cubes +inside the figure. After that we draw cube faces with a translucent color — first the distant and then the +near ones, so that the distant faces can be seen through the near ones. + +## Implementation in JavaScript {#implementation-in-javascript} + +{% include classes-point-cube-en.md -%} + +Create objects according to templates and draw their projections on the plane. + +```js +'use strict'; +// matrices-templates for cubes +const shape1 = [ // spatial cross + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]]]; +const shape2 = [ // cross-cube + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]]]; +// cube size, number of cubes in a row, indent +const size = 40, row = 5, gap = 50; +// arrays for cubes +const cubes1 = [], cubes2 = []; +// bypass the matrices, fill the arrays with cubes +for (let x=0; xMath.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock); + // sort the faces by remoteness from the projection center + perspective.sort((a,b)=>b.dist-a.dist); + // draw parallel projection + drawFigure(cnv1, parallel); + // draw perspective projection + drawFigure(cnv2, perspective); +} +``` +```js +// do not draw adjacent walls between neighboring cubes +function noAdjacent(array) { + // sort the faces by remoteness + array.sort((a,b) => b.dist-a.dist); + // remove the adjacent walls between cubes + for (let i=0, j=1; isetInterval(repaint,50)); +``` diff --git a/jekyll_site/en/2023/01/22/volumetric-tetris.md b/jekyll_site/en/2023/01/22/volumetric-tetris.md new file mode 100644 index 0000000..7ae4176 --- /dev/null +++ b/jekyll_site/en/2023/01/22/volumetric-tetris.md @@ -0,0 +1,53 @@ +--- +title: Volumetric tetris +description: General educational game in the broad meaning of this word. When learning programming languages, it is recommended to write your own version first and then... +sections: [Logical game,Experimental interface] +tags: [javascript,canvas,game,puzzle,geometry,matrix,graphics,square,cube,3d,three-dimensional] +scripts: [/js/classes-point-cube.js,/js/tetris-figures.js,/js/tetris-model.js,/js/tetris-controller.js,/js/tetris-view.js] +styles: [/css/pomodoro1.css] +canonical_url: /en/2023/01/22/volumetric-tetris.html +url_translated: /ru/2023/01/21/volumetric-tetris.html +title_translated: Объёмный тетрис +date: 2023.01.22 +lang: en +--- + +General educational game in the broad meaning of this word. When learning programming languages, it is +recommended to write your own version first and then use it to demonstrate and test other software or +hardware. The three-dimensional interface is written in JavaScript Canvas — the logic of the game itself +is two-dimensional. + +Description of graphics algorithm: [Spinning cube in space]({{ '/en/2023/01/11/spinning-cube-in-space.html' | relative_url }}). + +## Experimental interface {#experimental-interface} + +Turned off by default — you can just play Tetris. In addition to the flat version, two volumetric variants +are added: *parallel projection* and *perspective projection* — parameters for each of them can be changed. +For perspective projection: you can change the position of the observer screen and the remoteness of the +projection source. The observer looks at the center of the image, and the center of the projection is remote +at a distance, comparable to the size of the playing field. For parallel projection: you can change the +vertical position. For both projections: you can rotate the playing field along all three axes. The central +point for rotations — is the central lower far point of the field. For all variants of the image: the size +of the cube — 32, the size of the square — 30 and the indent — 2. The origin of coordinates is located at +the upper left point, the axes are directed: `X` to the right, `Y` downwards and `Z` to the distance. + +*Usage example:* start the game, collect a certain number of figures on the field, then pause the game, and +switch between the variants of the three-dimensional image, rotate the field with figures, change the settings. + +{% include volumetric-tetris-en.html -%} + +## Gaming process {#gaming-process} + +Controls: keyboard buttons with arrows — right, left, up, down and the button `pause`. + +Game points are awarded for fully collected rows of the elements of the figures. The number of points scored +depends on the number of rows collected, 10 points for each row if there are 10 cubes in a row, and multiply +increases, if collected at the same time: 2 lines — by 3 times, 3 lines — by 5 times, 4 lines — by 10 times. + +Game feature: the collected lines first blink and then disappear, while the gaming process is not suspended +for this time — the current figure continues to fall. + +Level increases when collecting 10 completed rows, that is 100 points, if there are 10 cubes in a row. At each +new level, the speed of the figures increases and reaches its maximum at level 21. In snail mode, the speed +increases 5 times slower and reaches a maximum at level 104. The current speed is displayed above the playing +field as a `meter` indicator. diff --git a/jekyll_site/en/index.md b/jekyll_site/en/index.md new file mode 100644 index 0000000..49d234f --- /dev/null +++ b/jekyll_site/en/index.md @@ -0,0 +1,48 @@ +--- +title: Code with comments +description: Notes about programming with code snippets and comments. Problem solutions and solution descriptions. +sections: [Problem solutions and solution descriptions] +tags: [javascript,canvas,geometry,matrix,algorithms,implementation,graphics,images,pictures,square,cube] +canonical_url: /en/ +url_translated: /ru/ +title_translated: Код с комментариями +lang: en +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Volumetric tetris" %} +{%- capture article_brief %} +General educational game in the broad meaning of this word. When learning programming languages, it is +recommended to write your own version first and then use it to demonstrate and test other software or +hardware. The three-dimensional interface is written in JavaScript Canvas — the logic of the game itself +is two-dimensional. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Spinning spatial cross" %} +{%- capture article_brief %} +We are writing an algorithm for rotating a three-dimensional figure by an angle around its center along all three +axes at once. In the previous example, we rotated cube in space — now there are a lot of cubes, the algorithm is +almost the same and we use the same formulas. We draw two variants of the figure: *spatial cross* and *cross-cube* +in two types of projections, consider the difference. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Spinning cube in space" %} +{%- capture article_brief %} +We consider the difference between parallel and perspective projection. Both are widely used in practice for various +purposes. In the previous example, we rotated square on plane — we pass into three-dimensional space. Now, to display +the rotation of a three-dimensional object on the screen plane, we first need to create a three-dimensional object, +rotate it by an angle, draw a projection from it and display already the projection on the screen. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Spinning square on plane" %} +{%- capture article_brief %} +Let's write an algorithm in JavaScript to rotate a square by an angle around its center, repeat the high +school program. We will use the `Math` class for calculations, and Canvas for displaying the results. + +The origin of the coordinates is in the upper left corner, the coordinate axes are directed to the right and +down. The central point for rotations `t0` is located in the center of the figure. A square is an array of +four points-vertices. We bypass the array of points, rotate each of them by an angle, then link the points +with lines and draw lines on the canvas. We renew the image at a frequency of 20 frames per second. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/jekyll_site/img/central-projection.svg b/jekyll_site/img/central-projection.svg new file mode 100644 index 0000000..c845ab9 --- /dev/null +++ b/jekyll_site/img/central-projection.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/column-vector2d.svg b/jekyll_site/img/column-vector2d.svg new file mode 100644 index 0000000..f9ad533 --- /dev/null +++ b/jekyll_site/img/column-vector2d.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/column-vector3dx.svg b/jekyll_site/img/column-vector3dx.svg new file mode 100644 index 0000000..5f7c52b --- /dev/null +++ b/jekyll_site/img/column-vector3dx.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/column-vector3dy.svg b/jekyll_site/img/column-vector3dy.svg new file mode 100644 index 0000000..bda2c38 --- /dev/null +++ b/jekyll_site/img/column-vector3dy.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/column-vector3dz.svg b/jekyll_site/img/column-vector3dz.svg new file mode 100644 index 0000000..fbb9e4c --- /dev/null +++ b/jekyll_site/img/column-vector3dz.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/euclidean-distance.svg b/jekyll_site/img/euclidean-distance.svg new file mode 100644 index 0000000..fe53f04 --- /dev/null +++ b/jekyll_site/img/euclidean-distance.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/linear-equation.svg b/jekyll_site/img/linear-equation.svg new file mode 100644 index 0000000..2d32f42 --- /dev/null +++ b/jekyll_site/img/linear-equation.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/oblique-projection.svg b/jekyll_site/img/oblique-projection.svg new file mode 100644 index 0000000..c6fb608 --- /dev/null +++ b/jekyll_site/img/oblique-projection.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/js/classes-point-cube.js b/jekyll_site/js/classes-point-cube.js new file mode 100644 index 0000000..def25cb --- /dev/null +++ b/jekyll_site/js/classes-point-cube.js @@ -0,0 +1,135 @@ +// © Головин Г.Г., Код с комментариями, 2023 +'use strict'; +// Класс Точка трёхмерного пространства содержит методы для поворотов на угол и для получения проекций +// на плоскость. При получении проекций, вычисляется расстояние от точки до центра проекции. Точка также +// содержит статический метод для сравнения двух проекций точек. +class Point { + // координаты точки + constructor(x,y,z) { + this.x=x; + this.y=y; + this.z=z; + } + // поворачиваем эту точку на угол (deg) + // по осям (x,y,z) относительно точки (t0) + rotate(deg, t0) { + // функции для получения синуса и косинуса угла в радианах + const sin = (deg) => Math.sin((Math.PI/180)*deg); + const cos = (deg) => Math.cos((Math.PI/180)*deg); + // получаем новые координаты точки по формулам + // матрицы поворота для трёхмерного пространства + let x,y,z; + // поворот по оси 'x' + y = t0.y+(this.y-t0.y)*cos(deg.x)-(this.z-t0.z)*sin(deg.x); + z = t0.z+(this.y-t0.y)*sin(deg.x)+(this.z-t0.z)*cos(deg.x); + this.y=y; this.z=z; + // поворот по оси 'y' + x = t0.x+(this.x-t0.x)*cos(deg.y)-(this.z-t0.z)*sin(deg.y); + z = t0.z+(this.x-t0.x)*sin(deg.y)+(this.z-t0.z)*cos(deg.y); + this.x=x; this.z=z; + // поворот по оси 'z' + x = t0.x+(this.x-t0.x)*cos(deg.z)-(this.y-t0.y)*sin(deg.z); + y = t0.y+(this.x-t0.x)*sin(deg.z)+(this.y-t0.y)*cos(deg.z); + this.x=x; this.y=y; + } + // получаем проекцию типа (type) с расстояния (d) + // на плоскость экрана наблюдателя (tv) + projection(type, tv, d) { + let proj = {}; + // получаем проекцию по экспериментальным формулам + switch (type) { + case 'parallel': { + proj.x = this.x; + proj.y = this.y+(tv.y-this.z)/4; + break; + } + case 'perspective': { + proj.x = tv.x+d*(this.x-tv.x)/(this.z-tv.z+d); + proj.y = tv.y+d*(this.y-tv.y)/(this.z-tv.z+d); + break; + } + } + // вычисляем расстояние до центра проекции + proj.dist = Math.sqrt((this.x-tv.x)*(this.x-tv.x) + +(this.y-tv.y)*(this.y-tv.y) + +(this.z-tv.z+d)*(this.z-tv.z+d)); + return proj; + } + // сравниваем две проекции точек (p1,p2), + // координаты (x,y) должны совпадать + static pEquals(p1, p2) { + return Math.abs(p1.x-p2.x)<0.0001 + && Math.abs(p1.y-p2.y)<0.0001; + } +}; +// Класс Куб содержит коллекцию вершин класса Точка и массив граней. Каждая грань — это массив из 4 вершин, +// выходящих из одной точки и идущих по часовой стрелке. Куб содержит методы для поворота всех вершин на угол +// и для получения проекций всех граней на плоскость. При получении проекций, вычисляется наклон грани — это +// удалённость от плоскости проекции. Куб также содержит два статических метода для сравнения двух проекций +// граней: для определения равноудалённых граней от центра проекции и смежных стенок между соседними кубиками. +class Cube { + // левая верхняя ближняя координата и размер + constructor(x,y,z,size) { + // правая нижняя дальняя координата + let xs=x+size,ys=y+size,zs=z+size; + let v={ // вершины + t000: new Point(x,y,z), // верх + t001: new Point(x,y,zs), // верх + t010: new Point(x,ys,z), // низ + t011: new Point(x,ys,zs), // низ + t100: new Point(xs,y,z), // верх + t101: new Point(xs,y,zs), // верх + t110: new Point(xs,ys,z), // низ + t111: new Point(xs,ys,zs)};// низ + this.vertices=v; + this.faces=[ // грани + [v.t000,v.t100,v.t110,v.t010], // передняя + [v.t000,v.t010,v.t011,v.t001], // левая + [v.t000,v.t001,v.t101,v.t100], // верхняя + [v.t001,v.t011,v.t111,v.t101], // задняя + [v.t100,v.t101,v.t111,v.t110], // правая + [v.t010,v.t110,v.t111,v.t011]];// нижняя + } + // поворачиваем вершины куба на угол (deg) + // по осям (x,y,z) относительно точки (t0) + rotate(deg, t0) { + for (let vertex in this.vertices) + this.vertices[vertex].rotate(deg, t0); + } + // получаем проекции типа (type) с расстояния (d) + // на плоскость экрана наблюдателя (tv) + projection(type, tv, d) { + let proj = []; + for (let face of this.faces) { + // проекция грани, массив вершин + let p = []; + // кумулятивная удалённость вершин + p.dist = 0; + // обходим вершины грани + for (let vertex of face) { + // получаем проекции вершин + let proj = vertex.projection(type, tv, d); + // накапливаем удалённость вершин + p.dist+=proj.dist; + // добавляем в массив вершин + p.push(proj); + } + // вычисляем наклон грани, удалённость от плоскости проекции + p.clock = ((p[1].x-p[0].x)*(p[2].y-p[0].y) + -(p[1].y-p[0].y)*(p[2].x-p[0].x))<0; + proj.push(p); + } + return proj; + } + // сравниваем две проекции граней (f1,f2), вершины + // должны быть равноудалены от центра проекции + static pEquidistant(f1, f2) { + return Math.abs(f1.dist-f2.dist)<0.0001; + } + // сравниваем две проекции граней (f1,f2), координаты + // точек по главной диагонали (p0,p2) должны совпадать + static pAdjacent(f1, f2) { + return Point.pEquals(f1[0],f2[0]) + && Point.pEquals(f1[2],f2[2]); + } +}; diff --git a/jekyll_site/js/spinning-cube.js b/jekyll_site/js/spinning-cube.js new file mode 100644 index 0000000..bf8fcc8 --- /dev/null +++ b/jekyll_site/js/spinning-cube.js @@ -0,0 +1,57 @@ +// © Головин Г.Г., Код с комментариями, 2023 +'use strict'; +// рисовать будем сразу две картинки, +// объект будет один, а проекций будет много +const canvas1 = document.getElementById('canvas1'); +const canvas2 = document.getElementById('canvas2'); +// создаём объект +const cube = new Cube(50,50,50,200); +// центр фигуры, вокруг него будем выполнять поворот +const t0 = new Point(150,150,150); +// удалённость центра проекции +const d = 300; +// положение экрана наблюдателя +const tv = new Point(150,150,80); +// угол поворота в градусах +const deg = {x:0,y:1,z:0}; + +// поворот фигуры и обновление изображения +function repaint() { + cube.rotate(deg, t0); + // рисуем параллельную проекцию + drawFigure(canvas1, cube.projection('parallel', tv)); + // рисуем перспективную проекцию + drawFigure(canvas2, cube.projection('perspective', tv, d)); +} + +// рисуем фигуру по точкам из массива +function drawFigure(canvas, proj) { + let context = canvas.getContext('2d'); + // сортируем грани по их наклону + proj.sort((a,b) => b.clock-a.clock); + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // обходим массив граней куба + for (let i = 0; i < proj.length; i++) { + // обходим массив точек и соединяем их линиями + context.beginPath(); + for (let j = 0; j < proj[i].length; j++) { + if (j == 0) { + context.moveTo(proj[i][j].x, proj[i][j].y); + } else { + context.lineTo(proj[i][j].x, proj[i][j].y); + } + } + context.closePath(); + // рисуем грань куба вместе с рёбрами + context.lineWidth = 2.2; + context.lineJoin = 'round'; + context.fillStyle = '#fff9'; + context.strokeStyle = '#222'; + context.fill(); + context.stroke(); + } +} + +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); diff --git a/jekyll_site/js/spinning-cube2.js b/jekyll_site/js/spinning-cube2.js new file mode 100644 index 0000000..dcb0aae --- /dev/null +++ b/jekyll_site/js/spinning-cube2.js @@ -0,0 +1,55 @@ +// © Головин Г.Г., Экспериментальная модель, 2023 +'use strict'; +let d3=300,tv3={x:150,y:150,z:60}; +let deg2={x:1,y:1,z:1},show=false; +// обработчики событий в форме +function changeAxis(val,caller) { + deg2[val]=0+caller.target.checked; +} +function changeDistance(caller) { + d3=caller.target.valueAsNumber; +} +function changeTv(val,caller) { + tv3[val]=caller.target.valueAsNumber; +} +function showCenter(caller) { + show=caller.target.checked; +} +const cube3 = new Cube(50,50,50,200); +const canvas3 = document.getElementById('canvas3'); +// перетаскивание центральной точки мышью +let msBtnPressed = false; +canvas3.onmouseup = ()=> msBtnPressed=false; +canvas3.onmousedown = (caller)=> { + msBtnPressed=true; + canvas3.onmousemove(caller); +} +canvas3.onmousemove = function(caller) { + if (msBtnPressed && show) { + tv3.x=caller.offsetX; + tv3.y=caller.offsetY; + document.getElementById('rangeX').value=caller.offsetX; + document.getElementById('resultX').value=caller.offsetX; + document.getElementById('rangeY').value=caller.offsetY; + document.getElementById('resultY').value=caller.offsetY; + } +} +// поворот фигуры и обновление изображения +function repaint3() { + cube3.rotate(deg2, t0); + // рисуем перспективную проекцию + drawFigure(canvas3, cube3.projection('perspective', tv3, d3)); + // центральная точка перспективной проекции + if (show) centerPoint(canvas3); +} +// центральная точка перспективной проекции +function centerPoint(canvas) { + const context = canvas.getContext('2d'); + context.beginPath(); + context.lineWidth = 2.2; + context.strokeStyle = '#222'; + context.arc(tv3.x, tv3.y, 5.5, 0, 2*Math.PI); + context.stroke(); +} +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint3,50)); diff --git a/jekyll_site/js/spinning-spatial-cross.js b/jekyll_site/js/spinning-spatial-cross.js new file mode 100644 index 0000000..8c0d8e9 --- /dev/null +++ b/jekyll_site/js/spinning-spatial-cross.js @@ -0,0 +1,116 @@ +// © Головин Г.Г., Код с комментариями, 2023 +'use strict'; +// матрицы-шаблоны для кубиков +const shape1 = [ // пространственный крест + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]]]; +const shape2 = [ // крест-куб + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]]]; +// размер кубика, количество кубиков в ряду, отступ +const size = 40, row = 5, gap = 50; +// массивы для кубиков +const cubes1 = [], cubes2 = []; +// обходим матрицы, заполняем массивы кубиками +for (let x=0; xMath.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock); + // сортируем грани по удалённости от центра проекции + perspective.sort((a,b)=>b.dist-a.dist); + // рисуем параллельную проекцию + drawFigure(cnv1, parallel); + // рисуем перспективную проекцию + drawFigure(cnv2, perspective); +} + +// смежные стенки между соседними кубиками не рисуем +function noAdjacent(array) { + // сортируем грани по удалённости + array.sort((a,b) => b.dist-a.dist); + // удаляем смежные стенки между кубиками + for (let i=0, j=1; isetInterval(repaint,50)); diff --git a/jekyll_site/js/spinning-spatial-cross2.js b/jekyll_site/js/spinning-spatial-cross2.js new file mode 100644 index 0000000..6ff5b98 --- /dev/null +++ b/jekyll_site/js/spinning-spatial-cross2.js @@ -0,0 +1,96 @@ +// © Головин Г.Г., Экспериментальная модель, 2023 +'use strict'; +let d5=300,tv5={x:150,y:150,z:125},show=false; +let deg2={x:1,y:1,z:1}; +let sortOrder=true,alpha=20,first=false; +// обработчики событий в форме +function changeAxis(val,caller) { + deg2[val]=0+caller.target.checked; +} +function changeDistance(caller) { + d5=caller.target.valueAsNumber; +} +function changeTv(val,caller) { + tv5[val]=caller.target.valueAsNumber; +} +function showCenter(caller) { + show=caller.target.checked; +} +function changeFigure(caller) { + if (caller.target.value=="first") first=true; + if (caller.target.value=="second") first=false; +} +function changeOrder(caller) { + if (caller.target.value=="linear") sortOrder=true; + if (caller.target.value=="reverse") sortOrder=false; +} +function changeAlpha(caller) { + alpha=caller.target.valueAsNumber; +} +const canvas5 = document.getElementById('canvas5'); +// перетаскивание центральной точки мышью +let msBtnPressed = false; +canvas5.onmouseup = ()=> msBtnPressed=false; +canvas5.onmousedown = (caller)=> { + msBtnPressed=true; + canvas5.onmousemove(caller); +} +canvas5.onmousemove = (caller)=> { + if (msBtnPressed && show) { + tv5.x=caller.offsetX; + tv5.y=caller.offsetY; + document.getElementById('rangeX').value=caller.offsetX; + document.getElementById('resultX').value=caller.offsetX; + document.getElementById('rangeY').value=caller.offsetY; + document.getElementById('resultY').value=caller.offsetY; + } +} +// массивы для кубиков +const cubes5a = [], cubes5b = []; +// обходим матрицы, заполняем массивы кубиками +for (let x=0; xb.dist-a.dist); + // сортировка в обратном порядке + if (!sortOrder) proj.reverse(); + // рисуем перспективную проекцию + drawFigure(canvas, proj, (100-alpha)/100); + // центральная точка перспективной проекции + if (show) centerPoint(canvas); +} +// центральная точка перспективной проекции +function centerPoint(canvas) { + const context = canvas.getContext('2d'); + context.beginPath(); + context.lineWidth = 3.2; + context.strokeStyle = '#66bb6a'; + context.arc(tv5.x, tv5.y, 6.5, 0, 2*Math.PI); + context.stroke(); +} +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint5,50)); diff --git a/jekyll_site/js/spinning-square.js b/jekyll_site/js/spinning-square.js new file mode 100644 index 0000000..6034980 --- /dev/null +++ b/jekyll_site/js/spinning-square.js @@ -0,0 +1,52 @@ +// © Головин Г.Г., Код с комментариями, 2023 +'use strict'; +let canvas = document.getElementById('canvas'); +// исходный массив точек-вершин квадрата +let square = [{x:50,y:50},{x:50,y:250},{x:250,y:250},{x:250,y:50}]; +// центр фигуры, вокруг него будем выполнять поворот +let t0 = {x:150, y:150}; +// угол поворота в градусах +let deg = 1; + +// поворот фигуры и обновление изображения +function repaint() { + // поворачиваем исходный массив точек на угол + for (let i = 0; i < square.length; i++) + square[i] = rotateOnDegree(t0, square[i], deg); + // рисуем текущий массив точек + drawFigure(canvas, square); +} + +// поворачиваем точку (t) на угол (deg) относительно точки (t0) +function rotateOnDegree(t0, t, deg) { + let t_new = {}; + // переводим угол поворота из градусов в радианы + let rad = (Math.PI / 180) * deg; + // рассчитываем координаты новой точки по формуле + t_new.x = t0.x+(t.x-t0.x)*Math.cos(rad)-(t.y-t0.y)*Math.sin(rad); + t_new.y = t0.y+(t.x-t0.x)*Math.sin(rad)+(t.y-t0.y)*Math.cos(rad); + // возвращаем новую точку + return t_new; +} + +// рисуем фигуру по точкам из массива +function drawFigure(canvas, arr) { + let context = canvas.getContext('2d'); + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // обходим массив точек и соединяем их линиями + context.beginPath(); + for (let i = 0; i < arr.length; i++) + if (i == 0) + context.moveTo(arr[i].x, arr[i].y); + else + context.lineTo(arr[i].x, arr[i].y); + context.closePath(); + // рисуем линии на холсте + context.lineWidth = 2.2; + context.strokeStyle = '#222'; + context.stroke(); +} + +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); diff --git a/jekyll_site/js/spinning-square2.js b/jekyll_site/js/spinning-square2.js new file mode 100644 index 0000000..94b3c82 --- /dev/null +++ b/jekyll_site/js/spinning-square2.js @@ -0,0 +1,26 @@ +// © Головин Г.Г., Код с комментариями, 2023 +'use strict'; +let canvas2 = document.getElementById('canvas2'); +// текущий массив точек +let square2 = []; +// вращающаяся точка +let t2 = {x:100, y:100}; + +// поворот фигуры и обновление изображения +function repaint2() { + // поворачиваем точку в обратную сторону + t2 = rotateOnDegree(t0, t2, -deg); + // обходим точки исходного массива и сдвигаем + for (let i = 0; i < square.length; i++) { + // текущая точка + square2[i] = {}; + // сдвигаем точку исходного массива + square2[i].x = square[i].x - t0.x + t2.x; + square2[i].y = square[i].y - t0.y + t2.y; + } + // рисуем текущий массив точек + drawFigure(canvas2, square2); +} + +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint2,50)); diff --git a/jekyll_site/js/tetris-controller.js b/jekyll_site/js/tetris-controller.js new file mode 100644 index 0000000..802bb3c --- /dev/null +++ b/jekyll_site/js/tetris-controller.js @@ -0,0 +1,177 @@ +// © Головин Г.Г., Обработка действий пользователя, 2023 +'use strict'; +// коды кнопок на клавиатуре +const KEY = {PAUSE:19,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40}; +// типы объёмного изображения +const VOLUME = {FLAT:0,PARALLEL:1,PERSPECTIVE:2}; +// текущий тип изображения +let vol = VOLUME.FLAT; +// кнопка нажата и удерживается +function keyPressed(caller) { + if (status == GAME.OVER) return; + switch (caller.keyCode) { + case KEY.PAUSE: { + status = GAME.PAUSE; + statusView.refresh(); + return; + } + case KEY.LEFT: { + caller.preventDefault(); + moveFigureLeft(); + break; + } + case KEY.RIGHT: { + caller.preventDefault(); + moveFigureRight(); + break; + } + case KEY.SPACE: + case KEY.UP: { + caller.preventDefault(); + rotateFigure(); + break; + } + case KEY.DOWN: { + caller.preventDefault(); + rapidFall = true; + if (sleepTimeout != undefined) { + clearTimeout(sleepTimeout); + stepDown(); + } + break; + } + default: { + break; + } + } + if (status == GAME.LEVEL || status == GAME.PAUSE) { + status = GAME.RUN; + setTimeout(figureFall, 60); + } +} +// кнопка отпущена +function keyReleased(caller) { + if (caller.keyCode == KEY.DOWN) { + rapidFall = false; + } +} +// изменить количество строк +function changeRows(caller) { + rows = caller.target.valueAsNumber; + container.changeSize(); + t0.reCalc(); + tv2.reCalc(); + refreshParams(); + prepareNewGame(); +} +// изменить количество колонок +function changeColumns(caller) { + columns = caller.target.valueAsNumber; + container.changeSize(); + t0.reCalc(); + tv2.reCalc(); + refreshParams(); + prepareNewGame(); +} +// переключить режим улитки +function changeSnailMode(caller) { + if (caller.target.checked) + reduction=REDUCTION_SNAIL; + else + reduction=REDUCTION_STEP; +} +// изменить тип объёмного изображения +function changeVolume(caller) { + const value = caller.target.value; + const old = vol; + if (value=="off") vol=VOLUME.FLAT; + if (value=="parallel") vol=VOLUME.PARALLEL; + if (value=="perspective") vol=VOLUME.PERSPECTIVE; + refreshDisabled(); + repaint(false); +} +// изменяем прозрачность фигур +function changeAlpha(caller) { + colors.alpha=caller.target.valueAsNumber; +} +// поворачиваем кубики, изменяем текущий угол +function rotate(axis, caller) { + deg[axis] = caller.target.valueAsNumber; + prepare3D(); +} +// вертикальная корректировка +function changeTv1(caller) { + tv1.y = caller.target.valueAsNumber; +} +// центральная точка на экране наблюдателя +function changeTv2(axis, caller) { + tv2[axis] = caller.target.valueAsNumber; +} +// удалённость источника проекции +function changeDistance2(caller) { + d2=caller.target.valueAsNumber; +} +// показать центральную точку +function showCenter(caller) { + tv2.show=!tv2.show; +} +// обновить игру и все настройки +function reload() { + vol=VOLUME.FLAT; + field3D=[]; + colors.setDefault(); + deg.setDefault(); + tv1.reCalc(); + tv2.reCalc(); + reduction=REDUCTION_STEP; + prepareNewGame(); + refreshParams(); +} +// обновить отображение параметров +function refreshParams() { + document.getElementById('speedometer').value=0; + document.getElementById('snail').checked=(reduction==REDUCTION_SNAIL); + document.getElementById('alpha').value=colors.alpha; + document.getElementById('oAlpha').value=colors.alpha + '%'; + document.getElementById('off').checked=(vol==VOLUME.FLAT); + document.getElementById('parallel').checked=(vol==VOLUME.PARALLEL); + document.getElementById('perspective').checked=(vol==VOLUME.PERSPECTIVE); + document.getElementById('rotateX').value = deg.x; + document.getElementById('rotateXo').value = deg.x + '°'; + document.getElementById('rotateY').value = deg.y; + document.getElementById('rotateYo').value = deg.y + '°'; + document.getElementById('rotateZ').value = deg.z; + document.getElementById('rotateZo').value = deg.z + '°'; + document.getElementById('tv1Y').value = tv1.y; + document.getElementById('tv1Yo').value = tv1.y; + document.getElementById('center').checked = tv2.show; + document.getElementById('tv2X').max = container.canvas.width; + document.getElementById('tv2X').value = tv2.x; + document.getElementById('tv2Xo').value = tv2.x; + document.getElementById('tv2Y').max = container.canvas.height; + document.getElementById('tv2Y').value = tv2.y; + document.getElementById('tv2Yo').value = tv2.y; + document.getElementById('tv2Z').max = d2/2; + document.getElementById('tv2Z').value = tv2.z; + document.getElementById('tv2Zo').value = tv2.z; + document.getElementById('dist').max = d2*2; + document.getElementById('dist').value = d2; + document.getElementById('oDist').value = d2; + refreshDisabled(); +} +// обновить доступность блоков +function refreshDisabled() { + document.getElementById('rotateX').disabled=(vol==VOLUME.FLAT); + document.getElementById('rotateY').disabled=(vol==VOLUME.FLAT); + document.getElementById('rotateZ').disabled=(vol==VOLUME.FLAT); + document.getElementById('tv1Y').disabled=(vol!=VOLUME.PARALLEL); + document.getElementById('center').disabled=(vol!=VOLUME.PERSPECTIVE); + document.getElementById('tv2X').disabled=(vol!=VOLUME.PERSPECTIVE); + document.getElementById('tv2Y').disabled=(vol!=VOLUME.PERSPECTIVE); + document.getElementById('tv2Z').disabled=(vol!=VOLUME.PERSPECTIVE); + document.getElementById('dist').disabled=(vol!=VOLUME.PERSPECTIVE); +} +// после загрузки всех частей страницы +document.addEventListener('DOMContentLoaded', function() { + refreshParams(); +}); diff --git a/jekyll_site/js/tetris-figures.js b/jekyll_site/js/tetris-figures.js new file mode 100644 index 0000000..17a873c --- /dev/null +++ b/jekyll_site/js/tetris-figures.js @@ -0,0 +1,42 @@ +// © Головин Г.Г., Набор фигур, 2023 +'use strict'; +// фигуры тетрамино +const FIGURE=[ + [[1,1],[1,0],[1,0]], + [[2,2],[0,2],[0,2]], + [[0,3],[3,3],[3,0]], + [[4,0],[4,4],[0,4]], + [[5,5],[5,5]], + [[6],[6],[6],[6]], + [[7,0],[7,7],[7,0]]]; +// полный набор фигур +FIGURE.set = function() { + let set = []; + for (let i=0; i6) return undefined; + this.type=num+1; + this.shape=FIGURE[num]; + } + // копия текущего объекта + clone() { + return new Figure(this.type-1); + } + // поворот по часовой стрелке + rotate() { + let nShape = [], shape = this.shape; + for (let y = 0; y < shape.length; y++) + for (let x = 0; x < shape[y].length; x++) { + if (nShape[x]==undefined) nShape[x] = []; + nShape[x][shape.length-y-1] = shape[y][x]; + } + this.shape=nShape; + } +}; diff --git a/jekyll_site/js/tetris-model.js b/jekyll_site/js/tetris-model.js new file mode 100644 index 0000000..e0359ec --- /dev/null +++ b/jekyll_site/js/tetris-model.js @@ -0,0 +1,245 @@ +// © Головин Г.Г., Логика игрового процесса, 2023 +'use strict'; +// игровое поле и его размеры +let field, rows = 20, columns = 10; +// ускорение падения фигур +const REDUCTION_STEP = 25; +const REDUCTION_SNAIL = 5; +let reduction = REDUCTION_STEP; +// скорость падения фигур +const START_DELAY = 600; +const MIN_DELAY = 80; +let stepDelay = START_DELAY; +let sleepTimeout, rapidFall = false; +// уровень, следующий уровень, счёт +let level, nextLevel, score; +// массив фигур тетрамино +const figures = FIGURE.set(); +// текущая фигура, следующая фигура +let currentFigure, nextFigure; +// статусы игры +const GAME = {RUN:0,LEVEL:1,PAUSE:2,OVER:3}; +// текущий статус +let status = GAME.PAUSE; +// подготовить новую игру +function prepareNewGame() { + field = []; + for (let i = 0; i < rows; i++) { + field[i] = []; + for (let j = 0; j < columns; j++) + field[i][j] = 0; + } + status = GAME.PAUSE; + level = 0; + score = 0; + nextLevel = (10 * columns) * (level + 1); + stepDelay = START_DELAY; + startFigureFall(); + repaint(); +} +// начало падения фигуры +function startFigureFall() { + currentFigure = nextFigure; + const x = Math.floor(columns/2+columns%2-1); + if (isFreeSpace(0, x)) { + doPlaceFigure(0, x); + repaint(); + } else { + status=GAME.OVER; + for (let y=-1; y>-4; y--) + if (isFreeSpace(y, x, false)) { + doPlaceFigure(y, x); + break; + } + repaint(); + return; + } + const rnd = Math.floor(Math.random() * figures.length); + nextFigure = figures[rnd].clone(); + if (status==GAME.LEVEL) return; + setTimeout(figureFall, 60); +} +// падение фигуры +function figureFall() { + if (rapidFall) + stepDown(); + else + sleepTimeout = setTimeout(stepDown, stepDelay); +} +// шаг вниз +function stepDown() { + sleepTimeout = undefined; + if (status==GAME.PAUSE) return; + if (isSpaceDown()) { + doStepDown(); + setTimeout(figureFall, 60); + } else { + mergeFigure(currentFigure.type); + let fullRows = 0; + for (let i = 0; i < rows; i++) + if (isFullRow(i)) { + for (let c = 3; c >= 0; c--) + setTimeout(blinkFullRow, 400 * fullRows + 100 * c, i, c); + setTimeout(slideDown, 400 * fullRows + 400, i); + fullRows++; + } + score += columns * [0,1,3,5,10][fullRows]; + if (score >= nextLevel) { + level++; + status = GAME.LEVEL; + nextLevel = (10 * columns) * (level + 1); + stepDelay = Math.max(MIN_DELAY,stepDelay-reduction); + } + startFigureFall(); + } +} +// свободное место ниже текущей фигуры +function isSpaceDown() { + for (let y = rows-2; y >= 0; y--) + for (let x = 0; x < columns; x++) + if (field[y][x]==11 && field[y+1][x]>0 && field[y+1][x]<11 + || field[y+1][x]==11 && y==rows-2) + return false; + return true; +} +// сдвинуть текущую фигуру вниз +function doStepDown() { + for (let y = rows-2; y >= 0; y--) + for (let x = 0; x < columns; x++) + if (field[y][x]==11) { + field[y][x]=0; + field[y+1][x]=11; + } + repaint(); +} +// завершение движения текущей фигуры +function mergeFigure(type) { + for (let x = 0; x < columns; x++) + for (let y = 0; y < rows; y++) + if (field[y][x] == 11) + field[y][x] = type; +} +// заполненная строка +function isFullRow(row) { + for (let x = 0; x < columns; x++) + if (field[row][x] == 0) + return false; + return true; +} +// моргание заполненной строки +function blinkFullRow(row, color) { + for (let x = 0; x < columns; x++) + field[row][x] = color; + repaint(); +} +// сдвинуть поле вниз +function slideDown(row) { + for (let y = row-1; y >= 0; y--) + for (let x = 0; x < columns; x++) + if (field[y+1][x]!=11 && field[y][x]!=11) + field[y+1][x]=field[y][x]; + repaint(); +} +// свободное место для текущей фигуры в пределах границ поля +function isFreeSpace(y, x, fullSize=true) { + const height = currentFigure.shape.length; + const wight = currentFigure.shape[0].length; + if (fullSize && (y<0 || y+height>rows || x<0 || x+wight>columns)) + return false; + for (let yy=0; yy0) + if (y+yy>=0 && y+yy=0 && x+xx0) + if (y+yy>=0 && y+yy=0 && x+xx0 && field[y][x-1]<11 + || field[y][x-1]==11 && x==1) + return false; + return true; +} +// сдвинуть текущую фигуру влево +function doStepLeft() { + for (let x = 1; x < columns; x++) + for (let y = 0; y < rows; y++) + if (field[y][x]==11) { + field[y][x]=0; + field[y][x-1]=11; + } +} +// для вызова из контроллера +function moveFigureRight() { + if (isSpaceRight()) + doStepRight(); + repaint(); +} +// свободное место справа от текущей фигуры +function isSpaceRight() { + for (let x = columns-2; x >=0; x--) + for (let y = 0; y < rows; y++) + if (field[y][x]==11 && field[y][x+1]>0 && field[y][x+1]<11 + || field[y][x+1]==11 && x==columns-2) + return false; + return true; +} +// сдвинуть текущую фигуру вправо +function doStepRight() { + for (let x = columns-2; x >=0; x--) + for (let y = 0; y < rows; y++) + if (field[y][x]==11) { + field[y][x]=0; + field[y][x+1]=11; + } +} +// для вызова из контроллера, поворот фигуры +function rotateFigure() { + let y = rows, x = columns; + for (let yy = 0; yy < rows; yy++) + for (let xx = 0; xx < columns; xx++) + if (field[yy][xx] == 11) { + if (y > yy) y = yy; + if (x > xx) x = xx; + } + if (y == rows || x == columns) return; + const old = currentFigure.shape; + doPlaceFigure(y, x, 0); + currentFigure.rotate(); + if (isFreeSpace(y, x)) + doPlaceFigure(y, x); + else if (isFreeSpace(y, x-1)) + doPlaceFigure(y, x-1); + else { + currentFigure.shape = old; + doPlaceFigure(y, x); + } + repaint(); +} +// после загрузки всех частей страницы, запускаем игру +document.addEventListener('DOMContentLoaded', function() { + addEventListener('keydown', keyPressed); + addEventListener('keyup', keyReleased); + const rnd = Math.floor(Math.random() * figures.length); + nextFigure = figures[rnd].clone(); + prepareNewGame(); +}); diff --git a/jekyll_site/js/tetris-view.js b/jekyll_site/js/tetris-view.js new file mode 100644 index 0000000..f0181d6 --- /dev/null +++ b/jekyll_site/js/tetris-view.js @@ -0,0 +1,235 @@ +// © Головин Г.Г., Визуализация игрового процесса, 2023 +'use strict'; +// размер клеточки, отступ +const size = 30, gap = 2; +// массив статусов игры +const statuses = ["ИГРА","УРОВЕНЬ","ПАУЗА","ЗАВЕРШЕНО"]; +// массив цветов для фигур +const colors = [ + 'rgba(240,240,240,1)', + 'rgba(150,0,0,1)', + 'rgba(0,0,150,1)', + 'rgba(150,150,0,1)', + 'rgba(0,150,150,1)', + 'rgba(0,150,0,1)', + 'rgba(150,0,150,1)', + 'rgba(150,80,80,1)']; +// прозрачность фигур +colors.setDefault = function() { + this.alpha = 0; +} +colors.setDefault(); +// получаем цвет, добавляем прозрачность только фигурам +colors.get = function(...numbers) { + for (let num of numbers) + if (num <= 0) return this[0]; + else if (num < colors.length) + return this[num].replace('1)', (100-this.alpha)/100 + ')'); + return this[0]; +} +// массив для кубиков +let field3D = []; +// центральная точка для поворотов +const t0 = {}; t0.reCalc = function() { + this.x = columns*(size+gap)/2; + this.y = rows*(size+gap); + this.z = (size+gap)*2; +}; +t0.reCalc(); +// угол поворота игрового поля с кубиками +let deg={}; deg.setDefault = function() { + this.x=-1; + this.y=0; + this.z=0; +}; +deg.setDefault(); +// параллельная проекция: центр и экран наблюдателя +let d1 = 600, tv1={}; tv1.reCalc = function() { + this.x=columns*(size+gap)/2; + this.y=(size+gap)*2; + this.z=(size+gap)*2; + d1 = Math.max(rows,columns)*(size+gap); +} +tv1.reCalc(); +// перспективная проекция: центр и экран наблюдателя +let d2 = 600, tv2 = {}; tv2.reCalc = function() { + this.x = columns*(size+gap)/2; + this.y = rows*(size+gap)/2; + this.z = (size+gap)*2; + this.show=false; + d2 = Math.max(rows,columns)*(size+gap); +}; +tv2.reCalc(); +// стакан с игрой +const container = {}; +container.canvas = document.getElementById('container'); +container.context = container.canvas.getContext('2d'); +container.changeSize = function() { + this.canvas.width = columns*size+(columns+1)*gap; + this.canvas.height = rows*size+(rows+1)*gap; +}; +container.changeSize(); +// перетаскивание центральной точки мышью +container.msBtnPressed = false; +container.canvas.onmouseup = ()=> container.msBtnPressed=false; +container.canvas.onmousedown = (caller)=> { + container.msBtnPressed=true; + container.canvas.onmousemove(caller); +} +container.canvas.onmousemove = (caller)=> { + if (vol==VOLUME.PERSPECTIVE && container.msBtnPressed && tv2.show) { + tv2.x = caller.offsetX; + tv2.y = caller.offsetY; + repaint(false); + refreshParams(); + } +} +// следующая фигура +const next = {}; +next.canvas = document.getElementById('next'); +next.context = next.canvas.getContext('2d'); +next.changeSize = function() { + this.canvas.width = 2*size+3*gap; + this.canvas.height = 4*size+5*gap; +}; +next.changeSize(); +// состояние игры +const statusView = {}; +statusView.level = document.getElementById('levelView'); +statusView.score = document.getElementById('scoreView'); +statusView.nextLevel = document.getElementById('nextLevelView'); +statusView.speed = document.getElementById('speedometer') +statusView.status = document.getElementById('statusView'); +statusView.refresh = function() { + this.level.innerText = level; + this.score.innerText = score; + this.nextLevel.innerText = nextLevel; + this.speed.value = speedometer(); + this.status.innerText = statuses[status]; +} +// обновление изображения +function repaint(refresh3D=true) { + // состояние игры + statusView.refresh(); + // стакан с игрой + if (vol==VOLUME.FLAT) { + drawCells(container.canvas, container.context, field, currentFigure.type); + } else { + if (refresh3D || field3D.length==0) + prepare3D(); + repaint3D(); + } + // следующая фигура + drawCells(next.canvas, next.context, nextFigure.shape); +} +// рисуем массив клеточек +function drawCells(canvas, context, array, color) { + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // обходим массив, рисуем клеточки + for (let y=0; y b.dist-a.dist); + // удаляем смежные стенки между соседними кубиками + for (let i=0, j=1; iMath.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock); + // обновляем стакан с игрой + drawCubes(container.canvas, container.context, proj); + // центральная точка перспективной проекции + if (vol==VOLUME.PERSPECTIVE && tv2.show) centerPoint(container.context); +} +// обходим массив, рисуем грани по точкам +function drawCubes(canvas, context, array) { + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // рисуем только видимые грани + const visible = function(face) { + // если есть хотя бы одна точка, которую можно нарисовать + for (let j = 0; j < face.length; j++) + if (face[j].x>-size && face[j].x-size && face[j].y + +

Ваш браузер не поддерживает Canvas

+
+
+ +### HTML {#html1} + +```html + +

Ваш браузер не поддерживает Canvas

+
+``` + +### JavaScript {#javascript1} + +```js +'use strict'; +let canvas = document.getElementById('canvas'); +// исходный массив точек-вершин квадрата +let square = [{x:50,y:50},{x:50,y:250},{x:250,y:250},{x:250,y:50}]; +// центр фигуры, вокруг него будем выполнять поворот +let t0 = {x:150, y:150}; +// угол поворота в градусах +let deg = 1; +``` +```js +// поворот фигуры и обновление изображения +function repaint() { + // поворачиваем исходный массив точек на угол + for (let i = 0; i < square.length; i++) + square[i] = rotateOnDegree(t0, square[i], deg); + // рисуем текущий массив точек + drawFigure(canvas, square); +} +``` +```js +// поворачиваем точку (t) на угол (deg) относительно точки (t0) +function rotateOnDegree(t0, t, deg) { + let t_new = {}; + // переводим угол поворота из градусов в радианы + let rad = (Math.PI / 180) * deg; + // рассчитываем координаты новой точки по формуле + t_new.x = t0.x+(t.x-t0.x)*Math.cos(rad)-(t.y-t0.y)*Math.sin(rad); + t_new.y = t0.y+(t.x-t0.x)*Math.sin(rad)+(t.y-t0.y)*Math.cos(rad); + // возвращаем новую точку + return t_new; +} +``` +```js +// рисуем фигуру по точкам из массива +function drawFigure(canvas, arr) { + let context = canvas.getContext('2d'); + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // обходим массив точек и соединяем их линиями + context.beginPath(); + for (let i = 0; i < arr.length; i++) + if (i == 0) + context.moveTo(arr[i].x, arr[i].y); + else + context.lineTo(arr[i].x, arr[i].y); + context.closePath(); + // рисуем линии на холсте + context.lineWidth = 2.2; + context.strokeStyle = '#222'; + context.stroke(); +} +``` +```js +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); +``` + +## Вращение в обратную сторону {#spinning-backwards} + +Добавим ещё одну точку, которую будем вращать в обратную сторону. Точка удалена от центра фигуры на +четверть длины стороны квадрата. Сместим центр квадрата в эту точку — сдвинем массив его вершин. Сам +квадрат будем вращать по часовой стрелке, а его центральную точку — против часовой стрелки. Этот код +работает вместе с предыдущим. + +
+ +

Ваш браузер не поддерживает Canvas

+
+
+ +### HTML {#html2} + +```html + +

Ваш браузер не поддерживает Canvas

+
+``` + +### JavaScript {#javascript2} + +```js +'use strict'; +let canvas2 = document.getElementById('canvas2'); +// текущий массив точек +let square2 = []; +// вращающаяся точка +let t2 = {x:100, y:100}; +``` +```js +// поворот фигуры и обновление изображения +function repaint2() { + // поворачиваем точку в обратную сторону + t2 = rotateOnDegree(t0, t2, -deg); + // обходим точки исходного массива и сдвигаем + for (let i = 0; i < square.length; i++) { + // текущая точка + square2[i] = {}; + // сдвигаем точку исходного массива + square2[i].x = square[i].x - t0.x + t2.x; + square2[i].y = square[i].y - t0.y + t2.y; + } + // рисуем текущий массив точек + drawFigure(canvas2, square2); +} +``` +```js +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint2,50)); +``` diff --git a/jekyll_site/ru/2023/01/10/spinning-cube-in-space.md b/jekyll_site/ru/2023/01/10/spinning-cube-in-space.md new file mode 100644 index 0000000..c1abea9 --- /dev/null +++ b/jekyll_site/ru/2023/01/10/spinning-cube-in-space.md @@ -0,0 +1,226 @@ +--- +title: Вращаем куб в пространстве +description: Рассматриваем разницу между параллельной и перспективной проекцией. Обе широко используются на практике для различных целей. В предыдущем примере мы вращали... +sections: [Линейная перспектива,Матрица поворота,Экспериментальная модель] +tags: [javascript,canvas,геометрия,графика,изображение,картинка,квадрат,куб] +scripts: [/js/classes-point-cube.js,/js/spinning-cube.js,/js/spinning-cube2.js] +styles: [/css/pomodoro1.css] +canonical_url: /ru/2023/01/10/spinning-cube-in-space.html +url_translated: /en/2023/01/11/spinning-cube-in-space.html +title_translated: Spinning cube in space +date: 2023.01.10 +--- + +Рассматриваем разницу между параллельной и перспективной проекцией. +Обе широко используются на практике для различных целей. В предыдущем примере мы +[вращали квадрат на плоскости]({{ '/ru/2023/01/05/spinning-square-on-plane.html' | relative_url }}) +— переходим в трёхмерное пространство. Теперь, чтобы отобразить на плоскости экрана поворот трёхмерного +объекта, нужно сначала создать трёхмерный объект, повернуть его на угол, срисовать с него проекцию и +отобразить на экране уже проекцию. + +Усложнённая модель, много кубиков: [Вращаем пространственный крест]({{ '/ru/2023/01/15/spinning-spatial-cross.html' | relative_url }}). + +
+
+ Параллельная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ Перспективная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ +*Параллельная проекция* — центр проекции бесконечно удалён от плоскости экрана наблюдателя, +размеры предметов выглядят одинаковыми. + +*Перспективная проекция* — параллельные линии сходятся в центре перспективы, предметы выглядят +уменьшающимися вдалеке. + +## Экспериментальная модель {#experimental-model} + +Размер куба 200, размер холста 300, начало координат находится в верхнем левом углу. Центр фигуры +в середине холста. Ось `X` направлена вправо, ось `Y` направлена вниз, ось `Z` направлена вдаль. +Выполняется поворот последовательно по всем трём осям: сначала по оси `X`, затем по оси `Y` и затем +по оси `Z`. Настройками модели можно управлять, например можно отключать лишнее вращение по осям и +изменять положение центра проекции на экране наблюдателя. + +
+ +

Ваш браузер не поддерживает Canvas

+
+
+
+
+ Вращение по осям: + + + + + + +
+Центр на экране наблюдателя: +
+ + + 150 +
+
+ + + 150 +
+
+ + + 60 +
+Удалённость центра проекции: +
+ + + 300 +
+
+ + +
+
+ +## Поворот точки в пространстве {#point-rotation-in-space} + +Рассчитываем новые координаты точки по формулам матрицы поворота для трёхмерного пространства. +Поворачиваем точку `t` относительно точки `t0` — получаем точку `t'`. + +*Поворот по оси `X`.* + +{% include image_svg.html src="/img/column-vector3dx.svg" style="width: 242.619pt; height: 59.0768pt;" +alt="&x'=x,&\\&y'=y_0+(y-y_0)cos\varphi-(z-z_0)sin\varphi,&\\&z'=z_0+(y-y_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} + +*Поворот по оси `Y`.* + +{% include image_svg.html src="/img/column-vector3dy.svg" style="width: 246.251pt; height: 59.0768pt;" +alt="&x'=x_0+(x-x_0)cos\varphi-(z-z_0)sin\varphi,&\\&y'=y,&\\&z'=z_0+(x-x_0)sin\varphi+(z-z_0)cos\varphi.&\\" %} + +*Поворот по оси `Z`.* + +{% include image_svg.html src="/img/column-vector3dz.svg" style="width: 246.793pt; height: 55.4753pt;" +alt="&x'=x_0+(x-x_0)cos\varphi-(y-y_0)sin\varphi,&\\&y'=y_0+(x-x_0)sin\varphi+(y-y_0)cos\varphi,&\\&z'=z.&\\" %} + +## Проекция точки {#point-projection} + +Экспериментальные формулы с возможностью смещения центра проекции `d0` на экране наблюдателя `tv`. +Отображаем точку пространства `t` на плоскость экрана — получаем точку `t'`. + +*Параллельная проекция.* + +{% include image_svg.html src="/img/oblique-projection.svg" style="width: 123.97pt; height: 37.2836pt;" +alt="&x'=x,&\\&y'=y+(y_v-z)/4.&\\" %} + +*Перспективная проекция.* + +{% include image_svg.html src="/img/central-projection.svg" style="width: 231.924pt; height: 37.2836pt;" +alt="&x'=x_v+d_0\cdot(x-x_v)/(z-z_v+d_0),&\\&y'=y_v+d_0\cdot(y-y_v)/(z-z_v+d_0).&\\" %} + +*Расстояние от точки до центра проекции.* + +{% include image_svg.html src="/img/euclidean-distance.svg" style="width: 319.911pt; height: 17.9328pt;" +alt="d(t,d_0)=\sqrt{(x-x_v)^2+(y-y_v)^2+(z-z_v+d_0)^2}." %} + +## Сортировка граней {#face-sorting} + +При создании кубика, вершины каждой грани задаём по часовой стрелке. При получении проекции, подставляем +в уравнение прямой три подряд идущие вершины, чтобы определить наклон грани и удалённость её от плоскости +проекции. + +*Уравнение прямой, проходящей через две точки.* + +{% include image_svg.html src="/img/linear-equation.svg" style="width: 137.171pt; height: 35.3194pt;" +alt="{(x-x_1)\over(y-y_1)}={(x_2-x_1)\over(y_2-y_1)}." %} + +## Описание алгоритма {#algorithm-description} + +Сначала обходим вершины куба и поворачиваем их на угол относительно центральной точки. Затем обходим грани +куба и получаем проекции входящих в них вершин. После этого сортируем проекции граней по удалённости. Затем +рисуем проекции на плоскости — соединяем точки линиями. Рисуем полупрозрачным цветом сперва дальние грани и +поверх них ближние, чтобы сквозь ближние грани было видно дальние. + +На каждом шаге отображения фигуры повторяем сортировку граней по удалённости, так как с изменением +угла поворота, координаты смещаются, и ближние грани становятся дальними. + +## Реализация на JavaScript {#implementation-in-javascript} + +{% include classes-point-cube-ru.md -%} + +Создаём объект и рисуем две проекции на плоскости. + +```js +'use strict'; +// рисовать будем сразу две картинки, +// объект будет один, а проекций будет много +const canvas1 = document.getElementById('canvas1'); +const canvas2 = document.getElementById('canvas2'); +// создаём объект +const cube = new Cube(50,50,50,200); +// центр фигуры, вокруг него будем выполнять поворот +const t0 = new Point(150,150,150); +// удалённость центра проекции +const d = 300; +// положение экрана наблюдателя +const tv = new Point(150,150,80); +// угол поворота в градусах +const deg = {x:0,y:1,z:0}; +``` +```js +// поворот фигуры и обновление изображения +function repaint() { + cube.rotate(deg, t0); + // рисуем параллельную проекцию + drawFigure(canvas1, cube.projection('parallel', tv)); + // рисуем перспективную проекцию + drawFigure(canvas2, cube.projection('perspective', tv, d)); +} +``` +```js +// рисуем фигуру по точкам из массива +function drawFigure(canvas, proj) { + let context = canvas.getContext('2d'); + // сортируем грани по их наклону + proj.sort((a,b) => b.clock-a.clock); + // очищаем весь холст целиком + context.clearRect(0, 0, canvas.width, canvas.height); + // обходим массив граней куба + for (let i = 0; i < proj.length; i++) { + // обходим массив точек и соединяем их линиями + context.beginPath(); + for (let j = 0; j < proj[i].length; j++) { + if (j == 0) { + context.moveTo(proj[i][j].x, proj[i][j].y); + } else { + context.lineTo(proj[i][j].x, proj[i][j].y); + } + } + context.closePath(); + // рисуем грань куба вместе с рёбрами + context.lineWidth = 2.2; + context.lineJoin = 'round'; + context.fillStyle = '#fff9'; + context.strokeStyle = '#222'; + context.fill(); + context.stroke(); + } +} +``` +```js +// после загрузки страницы, задаём интервал обновления изображения +document.addEventListener('DOMContentLoaded',()=>setInterval(repaint,50)); +``` diff --git a/jekyll_site/ru/2023/01/15/spinning-spatial-cross.md b/jekyll_site/ru/2023/01/15/spinning-spatial-cross.md new file mode 100644 index 0000000..4eda438 --- /dev/null +++ b/jekyll_site/ru/2023/01/15/spinning-spatial-cross.md @@ -0,0 +1,271 @@ +--- +title: Вращаем пространственный крест +description: Пишем алгоритм для поворота объёмной фигуры на угол вокруг своего центра по всем трём осям сразу. В предыдущем примере мы вращали куб в пространстве... +sections: [Объёмные фигуры,Матрица поворота,Экспериментальная модель] +tags: [javascript,canvas,геометрия,матрица,графика,изображение,картинка,квадрат,куб] +scripts: [/js/classes-point-cube.js,/js/spinning-spatial-cross.js,/js/spinning-spatial-cross2.js] +styles: [/css/pomodoro1.css] +canonical_url: /ru/2023/01/15/spinning-spatial-cross.html +url_translated: /en/2023/01/16/spinning-spatial-cross.html +title_translated: Spinning spatial cross +date: 2023.01.15 +--- + +Пишем алгоритм для поворота объёмной фигуры на угол вокруг своего центра по всем трём осям сразу. В предыдущем +примере мы [вращали куб в пространстве]({{ '/ru/2023/01/10/spinning-cube-in-space.html' | relative_url }}) +— теперь кубиков будет много, алгоритм будет почти такой же и формулы будем использовать те же. Рисуем два +варианта фигуры: *пространственный крест* и *крест-куб* в двух типах проекций, рассматриваем разницу. + +Тестирование экспериментального интерфейса: [Объёмный тетрис]({{ '/ru/2023/01/21/volumetric-tetris.html' | relative_url }}). + +## Пространственный крест {#spatial-cross} + +
+
+ Параллельная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ Перспективная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ +## Крест-куб {#cross-cube} + +
+
+ Параллельная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ Перспективная проекция + +

Ваш браузер не поддерживает Canvas

+
+
+
+ +*Параллельная проекция* — все кубики одинакового размера. + +*Перспективная проекция* — кубики выглядят уменьшающимися вдалеке. + +## Экспериментальная модель {#experimental-model} + +Слегка усложнённая версия из предыдущего примера — теперь кубиков много. В дополнение к предыдущим настройкам +можно поменять: вариант фигуры — *пространственный крест* или *крест-куб*, направление сортировки граней +— *линейная перспектива* или *обратная перспектива* и прозрачность стенок кубиков. + +
+ +

Ваш браузер не поддерживает Canvas

+
+
+
+
+
+ Вращение по осям: + + + + + + +
+ Центр на экране наблюдателя: +
+ + + 150 +
+
+ + + 150 +
+
+ + + 125 +
+ Удалённость центра проекции: +
+ + + 300 +
+
+ + +
+ Прозрачность кубиков: +
+ + + 20% +
+
+Вариант фигуры: +
+ + + + +
+Перспективная проекция: +
+ + + + +
+
+ +## Описание алгоритма {#algorithm-description} + +Подготавливаем матрицу из нулей и единиц, где единица означает кубик в определенном месте фигуры. Затем +обходим эту матрицу и заполняем массив кубиков с соответствующими координатами вершин. После этого запускаем +вращение по всем трём осям сразу. На каждом шаге обходим массив кубиков и получаем проекции их граней. Затем +сортируем массив граней по удалённости от центра проекции, обходим этот массив и выкидываем из него одинаковые +пары — это есть смежные стенки между соседними кубиками внутри фигуры. После этого полупрозрачным цветом рисуем +грани кубиков — сначала дальние и затем ближние, чтобы через ближние грани было видно дальние. + +## Реализация на JavaScript {#implementation-in-javascript} + +{% include classes-point-cube-ru.md -%} + +Создаём объекты по шаблонам и рисуем их проекции на плоскости. + +```js +'use strict'; +// матрицы-шаблоны для кубиков +const shape1 = [ // пространственный крест + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]], + [[0,0,0,0,0], [0,0,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,0]]]; +const shape2 = [ // крест-куб + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1]], + [[0,0,1,0,0], [0,0,0,0,0], [1,0,0,0,1], [0,0,0,0,0], [0,0,1,0,0]], + [[0,0,1,0,0], [0,0,1,0,0], [1,1,1,1,1], [0,0,1,0,0], [0,0,1,0,0]]]; +// размер кубика, количество кубиков в ряду, отступ +const size = 40, row = 5, gap = 50; +// массивы для кубиков +const cubes1 = [], cubes2 = []; +// обходим матрицы, заполняем массивы кубиками +for (let x=0; xMath.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock); + // сортируем грани по удалённости от центра проекции + perspective.sort((a,b)=>b.dist-a.dist); + // рисуем параллельную проекцию + drawFigure(cnv1, parallel); + // рисуем перспективную проекцию + drawFigure(cnv2, perspective); +} +``` +```js +// смежные стенки между соседними кубиками не рисуем +function noAdjacent(array) { + // сортируем грани по удалённости + array.sort((a,b) => b.dist-a.dist); + // удаляем смежные стенки между кубиками + for (let i=0, j=1; isetInterval(repaint,50)); +``` diff --git a/jekyll_site/ru/2023/01/21/volumetric-tetris.md b/jekyll_site/ru/2023/01/21/volumetric-tetris.md new file mode 100644 index 0000000..6764c29 --- /dev/null +++ b/jekyll_site/ru/2023/01/21/volumetric-tetris.md @@ -0,0 +1,50 @@ +--- +title: Объёмный тетрис +description: Общеобразовательная игра в широком смысле этого слова. При изучении языков программирования рекомендуется сначала написать свою версию и потом использовать... +sections: [Логическая игра,Экспериментальный интерфейс] +tags: [javascript,canvas,игра,головоломка,геометрия,матрица,графика,квадрат,куб,3d,трёхмерный] +scripts: [/js/classes-point-cube.js,/js/tetris-figures.js,/js/tetris-model.js,/js/tetris-controller.js,/js/tetris-view.js] +styles: [/css/pomodoro1.css] +canonical_url: /ru/2023/01/21/volumetric-tetris.html +url_translated: /en/2023/01/22/volumetric-tetris.html +title_translated: Volumetric tetris +date: 2023.01.21 +--- + +Общеобразовательная игра в широком смысле этого слова. При изучении языков программирования рекомендуется +сначала написать свою версию и потом использовать её для демонстрации и тестирования другого программного +обеспечения или оборудования. Трёхмерный интерфейс написан на JavaScript Canvas — логика самой игры двухмерная. + +Описание алгоритма графики: [Вращаем куб в пространстве]({{ '/ru/2023/01/10/spinning-cube-in-space.html' | relative_url }}). + +## Экспериментальный интерфейс {#experimental-interface} + +По умолчанию выключен — можно просто играть в тетрис. В дополнение к плоской версии добавлены два объёмных +варианта: *параллельная проекция* и *перспективная проекция* — параметры для каждого из них можно изменять. +Для перспективной проекции: можно изменять положение экрана наблюдателя и удалённость источника проекции. +Наблюдатель смотрит в центр изображения, а центр проекции удалён на расстояние, сопоставимое с размерами +игрового поля. Для параллельной проекции: можно изменять вертикальное положение. Для обеих проекций: можно +поворачивать игровое поле по всем трём осям. Центральная точка для поворотов — это центральная нижняя дальняя +точка поля. Для всех вариантов изображения: размер кубика — 32, размер квадратика — 30 и отступ — 2. +Начало координат расположено в верхней левой точке, оси направлены: `X` вправо, `Y` вниз и `Z` вдаль. + +*Пример использования:* начинаем игру, набираем какое-то количество фигур на поле, затем ставим игру на паузу, +и переключаемся между вариантами объёмного изображения, поворачиваем поле с фигурами, изменяем настройки. + +{% include volumetric-tetris-ru.html -%} + +## Игровой процесс {#gaming-process} + +Управление: кнопки на клавиатуре со стрелками — вправо, влево, вверх, вниз и кнопка пауза `pause`. + +Игровые очки начисляются за полностью собранные строки из элементов фигур. Количество набранных очков зависит +от количества собранных строк, по 10 очков за каждую строку, если в строке 10 кубиков, и кратно увеличивается, +если одновременно собрано: 2 строки — в 3 раза, 3 строки — в 5 раз, 4 строки — в 10 раз. + +Особенность игры: собранные строки сначала моргают и после этого исчезают, при этом игровой процесс на это +время не приостанавливается — текущая фигура продолжает падать. + +Уровень увеличивается при сборе 10 заполненных строк, то есть 100 очков, если в строке 10 кубиков. На каждом +новом уровне, скорость фигур повышается и на 21 уровне достигает максимума. В режиме улитки скорость повышается +в 5 раз медленнее и достигает максимума на 104 уровне. Текущая скорость отображается над игровым полем в виде +индикатора `meter`. diff --git a/jekyll_site/ru/index.md b/jekyll_site/ru/index.md new file mode 100644 index 0000000..e4e7a66 --- /dev/null +++ b/jekyll_site/ru/index.md @@ -0,0 +1,46 @@ +--- +title: Код с комментариями +description: Заметки на тему программирования с примерами кода и комментариями. Решения задач и описания решений. +sections: [Решения задач и описания решений] +tags: [javascript,canvas,геометрия,матрица,алгоритмы,реализация,графика,изображения,картинки,квадрат,куб] +canonical_url: / +url_translated: /en/ +title_translated: Code with comments +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Объёмный тетрис" %} +{%- capture article_brief %} +Общеобразовательная игра в широком смысле этого слова. При изучении языков программирования рекомендуется +сначала написать свою версию и потом использовать её для демонстрации и тестирования другого программного +обеспечения или оборудования. Трёхмерный интерфейс написан на JavaScript Canvas — логика самой игры двухмерная. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Вращаем пространственный крест" %} +{%- capture article_brief %} +Пишем алгоритм для поворота объёмной фигуры на угол вокруг своего центра по всем трём осям сразу. В предыдущем +примере мы вращали куб в пространстве — теперь кубиков будет много, алгоритм будет почти такой же и формулы +будем использовать те же. Рисуем два варианта фигуры: *пространственный крест* и *крест-куб* в двух типах проекций, +рассматриваем разницу. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Вращаем куб в пространстве" %} +{%- capture article_brief %} +Рассматриваем разницу между параллельной и перспективной проекцией. Обе широко используются на практике для +различных целей. В предыдущем примере мы вращали квадрат на плоскости — переходим в трёхмерное пространство. +Теперь, чтобы отобразить на плоскости экрана поворот трёхмерного объекта, нужно сначала создать трёхмерный +объект, повернуть его на угол, срисовать с него проекцию и отобразить на экране уже проекцию. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Вращаем квадрат на плоскости" %} +{%- capture article_brief %} +Напишем алгоритм на JavaScript для поворота квадрата на угол вокруг своего центра, повторим программу +средней школы. Для расчётов будем использовать класс `Math`, а для отображения результатов — Canvas. + +Начало координат находится в верхнем левом углу, координатные оси направлены вправо и вниз. Центральная точка +для поворотов `t0` расположена в центре фигуры. Квадрат — это массив из четырёх точек-вершин. Обходим массив +точек, поворачиваем каждую из них на угол, затем соединяем точки линиями и рисуем линии на холсте. Обновляем +картинку с частотой 20 кадров в секунду. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..8f54ea8 --- /dev/null +++ b/package.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "Подготовка архива для последующего развёртывания." +cd _site || exit +rm -rf ../pomodoro1.zip +7z a ../pomodoro1.zip ./* diff --git a/serve.sh b/serve.sh new file mode 100755 index 0000000..55b689c --- /dev/null +++ b/serve.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Локальное развёртывание для проверки корректности сборки." +jekyll serve --skip-initial-build --disable-disk-cache --host localhost +echo "Адрес сервера: http://localhost:4000"