Оригинальный адрес:Dev.to/Мистер Райан Флойд…
Автор гитхаб:github.com/MrRyanFloyd
Твиттер автора:twitter.com/mrryanfloyd
введение
Персональный сайт — второе резюме программиста. Если у вас крутая личная страница, рейтинг благосклонности интервьюера к вам будет постепенно повышаться.
В период карантина пользуюсьThree.js
а такжеAmmo.js
Сделал интерактивную 3D личную веб-страницу.
Адрес онлайн-просмотра:www.ryan-floyd.com/
Трехмерный мир Three.js
когда я былGoogle ExperimentsПобродив вокруг, я обнаружил, что во многих работах используетсяthree.js
написано.
three.js
это библиотека, упрощающая разработку 3D веб-приложений. Он родился в 2010 году, авторRicardo Cabello (Mr.doob),, имеет более 1300 участников на github и занимает 38-е место по количеству звезд во всех репозиториях.
когда видишьGoogle Experiments
Поиграв с этими крутыми 3D-эффектами, я решил начать учитьсяthree.js
.
Как работает Three.js
(Компонентная структура 3D-приложения, изображение взято изdiscoverthreejs.com)
Three.js
Упрощает отображение 3D-изображений в браузере, его основа основана наWebGL
, который позволяет браузеру рисовать 3D-изображения на холсте с помощью системной видеокарты.
WebGL
Он может рисовать только точки, линии и треугольники сам по себе, в то время какThree.js
правильноWebGL
Он инкапсулирован, поэтому мы можем легко создавать объекты, текстуры и выполнять 3D-расчеты.
использоватьThree.js
, мы добавляем все объекты (objects) в сцену (scene), а затем передаем данные, которые необходимо отрисовать, в рендерер (рендерер), рендерер отвечает за размещение сцены в сцене<canvas>
нарисовано на холсте.
(Архитектура приложения Three.js, картинки изthreejsfundamentals.org)
дляThree.js
Ядром приложения является сцена (scene object), представляющая собой граф сцены (scene graph).
В 3D-движке граф сцены представляет собой иерархический древовидный граф, где каждый узел в дереве представляет часть пространства. Эта структура немного похожа на дерево DOM, ноThree.js
Сцена (scene) больше похожа на виртуальный DOM, только обновляет и рендерит часть смены сцены. Основой всего этого является Three.jsWebGLRenderer
Объект в сцене также называетсяMesh
. существуетThree.js
в мире,MeshКгеометрияGeometry
(определяет форму предмета) +материалMaterial
(определенный внешний вид объекта) конфигурация.
Еще одним важным элементом сцены является камера.camera
, который определяет, какие части сцены нарисованы в каком визуальном эффектеcanvas
на холсте.
Затем идет анимация, для достижения анимации рендерер (рендерер) обычно используетrequestAnimationFrame()
метод, который отрисовывает обновления сцены 60 раз в секундуcanvas
начальство.requestAnimationFrame()
Принцип и использование метода могут относиться кMDN.
Следующий пример взят изThree.js
Официальная документация создает вращающийся 3D-куб.
<html>
<head>
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="https://unpkg.com/three@0.119.0/build/three.js"></script>
<script>
//创建场景和相机
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
//创建渲染器,设置尺寸为窗口尺寸,并将渲染后的元素添加到body
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//创建一个Mesh(绿色的3D立方体),并添加到场景中
var geometry = new THREE.BoxGeometry();
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
//设置照相机的位置
camera.position.z = 5;
//浏览器每次渲染的时候更新立方体的旋转角度
var animate = function () {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
</script>
</body>
</html>
Эффект следующий:
Физический движок Ammo.js
Ammo.js — это прямой порт физического движка Bullet на JavaScript (Bullet Physics — это движок физического моделирования с открытым исходным кодом). У меня нет глубокого понимания того, как физический движок работает под капотом.В двух словах, физический движок создает циклы на основе параметров, которые вы передаете (например, гравитации), и обновляет состояние в каждом цикле, чтобы имитировать естественные физические движение и эффекты столкновения.
Объекты в петле (обычно также твердые тела) с такими физическими свойствами, как сила, масса, инерция, трение и т. д. Каждый цикл, столкновения и взаимодействия обнаруживаются путем постоянной проверки положения, состояния и движения всех объектов. В случае взаимодействия положение объекта будет обновлено в зависимости от прошедшего времени и физических свойств объекта. Ниже приведен фрагмент моего кода, показывающий, как создать цикл физического движка и как добавить его к сфере в Three.js.
//引入库
import * as THREE from "three";
import * as Ammo from "./builds/ammo";
import {scene} from "./resources/world";
//初始化 Ammo.js 物理引擎
Ammo().then((Ammo) => {
// 创建物理世界
function createPhysicsWorld() {
//完全碰撞检测算法
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
// 重叠对/碰撞的调度计算
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
// 所有可能碰撞对的宽相位碰撞检测列表
let overlappingPairCache = new Ammo.btDbvtBroadphase();
// 使物体正确地交互,考虑重力、力、碰撞等
let constraintSolver = new Ammo.btSequentialImpulseConstraintSolver();
// 根据这些参数创建物理世界。 参考bullet physics文档
let physicsWorld = new Ammo.btDiscreteDynamicsWorld(
dispatcher,
overlappingPairCache,
constraintSolver,
collisionConfiguration
);
// 添加重力
physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));
}
//创建球体
function createBall(){
//球体参数
let pos = {x: 0, y: 0, z: 0};
let radius = 2;
let quat = {x: 0, y: 0, z: 0, w: 1};
let mass = 3;
//three.js相关代码
//创建球体并添加到场景中
let ball = new THREE.Mesh(new THREE.SphereBufferGeometry(radius), new THREE.MeshStandardMaterial({color: 0xffffff}));
ball.position.set(pos.x, pos.y, pos.z);
scene.add(ball);
//Ammo.js相关代码
//设置位置和旋转
let transform = new Ammo.btTransform();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(
new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
);
//设置物体运动
let motionState = new Ammo.btDefaultMotionState(transform);
//设置碰撞边界框
let collisionShape = new Ammo.btSphereShape(radius);
collisionShape.setMargin(0.05);
//设置惯性
let localInertia = new Ammo.btVector3(0, 0, 0);
collisionShape.calculateLocalInertia(mass, localInertia);
//生成创建刚体(物体)的结构信息
let rigidBodyStructure = new Ammo.btRigidBodyConstructionInfo(
mass,
motionState,
collisionShape,
localInertia
);
//基于上面的结构信息创建物体
let body = new Ammo.btRigidBody(rigidBodyStructure);
//当物体运动时,为其添加摩擦力
body.setFriction(10);
body.setRollingFriction(10);
// 将物体添加到物理世界,这样Ammo.js引擎才能不断更新物体的状态
physicsWorld.addRigidBody(body);
}
createPhysicsWorld();
createBall()
}
движение и взаимодействие
В физическом мире, моделируемом Ammo.js, взаимодействия вычисляются на основе свойств и сил.
У каждого объекта есть свойство ограничивающей рамки, и физический движок использует эту ограничивающую рамку для определения положения объекта.
После проверки ограничивающих рамок всех объектов в каждом цикле анимации, если ограничивающие рамки любых двух объектов находятся в одном и том же положении, движок запишет «столкновение» и соответствующим образом обновит объекты.Для твердых тел это означает, что два объекта не могут находиться в одном и том же положении.
Ниже приведен фрагмент кода, показывающий, как обновляются цикл рендеринга и физика мира.
//渲染框架
function renderFrame() {
//记录上一次渲染的时间
let deltaTime = clock.getDelta();
//基于用户输入,计算球会受到的力和产生的速度
moveBall();
//根据时间更新物理世界状态
updatePhysics(deltaTime);
//进行渲染
renderer.render(scene, camera);
// 循环
requestAnimationFrame(renderFrame);
}
//更新物理世界状态的方法定义
function updatePhysics(deltaTime) {
physicsWorld.stepSimulation(deltaTime, 10);
//遍历“刚体”列表,并更新物理世界中的所有刚体状态
for (let i = 0; i < rigidBodies.length; i++) {
//变量定义:three.js需要的meshObject,和ammo.js需要的ammoObject
let meshObject = rigidBodies[i];
let ammoObject = meshObject.userData.physicsBody;
//获取物体当前运动状态
let objectMotion = ammoObject.getMotionState();
//如果物体正在移动,则获取物体的当前位置和旋转信息
if (objectMotion) {
objectMotion.getWorldTransform(transform);
let mPosition = transform.getOrigin();
let mQuaternion = transform.getRotation();
// 更新物体的位置和旋转状态
meshObject.position.set(mPosition.x(), mPosition.y(), mPosition.z());
meshObject.quaternion.set(mQuaternion.x(), mQuaternion.y(), mQuaternion.z(), mQuaternion.w());
}
}
}
пользовательский ввод
Мы хотим, чтобы пользователи могли перемещать сферу в приложении как на настольных, так и на мобильных устройствах с сенсорным экраном.
Для событий клавиатуры, когда нажата клавиша со стрелкой, добавьте силу в соответствующем направлении к сфере, прослушивая события «keydown» и «keyup».
Для сенсорных экранов на экране создается джойстик-контроллер. Затем мы добавляем прослушиватели событий «touchstart», «touchmove» и «touchend» к элементу div (контроллеру), используемому для управления.
Контроллер отслеживает начальные, текущие и конечные координаты движения пальца пользователя, а затем соответствующим образом обновляет силы мяча при каждом рендеринге.
Ниже приведен лишь фрагмент кода контроллера, чтобы показать некоторые общие понятия. Полный код можно получить по адресу исходного кода в нижней части этой статьи.
// 在坐标平面上保持对当前球体运动的跟踪
let moveDirection = { left: 0, right: 0, forward: 0, back: 0 };
//控制器div在屏幕上的位置坐标
let coordinates = { x: 0, y: 0 };
//保存触摸事件的起始坐标的变量
let dragStart = null;
//创建控制器div元素
const stick = document.createElement("div");
//监听用户触摸点的移动
function handleMove(event) {
//没有移动,返回
if (dragStart === null) return;
//有移动,获取新的触摸点的x、y坐标
if (event.changedTouches) {
event.clientX = event.changedTouches[0].clientX;
event.clientY = event.changedTouches[0].clientY;
}
//根据触摸点的移动,计算出控制器div的实时坐标
const xDiff = event.clientX - dragStart.x;
const yDiff = event.clientY - dragStart.y;
const angle = Math.atan2(yDiff, xDiff);
const distance = Math.min(maxDiff, Math.hypot(xDiff, yDiff));
const xNew = distance * Math.cos(angle);
const yNew = distance * Math.sin(angle);
coordinates = { x: xNew, y: yNew };
//根据实时坐标更新样式
stick.style.transform = `translate3d(${xNew}px, ${yNew}px, 0px)`;
//根据坐标计算出球的运动方向
touchEvent(coordinates);
}
//根据用户的触摸点移动坐标计算出球的运动方向
function touchEvent(coordinates) {
// 向右运动
if (coordinates.x > 30) {
moveDirection.right = 1;
moveDirection.left = 0;
// 向左运动
} else if (coordinates.x < -30) {
moveDirection.left = 1;
moveDirection.right = 0;
} else {
moveDirection.right = 0;
moveDirection.left = 0;
}
//向前运动
if (coordinates.y > 30) {
moveDirection.back = 1;
moveDirection.forward = 0;
//向后运动
} else if (coordinates.y < -30) {
moveDirection.forward = 1;
moveDirection.back = 0;
} else {
moveDirection.forward = 0;
moveDirection.back = 0;
}
}
Вот эффект от использования джойстика:
Эпилог
Сейчас мы создали模拟真实物理世界的、可交互的3D应用
всех инструментов. Используйте свое воображение и желание творить красоту, чтобы создать собственное 3D-приложение! В эпоху Интернета любой человек учится всю жизнь.
Исходный код этого проекта можно найти на моем Github по адресу:GitHub.com/г-н Райан Флойд…
Если у вас есть какие-либо отзывы или вопросы, пожалуйста, оставьте сообщение или черезLinkedInСвяжитесь со мной!