Если вы не читали первую статью, то пожалуйста, перейдите оп ссылке :
Анимация туннеля - Часть 1
С возвращением! 🖖 Спасибо что продолжаете читать, я предполагаю что первая часть вам понравилась и вы хотели бы узнать больше! Как я и сказал в конце прошлой части, вы сейчас увидите как сгенерировать туннель с помощью частиц, вместо поверхности TubeGeometry()
.
Вычисление позиции частиц
Чтобы сделать, что мы задумали, надо генерировать круги частиц на всем протяжении пути. 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()
.
Сейчас у нас есть все, что нужно для каждого сегмента и мы можем начать генерировать частицы для каждого сегмента. Мы будем хранить каждую частицу как 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