8 мин.

Эффект путешествия в туннеле - Часть 2

Эффект путешествия в туннеле - Часть 2
Это вторая часть гайда по созданию эффекта путешествия в туннеле.
Если вы не читали первую статью, то пожалуйста, перейдите оп ссылке :
Анимация туннеля - Часть 1

С возвращением! 🖖 Спасибо что продолжаете читать, я предполагаю что первая часть вам понравилась и вы хотели бы узнать больше! Как я и сказал в конце прошлой части, вы сейчас увидите как сгенерировать туннель с помощью частиц, вместо поверхности TubeGeometry().

  • Это один из видов эффекта, который вы сможете создать в конце

{{% toc %}}

{{% /toc %}}

Вычисление позиции частиц

Чтобы сделать, что мы задумали, надо генерировать круги частиц на всем протяжении пути. Three.js использует такой же способ генерации геометрии трубы, с одним отличием, что он использует грани для создания поверхности трубы.
Для начала нам надо уточнить некоторые детали:

  • Радиус
  • Кол-во сегментов
  • Кол-во частиц в одном сегменте
// Кол-во кругов, из которых будет состоять весь маршрут
var segments = 500;
// Кол-во частей, которые будут формировать каждый круг
var circlesDetail = 10;
// Радиус трубы
var radius = 5;

Сейчас мы знаем кол-во частиц, которы нам надо для всего туннеля (спойлер: segments * circlesDetail), теперь нам надо вычислить Frenet frames.
Если честно, я не эксперт в этой области (слова автора оригинальной статьи 😉 ), но насколько я понял, frenet frames это значения вычисленные для всех сегментов трубы. Каждый кадр сделан при помощи векторов: касательной, нормали и бинормали (перпендикулярна двум предыдущим). Грубо говоря, эти значения показывают значение поворота для каждого сегмента и куда он смотрит.
Если вы хотите узнать как находятся эти значения, то советую прочитать эту статью на Wikipedia.
Благодаря Three.js нам не надо понимать, как это все работает и мы можем использовать функцию для генерации кадров (frames) из нашего пути.

var frames = path.computeFrenetFrames(segments, true);
// Второй параметр показывает наш путь закрыт или нет, в нашем случае это true.

В возвращаемым значение этой функции будет три массива Vector3().
frenetFrames
Сейчас у нас есть все, что нужно для каждого сегмента и мы можем начать генерировать частицы для каждого сегмента. Мы будем хранить каждую частицу как Vector3() в Geometry(), чтобы использовать позже.

// Создать пустую Geometry, куда мы будем записывать все частицы
var geometry = new THREE.Geometry();

Сейчас у нас есть положение каждой частицы сегмента. Это то, почему я буду итерироваться по всем сегментам. Я не буду описывать как это работает, все можно понять по коду и комментария к нему ниже! ⬇️

// Цикл по всем сегментам
for (var i = 0; i < segments; i++) {
  // Получение значения нормали сегмента из Frenet frames
  var normal = frames.normals[i];
  // Получение значения бинормали сегмента из Frenet frames
  var binormal = frames.binormals[i];

  // Вычисление индекса сегмента (от 0 до 1)
  var index = i / segments;

  // Получение координат точки в центре сегмента
  // Мы используем функию, которая применялась для передвижения
  // камеры в первой части
  var p = path.getPointAt(index);

  // Цикл для частиц, находящимся на каждом сегменте
  for (var j = 0; j < circlesDetail; j++) {
    // Клонирование точки в центр круга
    var position = p.clone();

    // Нам нужно получить позицию каждой точки, основываясь
    // на угле от 0 до Pi*2
    // Если вы хотите получить только половину трубы (как
    // на водной горке) то надо вычислить значения от 0 до Pi.
    var angle = (j / circlesDetail) * Math.PI * 2;

    // Вычисление синуса угла
    var sin = Math.sin(angle);
    // Вычисление отрицательного косинуса угла
    var cos = -Math.cos(angle);

    // Вычисление нормали (длинна равна единице прим. перев.
    // каждой точки, основываясь на угле и
    // векторов нормали, бинормали каждого сегмента.
    var normalPoint = new THREE.Vector3(0, 0, 0);
    normalPoint.x = cos * normal.x + sin * binormal.x;
    normalPoint.y = cos * normal.y + sin * binormal.y;
    normalPoint.z = cos * normal.z + sin * binormal.z;

    // Умножаем полученный вектор на радиус, чтобы у нас не была труба с единичным радиусом
    normalPoint.multiplyScalar(radius);

    // Добавляем значение нормали к центру круга
    position.add(normalPoint);

    // И добавляем вектор в нашу геометрию.
    geometry.vertices.push(position);
  }
}

Фуух, это самый сложный участок нашего проекта, который не так прост для понимания. Мне даже пришлось лазить в исходники Three.js, чтобы убедиться в том, что я не накосячил.

