Навигация

• Онлайн: 1



Рейтинг@Mail.ru Индекс цитирования

Яндекс
creation/prograf/startrek.txt · Последнее изменение: 18.03.2026 15:02 — nozdr

Путь к звёздам

Мой путь к звёздам – это о том, как я пытался в словах и числах найти красоту, такую же, как и у звёзд на небе! И не только пытался, я иду по этому пути и сейчас… ищу красоту в чём её вроде бы и не видно, а она там есть. Как суслик.

Звёздные слова

Ещё в детском саду нас (по крайней мере, меня) научили считать и читать. И я узнал, что в русском алфавите 33 буквы. Все буквы в алфавите расположены в определённом порядке, который так и называют «алфавитный порядок».

Получается, что у каждой буквы есть свой номер. На базе этого знания давным-давно был придуман простейший шифр, который называется A1Z26 или в русском варианте А1Я33. В нём буквы алфавита заменяются на их порядковые номера.

Например, слово «ЗВЕЗДА» можно зашифровать как последовательность 9-3-6-9-5-1. Про такой шифр я узнал из книжки "Мастерская головоломок", 1964 [4Mb, djvu]

После прочтения этой книги у меня в голове всегда крутились разные мысли о том, как бы так «зашифровать» какое-нибудь слово новым хитрым шифром, а именно, превратив его в рисунок. Итак, слово «звезда». Что с ним такого можно сделать? Инструментарий для «понимания» появился у меня только в средней школе, когда нас научили понятию «функция» и научили строить их графики.

Итак, у меня была табличка с шестью дискретными значениями. Для начала я просто попробовал отобразить их на графике в виде точек.

Если делать грамотно и сейчас, то можно построить в экселе точечную диаграмму. Это, в общем-то, то же самое, что и на бумажке :))

Эти точки прям как звёзды в небе, напомнило мне созвездие. А как обычно рисуют созвездия? Самые яркие их звёзды соединяют линиями. Все знают «ковш» Большой Медведицы и W Кассиопеи. Я попробовал и здесь так соединить.

Затем стал пробовать проводить линии как-нибудь по-другому. Если начать в нулевой точке, затем пройтись по порядку по всем точкам и закончить снова в нулевой точке, то получится «нулевой контур».

А если нулевую точку не привлекать, а начинать с первой буквы, то получится «первичный контур»:

Затем я стал пробовать закрашивать получившиеся фигуры.

В принципе, уже получалось довольно красиво. Но хотелось получить что-то прям очень красивое, идеальное. Вспомнил, что вот в калейдоскопе вроде насыпаны разные кусочки стекляшек, а картинка получается симметричная. Значит, надо попробовать получившуюся фигуру каким-нибудь образом «отзеркалить». Для начала продублировал по четырём осям с поворотом на 90°.

Разве можно подумать, что такими фигурами зашифрованы слова?

Но это ещё не всё. Попробовал добавить больше «зеркальности» – наложил получившиеся фигуры сами на себя, но отзеркаленные.

Выглядит очень красиво, но из-за наложения потерялась информация, в таком виде «прочитать» зашифрованное слово будет невозможно, а ведь всё же хотелось не потерять изначальную информацию. А значит, решение получилось всё же не очень красивым.

Попробовал накладывать другим цветом и с прозрачностью:

Получилось похоже на военные кресты и ордена :)

Это сейчас я всё рисую на компьютере при помощи экселя и фотошопа, либо программирую на яваскрипте или питоне. А раньше я всё это вручную рисовал. В основном, я тренировался на кошках именах своих одноклассников и одногруппников (потому что продолжал заниматься такими вещами даже в институте).

NB: кстати, идея для бизнеса: можно делать именные медальоны :)

Звёздные часы

