# \[Перевод] Эффект путешествия в туннеле - Часть 2

> Продолжаем строить туннель на Three.js. Во второй части усложняем задачу: отказываемся от готовой геометрии и генерируем стенки туннеля из тысяч отдельных частиц.

**Published:** 2017-07-28

**Canonical:** <https://grishy.dev/ru/posts/tunnel-animation-2/>

**Translations:** [Русский](https://grishy.dev/ru/posts/tunnel-animation-2.md)

![\[Перевод\] Эффект путешествия в туннеле - Часть 2](https://grishy.dev/_astro/cover.xFRZ6pON.avif)

---

> **Note**
>
> Это вторая часть гайда по созданию эффекта путешествия в туннеле.
>
> \
>
>
> Если вы не читали первую статью, то пожалуйста, перейдите по ссылке:
>
> \
> [Анимация туннеля - Часть 1](https://grishy.dev/ru/posts/tunnel-animation-1/)

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

- [![](https://s3-us-west-2.amazonaws.com/s.cdpn.io/127738/tunnel_mario.jpg)](https://codepen.io/Mamboleoo/full/NdGPvJ/)

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

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

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

- Радиус
- Кол-во сегментов
- Кол-во частиц в одном сегменте

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

Сейчас мы знаем количество частиц, которое нужно для всего туннеля (спойлер: segments \* circlesDetail), теперь надо вычислить `Frenet frames`.
Если честно, я не эксперт в этой области (слова автора оригинальной статьи 😉 ), но насколько я понял, frenet frames это значения вычисленные для всех сегментов трубы. Каждый кадр сделан при помощи векторов: касательной, нормали и бинормали (перпендикулярна двум предыдущим). Грубо говоря, эти значения показывают значение поворота для каждого сегмента и куда он смотрит.\
Если вы хотите узнать как находятся эти значения, то советую прочитать эту [статью на Wikipedia](https://ru.wikipedia.org/wiki/%D0%A2%D1%80%D1%91%D1%85%D0%B3%D1%80%D0%B0%D0%BD%D0%BD%D0%B8%D0%BA_%D0%A4%D1%80%D0%B5%D0%BD%D0%B5).\
Благодаря Three.js нам не надо понимать, как это все работает и мы можем использовать функцию для генерации кадров (frames) из нашего пути.

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

В возвращаемым значение этой функции будет три массива `Vector3()`.\
![Координатные оси Frenet для сегмента туннеля](https://grishy.dev/_astro/frenetFrames.BDorJr06.avif)\
Сейчас у нас есть все, что нужно для каждого сегмента и мы можем начать генерировать частицы для каждого сегмента. Мы будем хранить каждую частицу как `Vector3()` в `Geometry()`, чтобы использовать позже.

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

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

```js
// Цикл по всем сегментам
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, чтобы убедиться в том, что я не накосячил.

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

(Нажмите Return, если вы потеряли туннель)

See the Pen&#x20;
[Calculate the positions of the particles](https://codepen.io/Mamboleoo/pen/Npmqyo/) by
Louis Hoebregts ([@Mamboleoo](https://codepen.io/Mamboleoo)) on&#x20;
[CodePen](https://codepen.io).

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

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

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

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

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

See the Pen [Create the tube](https://codepen.io/Mamboleoo/pen/aWzGrJ/) by Louis
Hoebregts ([@Mamboleoo](https://codepen.io/Mamboleoo)) on&#x20;
[CodePen](https://codepen.io).

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

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

```js
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](https://codepen.io/Mamboleoo/pen/evxYob/) by Louis
Hoebregts ([@Mamboleoo](https://codepen.io/Mamboleoo)) on&#x20;
[CodePen](https://codepen.io).

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

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

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

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

See the Pen [Crazy 4](https://codepen.io/Mamboleoo/pen/BWMyZr/) by Louis Hoebregts (
[@Mamboleoo](https://codepen.io/Mamboleoo)) on [CodePen](https://codepen.io)
.

```js
// В начале создадим новый цвет, основываясь на индексе
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](https://codepen.io/Mamboleoo/pen/OpdPvZ/) by Louis Hoebregts (
[@Mamboleoo](https://codepen.io/Mamboleoo)) on [CodePen](https://codepen.io)
.

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

See the Pen [Crazy 6](https://codepen.io/Mamboleoo/pen/EmaReQ/) by Louis Hoebregts ([@Mamboleoo](https://codepen.io/Mamboleoo)) on [CodePen](https://codepen.io).

Тут я соединил точки в каждом сегменте, для создания линии. Я поигрался с углом и цветом для создания иллюзии поворота.

```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>
