Создайте геометрию
Мы будем использовать только два типа геометрии для каждой части поезда: геометрию коробки для кабины и геометрию цилиндра с различными параметрами для всего остального.
Геометрия кабины
Прежде всего, кабина коробчатой формы. Здесь будет достаточно одного BoxBufferGeometry. Создайте его со следующими параметрами:
Length = 2
Width = 2.25
Height = 1.5
function createGeometries() {
const cabin = new BoxBufferGeometry(2, 2.25, 1.5);
}
Различные значения длины, ширины и высоты дадут нам прямоугольную рамку, в отличие от куба, который мы использовали в предыдущих главах.
Геометрия носа
Затем создайте первый CylinderBufferGeometry для носа, используя следующие параметры:
Top radius = 0.75
Bottom radius = 0.75
Height = 3
Radial segments = 12
radiusTop и radiusBottom равны, поэтому мы получим цилиндр. Значение 12 для radialSegments в сочетании с Material.flatShading заставит цилиндр выглядеть так, как будто он был грубо вырезан.
function createGeometries() {
const cabin = new BoxBufferGeometry(2, 2.25, 1.5);
const nose = new CylinderBufferGeometry(0.75, 0.75, 3, 12);
}
Геометрия колес
Мы можем повторно использовать одну CylinderBufferGeometry для всех четырех колес, даже для большого заднего колеса. Вы можете повторно использовать геометрию в любом количестве сеток, а затем изменить положение, вращение и масштаб для каждой сетки. Это более эффективно, чем создание новой геометрии для каждой сетки, и вы должны делать это по возможности. Создайте геометрию цилиндра со следующими параметрами:
Top radius = 0.4
Bottom radius = 0.4
Height = 1.75
Radial segments = 16
Более высокое значение 16 для радиальных сегментов сделает колеса более округлыми. Мы создаем геометрию правильного размера для трех меньших колес, поэтому позже нам придется увеличить масштаб большого заднего колеса.
function createGeometries() {
const cabin = new BoxBufferGeometry(2, 2.25, 1.5);
const nose = new CylinderBufferGeometry(0.75, 0.75, 3, 12);
// we can reuse a single cylinder geometry for all 4 wheels
const wheel = new CylinderBufferGeometry(0.4, 0.4, 1.75, 16);
}
Геометрия дымохода
Наконец, дымоход. Это конус, а не цилиндр, но, как обсуждалось выше, если мы создадим геометрию цилиндра с разными значениями для radiusTop и radiusBottom, результатом будет форма конуса. На этот раз оставьте для радиальных сегментов значение по умолчанию 8.
Top radius = 0.3
Bottom radius = 0.1
Height = 0.5
Radial segments = default value
function createGeometries() {
const cabin = new BoxBufferGeometry(2, 2.25, 1.5);
const nose = new CylinderBufferGeometry(0.75, 0.75, 3, 12);
// we can reuse a single cylinder geometry for all 4 wheels
const wheel = new CylinderBufferGeometry(0.4, 0.4, 1.75, 16);
// different values for the top and bottom radius creates a cone shape
const chimney = new CylinderBufferGeometry(0.3, 0.1, 0.5);
}
Окончательный модуль геометрии
Наконец, верните все геометрические формы как объект в конце функции. Собирая все это вместе, вот последний модуль геометрии:
import { BoxBufferGeometry, CylinderBufferGeometry } from 'three';
function createGeometries() {
const cabin = new BoxBufferGeometry(2, 2.25, 1.5);
const nose = new CylinderBufferGeometry(0.75, 0.75, 3, 12);
// we can reuse a single cylinder geometry for all 4 wheels
const wheel = new CylinderBufferGeometry(0.4, 0.4, 1.75, 16);
// different values for the top and bottom radius creates a cone shape
const chimney = new CylinderBufferGeometry(0.3, 0.1, 0.5);
return {
cabin,
nose,
wheel,
chimney,
};
}
export { createGeometries };
Создаем меши
Все, что осталось, это создать сетки. Сначала мы создадим кабину, нос и дымоход по отдельности, затем мы создадим одно колесо и клонируем его, чтобы создать остальные три.
Сетка кабины и дымохода
Создайте сетки каюты и дымохода как обычно, используя материал корпуса для кабины и материал деталей для дымохода, затем переместите каждую сетку на место.
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
}
Значения, введенные для позиций, являются результатом некоторых проб и ошибок. Однако по мере практики вы обнаружите, что позиционирование объектов становится более интуитивным и быстрым. Как мы упоминали выше, нет необходимости вращать дымоход, так как он уже правильно ориентирован, когда мы его создаем.
Носовая сетка
Далее идет большой красный нос. Создайте сетку как обычно, используя geometries.nose и materials.body. На этот раз нам нужно повернуть сетку и расположить ее:
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
const nose = new Mesh(geometries.nose, materials.body);
nose.position.set(-1, 1, 0);
nose.rotation.z = Math.PI / 2;
}
Это завершает красный корпус поезда вместе с дымоходом.
Создайте прототип колеса
Теперь о колесах. Сначала мы создадим smallWheelRear, а затем клонируем его для создания остальных, как мы это сделали с нашей protoSphere из предыдущей главы. Создайте сетку smallWheelRear, а затем переместите ее на половину единицы по оси Y, чтобы расположить под поездом. Затем поверните его так, чтобы он лежал вдоль оси X.
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
const nose = new Mesh(geometries.nose, materials.body);
nose.position.set(-1, 1, 0);
nose.rotation.z = Math.PI / 2;
const smallWheelRear = new Mesh(geometries.wheel, materials.detail);
smallWheelRear.position.y = 0.5;
smallWheelRear.rotation.x = Math.PI / 2;
}
Когда мы клонируем это колесо для создания остальных колес, клонированные сетки унаследуют преобразования от прототипа. Это означает, что клонированные колеса начнут правильно вращаться и располагаться в нижней части поезда, и нам просто нужно разложить их вдоль оси X.
Создаем другие маленькие колеса
Клонируйте колесо, чтобы создать два других маленьких колеса, затем переместите каждое в положение по оси X:
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
const nose = new Mesh(geometries.nose, materials.body);
nose.position.set(-1, 1, 0);
nose.rotation.z = Math.PI / 2;
const smallWheelRear = new Mesh(geometries.wheel, materials.detail);
smallWheelRear.position.y = 0.5;
smallWheelRear.rotation.x = Math.PI / 2;
const smallWheelCenter = smallWheelRear.clone();
smallWheelCenter.position.x = -1;
const smallWheelFront = smallWheelRear.clone();
smallWheelFront.position.x = -2;
}
Создайте большое заднее колесо
Последний элемент нашего поезда - большое заднее колесо. Еще раз клонируйте маленькое колесо, затем переместите его в заднюю часть поезда. На этот раз нам также нужно масштабировать его, чтобы увеличить:
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
const nose = new Mesh(geometries.nose, materials.body);
nose.position.set(-1, 1, 0);
nose.rotation.z = Math.PI / 2;
const smallWheelRear = new Mesh(geometries.wheel, materials.detail);
smallWheelRear.position.y = 0.5;
smallWheelRear.rotation.x = Math.PI / 2;
const smallWheelCenter = smallWheelRear.clone();
smallWheelCenter.position.x = -1;
const smallWheelFront = smallWheelRear.clone();
smallWheelFront.position.x = -2;
const bigWheel = smallWheelRear.clone();
bigWheel.position.set(1.5, 0.9, 0);
bigWheel.scale.set(2, 1.25, 2);
}
Путем масштабирования мы увеличили диаметр большого колеса вдвое и увеличили его длину на 1,25. Но как мы определились, какие оси масштабировать?
Посмотрите еще раз на начальную позицию только что созданного CylinderBufferGeometry. Масштабирование происходит независимо от поворота, поэтому, несмотря на то, что мы повернули сетку, мы должны решить, как масштабировать, основываясь на исходной, неизмененной геометрии. Изучив эту диаграмму, мы видим, что для увеличения высоты нам нужно масштабировать по оси Y, а для увеличения диаметра нам нужно масштабировать на равную величину по оси X и оси Z. Это дает нам окончательное значение .scale (2,1.25,2).
Финальный модуль Meshes
Собирая все вместе, вот последний модуль .createMeshes. И снова мы вернули объект, содержащий все сетки, для использования в модуле Train.
import { Mesh } from 'three';
import { createGeometries } from './geometries.js';
import { createMaterials } from './materials.js';
function createMeshes() {
const geometries = createGeometries();
const materials = createMaterials();
const cabin = new Mesh(geometries.cabin, materials.body);
cabin.position.set(1.5, 1.4, 0);
const chimney = new Mesh(geometries.chimney, materials.detail);
chimney.position.set(-2, 1.9, 0);
const nose = new Mesh(geometries.nose, materials.body);
nose.position.set(-1, 1, 0);
nose.rotation.z = Math.PI / 2;
const smallWheelRear = new Mesh(geometries.wheel, materials.detail);
smallWheelRear.position.y = 0.5;
smallWheelRear.rotation.x = Math.PI / 2;
const smallWheelCenter = smallWheelRear.clone();
smallWheelCenter.position.x = -1;
const smallWheelFront = smallWheelRear.clone();
smallWheelFront.position.x = -2;
const bigWheel = smallWheelRear.clone();
bigWheel.position.set(1.5, 0.9, 0);
bigWheel.scale.set(2, 1.25, 2);
return {
nose,
cabin,
chimney,
smallWheelRear,
smallWheelCenter,
smallWheelFront,
bigWheel,
};
}
export { createMeshes };
Добавляем меши в поезд
Затем мы добавим сетки в Train. Сделаем это в конструкторе поезда.
class Train extends Group {
constructor() {
super();
this.meshes = createMeshes();
this.add(
this.meshes.nose,
this.meshes.cabin,
this.meshes.chimney,
this.meshes.smallWheelRear,
this.meshes.smallWheelCenter,
this.meshes.smallWheelFront,
this.meshes.bigWheel,
);
}
}
После этого поезд должен появиться в вашей сцене.
Крути колёса!
В качестве последнего штриха давайте начнем вращать колеса. Дайте поезду метод .tick, следуя тому же шаблону, который мы используем для всех анимированных объектов.
class Train extends Group {
constructor() {
// ... lines skipped for clarity
}
tick(delta) {}
}
Затем в World добавьте поезд в массив обновляемой информации.
constructor(container) {
camera = createCamera();
renderer = createRenderer();
scene = createScene();
loop = new Loop(camera, scene, renderer);
container.append(renderer.domElement);
const controls = createControls(camera, renderer.domElement);
const { ambientLight, mainLight } = createLights();
const train = new Train();
loop.updatables.push(controls, train);
scene.add(ambientLight, mainLight, train);
const resizer = new Resizer(container, camera, renderer);
scene.add(createAxesHelper(), createGridHelper());
}
Теперь нам нужно выяснить, на какой оси вращать колеса. Обратимся еще раз к схеме начальной геометрической ориентации цилиндра. Мы хотим, чтобы он вращался вокруг оси, проходящей через его центр, то есть оси Y. Тот факт, что мы повернули колеса так, чтобы они лежали вдоль оси Z, этого не меняет.
Далее нам нужно выяснить, с какой скоростью крутятся колеса. Мы будем вращать со скоростью 24 ∘ в секунду, чтобы дать нам один полный оборот каждые пятнадцать секунд. Как обычно, мы должны преобразовать это в радианы с помощью вспомогательной функции degToRad.
import { Group, MathUtils } from 'three';
import { createMeshes } from './meshes.js';
const wheelSpeed = MathUtils.degToRad(24);
class Train extends Group {
Наконец, обновите метод tick, чтобы вращать каждое из четырех колес. Здесь, как обычно, мы должны масштабировать посекундную скорость по дельте. Вернитесь к Цикл анимации для объяснения того, почему мы это делаем.
tick(delta) {
this.meshes.bigWheel.rotation.y += wheelSpeed * delta;
this.meshes.smallWheelRear.rotation.y += wheelSpeed * delta;
this.meshes.smallWheelCenter.rotation.y += wheelSpeed * delta;
this.meshes.smallWheelFront.rotation.y += wheelSpeed * delta;
}
Как только вы внесете это изменение, колесо должно начать вращаться, и на этом наш игрушечный поезд готов!
За пределами простых форм
Последние две главы показали нам как сильные стороны, так и ограничения встроенной геометрии three.js. Легко создать сто или тысячу клонов меша в цикле, и относительно легко создать простую модель игрушечного поезда. Однако создание объекта реального мира, такого как кошка или человек, скоро ошеломит нас. Даже для такой базовой модели, как эта, метод проб и ошибок, необходимых для перемещения частей поезда в нужное положение, занял некоторое время.
Чтобы создавать по-настоящему потрясающие модели, нам нужно использовать внешнюю программу, предназначенную для этой цели, а затем загрузить модель в three.js. В следующей главе мы увидим, как это сделать.
Завершение
Я немного поэкспериментировал и у меня получился достаточно примитивный автомобиль. Ссылку на оригинальную статью прикреплю ниже. Попробуйте создать что-то своё. У вас непременно получится.
Экспериментируйте в редакторе кода.
Спасибо за просмотр.