Следуя такой же идеологии я на javascript запрограммировал звёздные часы. Вычисляется время в формате ЧЧ:ММ:СС:мс, всего 9 цифр (для миллисекунд три цифры). Далее цифры «звездятся», замыкаясь по нулевому контуру и повторяются 4 раза с поворотом на 90°, а затем сами на себя зеркалятся. За счёт того, что это единый массив точек, составляющий один полигон, при раскраске используется интересный алгоритм «инвертирования» накладывающихся друг на друга частей. В результате при наложении информация не теряется и у многоугольника вместо сплошной получается «шахматная» раскраска.

Код javascript

Код javascript

<canvas id="canvas1"  width="280" height="280"></canvas>
<script>
function draw1() {
  ctx1.globalAlpha = 0.6;
  ctx1.fillStyle = '#fff';
  ctx1.fillRect(0,0,xx,yy); ctx1.beginPath(); ctx1.fillStyle='#f00'; ctx1.strokeStyle="#800"; ctx1.lineWidth=3;
 
  dat=new Date(); s=""+(((s=dat.getHours())<10)?"0"+s:s)+(((s=dat.getMinutes())<10)?"0"+s:s)
    +(((s=dat.getSeconds())<10)?"0"+s:s)+(((s=dat.getMilliseconds())<10)?"00"+s:((s>100)?s:"0"+s));
 
  var p = [];
  for(i=0;i<160;i+=20) {p[i]=0; p[i+1]=0;}
  for(i=1;i<10;i++) { smy=parseInt(s.substr(i-1,1))*my; imx=i*mx; i2=i*2;
    p[i2    ]= imx; p[i2+  1]=-smy;
    p[i2+ 20]= imx; p[i2+ 21]= smy;
    p[i2+ 40]=-imx; p[i2+ 41]=-smy;
    p[i2+ 60]=-imx; p[i2+ 61]= smy;
    p[i2+ 80]= smy; p[i2+ 81]=-imx;
    p[i2+100]= smy; p[i2+101]= imx;
    p[i2+120]=-smy; p[i2+121]=-imx;
    p[i2+140]=-smy; p[i2+141]= imx;
  }
  ctx1.moveTo(p[0]+d,p[1]+d); for(i=2;i<p.length-1;i+=2) {ctx1.lineTo(p[i]+d,p[i+1]+d)} ctx1.stroke(); ctx1.fill();
}
 
  var canvas1=document.getElementById("canvas1"), ctx1 = canvas1.getContext('2d');
  var d=140, xx=yy=d*2, m=13, mx=5, my=13;
  setInterval('draw1()',200);
 
</script>

Если использовать разные полигоны, разбить точки на группы и сделать полупрозрачность, тогда инвертирования не будет, но будет более красочно :)

Снежинки

У снежинок, в отличие от крестов и «звезд», получившихся выше, шестилучевая симметрия. А значит, что первичный или нулевой контур надо поворачивать не 4 раза по 90°, а 6 раз по 60°. При этом надо обязательно дополнительно зеркалить. Ну потому что снежинки, они прям симметричные-симметричные, с осевой симметрией, а не центральной.

И получится тогда как-то так:

Код javascript

Код javascript