Вы можете проверить как это работает, посмотрев демо ниже, и увидеть как частицы добавляются одна за другой.
(Нажмите Ruturn, если вы потеряли туннель)

See the Pen Calculate the positions of the particles by Louis Hoebregts (@Mamboleoo) on CodePen.

Создание трубы

Сейчас у нас есть объект Geometry с вершинами. В Three.js мы можем создать прекрасное демо из частиц, используя Points конструктор. Это позволит вам рендерить простые точки с прекрасной производительностью. Вы можете видоизменить эти точки, например поменяв цвет или выбрав текстуру для них.
Точно так же, как когда-то мы создавали Mesh, нам опять понадобятся две вещи для создания объекта Points. Нам нужен материал и геометрия. С прошлого шага у нас есть геометрия, остается только определить материал.

var material = new THREE.PointsMaterial({
  size: 1, // Размер каждой точки
  sizeAttenuation: true,
  // Если мы хотим, чтобы точка изменяла свои в размеры в
  // зависимости от расстояния до камеры
  color: 0xff0000, // Цвет точки
});

Наконец, мы создаем наш объект Points и добавляем его на сцену таким образом:

var tube = new THREE.Points(geometry, material);
scene.add(tube);

See the Pen Create the tube by Louis Hoebregts (@Mamboleoo) on CodePen.

Заставляем все двигаться

Чтобы все пришло в движение, мы переиспользуем некоторый код из прошлой части.

var percentage = 0;
function render() {
  // Увеличиваем процент
  percentage += 0.0005;
  // Получаем точку, где камера должна находиться
  var p1 = path.getPointAt(percentage % 1);
  // Получаем точку, куда должна смотреть камера
  var p2 = path.getPointAt((percentage + 0.01) % 1);
  camera.position.set(p1.x, p1.y, p1.z);
  camera.lookAt(p2);

  // Рендерим цену
  renderer.render(scene, camera);

  // Вызываем следующий кадр
  requestAnimationFrame(render);
}

🎉 Ураа, у нас наконец-то появился уровень, сделанный из частиц 🎉

See the Pen Moving particle tunnel by Louis Hoebregts (@Mamboleoo) on CodePen.

Немного безумства

Что мы узнали во второй части достаточно для создания неограниченного кол-ва туннелей! Ниже находятся примеры туннелей, которые были основаны на том, что мы узнали в этой части.

Яркие туннели

Для этого примера я задаю для каждой точки случайный цвет. Ещё я добавил туман на сцену, для создания эффекта затухания в туннеле.

See the Pen Crazy 4 by Louis Hoebregts (@Mamboleoo) on CodePen.

// В начале создадим новый цвет, основываясь на индексе
var color = new THREE.Color("hsl(" + index * 360 * 4 + ", 100%, 50%)");
// Добавить новый цвет в массив всех цветов, расположенных в Geometry
geometry.colors.push(color);

var material = new THREE.PointsMaterial({
  size: 0.2,
  vertexColors: THREE.VertexColors, // Мы указываем, что цве надо брать из Geometry
});

// Добавить немножко тумана на сцену
scene.fog = new THREE.Fog(0x000000, 30, 150);

Квадратная пещера

В этом примере я использовал только Кубы. Для их расстановки я использовал объект Points, я создаю новый Mesh для каждой позиции точки. Ещё я раскашиваю его, основываясь на шуме Перлина.

See the Pen Crazy 5 by Louis Hoebregts (@Mamboleoo) on CodePen.

Восьмигранные туннели

See the Pen Crazy 6 by Louis Hoebregts (@Mamboleoo) on CodePen.

Тут я соединил точки в каждом сегменте, для создания линии. Я поигрался с углом и цветом для создания иллюзии поворота. ```js for (var i = 0; i < tubeDetail; i++) { // Создания новой геометрии для каждого сегмента var circle = new THREE.Geometry(); for (var j = 0; j < circlesDetail; j++) { // Добавить позицию каждой вершины circle.vertices.push(position); } // Создаем дубликат первой вершины для создания замкнутой линии circle.vertices.push(circle.vertices[0]); // Создаем материал с уникальным цветом var material = new THREE.LineBasicMaterial({ color: new THREE.Color("hsl("+(noise.simplex2(index*10,0)*60 + 300)+",50%,50%)") }); // Создание объекта линии var line = new THREE.Line(circle, material); // Добавляем его на сцену scene.add(line); } ``` --- Спасибо за прочтение моего поста о создании эффекта путешествия в туннеле! Пожалуйста, не стесняётесь задавать вопросы в комментариях, если вам что-то не понятно. 😉

https://codepen.io/Mamboleoo/post/tunnel-animation-2


https://github.com/grishy/blog/blob/hugo/content/post/tunnel-animation-2.md