<canvas id="canvas3" width="280" height="280"></canvas>
function draw3() {
  ctx3.globalAlpha = 0.6; 
  ctx3.fillStyle = '#fff'; 
  ctx3.fillRect(0, 0, xx, yy);
  ctx3.beginPath(); 
  ctx3.fillStyle = '#33f'; 
  ctx3.strokeStyle = "#668"; 
  ctx3.lineWidth = 3;
 
  // Получаем время в формате ЧЧММССмс (9 цифр)
  dat = new Date();
  s = "" + 
    ((dat.getHours() < 10) ? "0" + dat.getHours() : dat.getHours()) +
    ((dat.getMinutes() < 10) ? "0" + dat.getMinutes() : dat.getMinutes()) +
    ((dat.getSeconds() < 10) ? "0" + dat.getSeconds() : dat.getSeconds()) +
    (dat.getMilliseconds() < 10 ? "00" + dat.getMilliseconds() : 
     (dat.getMilliseconds() < 100 ? "0" + dat.getMilliseconds() : dat.getMilliseconds()));
 
  var p3 = [];
  let pointIndex = 0;
 
  // Начинаем с центра
  p3[pointIndex++] = 0;
  p3[pointIndex++] = 0;
 
  // Создаём 6 основных лучей (через 60 градусов)
  let mainAngleStep = (Math.PI * 2) / 6;
 
  for (let mainRay = 0; mainRay < 6; mainRay++) {
    let mainAngle = mainRay * mainAngleStep + Math.PI/6;
 
    // Для каждого основного луча создаём два графика:
    // 1. Сам график
    // 2. Его зеркальное отражение
 
    for (let mirror = 0; mirror < 2; mirror++) {
      // Определяем угол для текущего графика
      let angle;
      let mirrorFactor;
 
      if (mirror === 0) {
        angle = mainAngle + otkl;
        mirrorFactor = 1; // нормальное направление
      } else {
        angle = mainAngle - otkl;
        mirrorFactor = -1; // зеркальное отражение
      }
 
      // Единичные векторы для системы координат графика
      let rayX = Math.cos(angle);      // направление графика
      let rayY = Math.sin(angle);
      let perpX = Math.cos(angle + Math.PI/2); // перпендикуляр
      let perpY = Math.sin(angle + Math.PI/2);
 
      // Строим график по 9 точкам (по количеству цифр)
      for (let i = 0; i < 9; i++) {
        let digit = parseInt(s.substr(i, 1)); // текущая цифра
 
        // X координата на луче (расстояние от центра)
        let xOnRay = (i + 1) * mx;
 
        // Y координата (отклонение) зависит от цифры и зеркальности
        // mirrorFactor создаёт зеркальное отражение
        //let yOnPerp = (digit - 4.5) * my * mirrorFactor;
        let yOnPerp = digit * my * mirrorFactor;
 
        // Преобразуем в декартовы координаты
        let x = xOnRay * rayX + yOnPerp * perpX;
        let y = xOnRay * rayY + yOnPerp * perpY;
 
        p3[pointIndex++] = x;
        p3[pointIndex++] = y;
      }
 
      // Возвращаемся в центр для следующего графика
      // (кроме самого последнего графика)
      if (!(mainRay === 5 && mirror === 1)) {
        p3[pointIndex++] = 0;
        p3[pointIndex++] = 0;
      }
    }
  }
 
  // Рисуем замкнутую ломаную
  ctx3.moveTo(p3[0] + d, p3[1] + d);
  for (let i = 2; i < pointIndex; i += 2) {
    ctx3.lineTo(p3[i] + d, p3[i+1] + d);
  }
  ctx3.closePath();
  ctx3.stroke();
  ctx3.fill();
}
 
  var canvas3=document.getElementById("canvas3"), ctx3 = canvas3.getContext('2d');
  var d=140, xx=yy=d*2, m=13, mx=5, my=13, otkl=0;
  setInterval('draw3()',200);

Калейдоскоп

В калейдоскопе обычно расположены три длинных зеркала, перед которыми при вращении пересыпаются всякие цветные кусочки. На просвет из-за многократного отражения и повторения получаются изумительные и завораживающие узоры. Можно попытаться изобразить слова в калейдоскопном стиле.

Обычно калейдоскоп устроен следующим образом:

Физически у нас есть одно треугольное окно, в котором видно нечто. И это нечто потом отражается в трёх зеркалах. Чтобы повторить программно, нужно будет сначала нарисовать наше слово на сторонах треугольника, а потом уже замостить этим треугольником всю плоскость. Если применить эту идеологию к нашим звёздам и снежинкам, то получим вот такую красоту:

Код javascript

Код javascript

<canvas id="canvas4" width="400" height="400"></canvas>
 
function draw4() {
  var xx = yy = d4 * 2;
  ctx4.clearRect(0, 0, xx, yy);
 
  // Получаем время в формате ЧЧММССмс (9 цифр)
  dat = new Date();
  s = "" + 
    ((dat.getHours() < 10) ? "0" + dat.getHours() : dat.getHours()) +
    ((dat.getMinutes() < 10) ? "0" + dat.getMinutes() : dat.getMinutes()) +
    ((dat.getSeconds() < 10) ? "0" + dat.getSeconds() : dat.getSeconds()) +
    (dat.getMilliseconds() < 10 ? "00" + dat.getMilliseconds() : 
     (dat.getMilliseconds() < 100 ? "0" + dat.getMilliseconds() : dat.getMilliseconds()));
 
  // Параметры базового треугольника
  let centerX = d4;
  let centerY = d4;
  let size = 100; // немного уменьшим размер, чтобы больше поместилось
 
  // Вершины равностороннего треугольника (базовый)
  let angles = [
    -Math.PI/2,           // верх
    -Math.PI/2 + 2*Math.PI/3, // правая нижняя
    -Math.PI/2 + 4*Math.PI/3  // левая нижняя
  ];
 
  let baseV = [];
  for (let i = 0; i < 3; i++) {
    baseV.push({
      x: centerX + Math.cos(angles[i]) * size,
      y: centerY + Math.sin(angles[i]) * size
    });
  }
 
  let vTop = baseV[0];
  let vRight = baseV[1];
  let vLeft = baseV[2];
 
  // Вычисляем высоту треугольника
  let triangleHeight = Math.abs(vTop.y - (vRight.y + vLeft.y) / 2);
  let maxPointHeight = triangleHeight * 0.2;
 
  // Функция для вычисления расстояния от точки на стороне до противоположной стороны
  function distanceToOppositeSide(pointOnSide, sideStart, sideEnd, oppositeVertex) {
    let dx = sideEnd.x - sideStart.x;
    let dy = sideEnd.y - sideStart.y;
    let sideLength = Math.sqrt(dx * dx + dy * dy);
 
    let ux = dx / sideLength;
    let uy = dy / sideLength;
 
    let px = pointOnSide.x - sideStart.x;
    let py = pointOnSide.y - sideStart.y;
 
    let proj = px * ux + py * uy;
 
    let distToSide = Math.abs((sideEnd.x - sideStart.x) * (sideStart.y - oppositeVertex.y) - 
                              (sideStart.x - oppositeVertex.x) * (sideEnd.y - sideStart.y)) / sideLength;
 
    let t = proj / sideLength;
 
    return distToSide * (1 - Math.abs(t - 0.5) * 2);
  }
 
  // Функция для генерации точек графика на стороне треугольника
  function generateGraphPoints(start, end, thirdVertex) {
    let points = [];
    let numPoints = 9;
 
    let dx = end.x - start.x;
    let dy = end.y - start.y;
    let sideLength = Math.sqrt(dx * dx + dy * dy);
 
    let nx = -dy / sideLength;
    let ny = dx / sideLength;
 
    let midX = (start.x + end.x) / 2;
    let midY = (start.y + end.y) / 2;
    let toThirdX = thirdVertex.x - midX;
    let toThirdY = thirdVertex.y - midY;
 
    if (nx * toThirdX + ny * toThirdY < 0) {
      nx = -nx;
      ny = -ny;
    }
 
    points.push({ x: start.x, y: start.y });
 
    for (let i = 0; i < numPoints; i++) {
      let digit = parseInt(s.substr(i, 1));
      let t = (i + 1) / (numPoints + 1);
 
      let baseX = start.x * (1 - t) + end.x * t;
      let baseY = start.y * (1 - t) + end.y * t;
 
      let maxOffset = distanceToOppositeSide({x: baseX, y: baseY}, start, end, thirdVertex);
      let limitedMaxOffset = Math.min(maxOffset, maxPointHeight);
      let scale = limitedMaxOffset / 9;
      let offset = digit * scale;
 
      points.push({
        x: baseX + nx * offset,
        y: baseY + ny * offset
      });
    }
 
    points.push({ x: end.x, y: end.y });
 
    return points;
  }
 
  // Генерируем точки графиков для базового треугольника
  let points1 = generateGraphPoints(vTop, vRight, vLeft);
  let points2 = generateGraphPoints(vRight, vLeft, vTop);
  let points3 = generateGraphPoints(vLeft, vTop, vRight);
 
  // Функция для создания полигона на одной стороне
  function createSidePolygon(sidePoints, sideStart, sideEnd) {
    let polygon = [];
    polygon.push(sideStart);
    for (let i = 1; i < sidePoints.length - 1; i++) {
      polygon.push(sidePoints[i]);
    }
    polygon.push(sideEnd);
    polygon.push(sideStart);
    return polygon;
  }
 
  // Создаём три полигона для базового треугольника
  let basePolygons = [
    createSidePolygon(points1, vTop, vRight),
    createSidePolygon(points2, vRight, vLeft),
    createSidePolygon(points3, vLeft, vTop)
  ];
 
  // Функция для отражения точки относительно прямой
  function reflectPoint(point, lineStart, lineEnd) {
    let dx = lineEnd.x - lineStart.x;
    let dy = lineEnd.y - lineStart.y;
    let lengthSq = dx * dx + dy * dy;
 
    // Проекция точки на прямую
    let t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / lengthSq;
    let projX = lineStart.x + t * dx;
    let projY = lineStart.y + t * dy;
 
    // Отражение
    return {
      x: 2 * projX - point.x,
      y: 2 * projY - point.y
    };
  }
 
  // Функция для отражения полигона
  function reflectPolygon(polygon, lineStart, lineEnd) {
    return polygon.map(p => reflectPoint(p, lineStart, lineEnd));
  }
 
  // Структура для хранения треугольника с его полигонами
  class TriangleTile {
    constructor(v0, v1, v2, polygons, reflectionChain = []) {
      this.v = [v0, v1, v2];
      this.polygons = polygons; // массив из трёх полигонов для трёх сторон
      this.reflectionChain = reflectionChain;
    }
 
    // Получить ключ для уникальной идентификации треугольника
    getKey() {
      let precision = 1;
      let keys = this.v.map(p => [
        Math.round(p.x / precision),
        Math.round(p.y / precision)
      ]);
      keys.sort((a,b) => a[0]-b[0] || a[1]-b[1]);
      return keys.map(k => `${k[0]},${k[1]}`).join('|');
    }
  }
 
  // Создаём базовый тайл
  let baseTile = new TriangleTile(vTop, vRight, vLeft, basePolygons);
 
  // Очередь для BFS
  let tiles = [baseTile];
  let queue = [baseTile];
  let visited = new Set([baseTile.getKey()]);
 
  // BFS для генерации отражений (до 3 уровней глубины, чтобы не перегружать)
  while (queue.length > 0) {
    let current = queue.shift();
 
    // Для каждой стороны треугольника создаём отражение
    for (let side = 0; side < 3; side++) {
      let sideStart = current.v[side];
      let sideEnd = current.v[(side + 1) % 3];
 
      // Отражаем все вершины
      let newV = current.v.map(p => reflectPoint(p, sideStart, sideEnd));
 
      // Проверяем, не слишком ли далеко ушел треугольник
      let tooFar = newV.some(p => 
        Math.abs(p.x - centerX) > 500 || Math.abs(p.y - centerY) > 500
      );
 
      if (tooFar) continue;
 
      // Создаём новый тайл
      let newTile = new TriangleTile(
        newV[0], newV[1], newV[2],
        current.polygons.map(p => reflectPolygon(p, sideStart, sideEnd)),
        [...current.reflectionChain, side]
      );
 
      let key = newTile.getKey();
      if (!visited.has(key)) {
        visited.add(key);
        tiles.push(newTile);
        queue.push(newTile);
      }
    }
 
    // Ограничиваем количество тайлов для производительности
    if (tiles.length > 200) break;
  }
 
  // Рисуем все тайлы
  tiles.forEach(tile => {
    // Для каждого из трёх полигонов в тайле
    tile.polygons.forEach((polygon, index) => {
      // Выбираем цвет в зависимости от стороны и положения
      let hue;
      if (index === 0) hue = 0;      // красный
      else if (index === 1) hue = 120; // зеленый
      else hue = 240;                  // синий
 
      // Добавляем небольшой сдвиг оттенка в зависимости от положения
      let posHue = (tile.v[0].x + tile.v[0].y) * 0.1;
 
      ctx4.beginPath();
      ctx4.fillStyle = `hsla(${hue + posHue}, 80%, 60%, 0.25)`;
 
      ctx4.moveTo(polygon[0].x, polygon[0].y);
      for (let i = 1; i < polygon.length; i++) {
        ctx4.lineTo(polygon[i].x, polygon[i].y);
      }
      ctx4.fill();
    });
  });
 
  // Рисуем графики поверх заливки (только для базового тайла, иначе слишком много)
  // Но для красоты нарисуем для нескольких ближайших
 
  // Рисуем графики для всех тайлов (но только линии)
  tiles.forEach(tile => {
    tile.polygons.forEach((polygon, index) => {
      let hue;
      if (index === 0) hue = 0;
      else if (index === 1) hue = 120;
      else hue = 240;
 
      ctx4.beginPath();
      ctx4.strokeStyle = `hsla(${hue}, 80%, 40%, 0.5)`;
      ctx4.lineWidth = 1;
 
      ctx4.moveTo(polygon[0].x, polygon[0].y);
      for (let i = 1; i < polygon.length; i++) {
        ctx4.lineTo(polygon[i].x, polygon[i].y);
      }
      ctx4.stroke();
    });
  });
 
  // Рисуем контуры базового треугольника (для ориентации)
  ctx4.beginPath();
  ctx4.strokeStyle = "#fff";
  ctx4.lineWidth = 1;
  ctx4.moveTo(vTop.x, vTop.y);
  ctx4.lineTo(vRight.x, vRight.y);
  ctx4.lineTo(vLeft.x, vLeft.y);
  ctx4.closePath();
  ctx4.stroke();
}
 
  var canvas4=document.getElementById("canvas4"), ctx4 = canvas4.getContext('2d');
  var mx=5, my=13, d4=200;
  setInterval('draw4()',100);

Звёздные пути

Если взять «контурную» идею отображения, но только вместо рисования графика значений букв или цифр времени ходить по реальным путям, то получим «звёздные пути». Что это за «реальные пути» такие? А вот какие. Например, путь обхода конём шахматной доски.

Есть известная классическая задача обхода конём шахматной доски. Берём готовый маршрут.

Закольцовываем его и раскрашиваем «шахматкой» – получаем «звёздный путь» :)

Аналогичный подход можно применить к волшебным квадратам, если последовательно обходить в нём числа. Так как квадраты бывают разного порядка и чем выше порядок, тем больше вариантов размещения в них чисел, то каждому такому квадрату будет соответствовать свой уникальный узор.

Например, вот для такого магического квадрата 9х9 …

… получается вот такая магическая решёточка.

Это способ превращения вроде бы невзрачных чисел и таблиц в красивые картинки можно попробовать применить, в принципе, к любым последовательностям чисел. Если в этих последовательностях есть какие-то закономерности, то и картинки будут получаться говорящие. Если же последовательности будут «немые», то их можно «озвучить», применив зеркалирование, повторение, отражение, раскраску – тогда точно получится красиво!


Инструменты страницы

Инструменты пользователя