Files
walkersim/src/renderer3d.js

175 lines
4.8 KiB
JavaScript

import * as THREE from 'three';
import { OrbitControls } from 'https://unpkg.com/three@0.161.0/examples/jsm/controls/OrbitControls.js';
const SIDE_LINKS = [
['A', 'B', 0xea0000],
['O', 'C', 0x00ff00],
['A', 'D', 0x111111],
['C', 'D', 0x111111],
['C', 'F', 0x111111],
['B', 'E', 0x111111],
['C', 'E', 0x111111],
['C', 'G', 0x111111],
];
const JOINT_KEYS = ['A', 'B', 'O', 'C', 'D', 'E', 'F', 'G'];
function toVec3(point, z = 0) {
return new THREE.Vector3(point.x, -point.y, z);
}
function createLine(color) {
const geometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(),
new THREE.Vector3(1, 0, 0),
]);
const material = new THREE.LineBasicMaterial({ color });
return new THREE.Line(geometry, material);
}
function setLinePoints(line, a, b) {
const pos = line.geometry.attributes.position;
pos.setXYZ(0, a.x, a.y, a.z);
pos.setXYZ(1, b.x, b.y, b.z);
pos.needsUpdate = true;
line.geometry.computeBoundingSphere();
}
export function create3DRenderer(mountEl) {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0f1116);
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 2000);
camera.position.set(0, 95, 170);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.outputColorSpace = THREE.SRGBColorSpace;
mountEl.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
controls.update();
scene.add(new THREE.AmbientLight(0xffffff, 0.65));
const keyLight = new THREE.DirectionalLight(0xffffff, 0.55);
keyLight.position.set(90, 120, 90);
scene.add(keyLight);
const fillLight = new THREE.DirectionalLight(0x9ab6ff, 0.25);
fillLight.position.set(-120, 80, -40);
scene.add(fillLight);
const grid = new THREE.GridHelper(320, 32, 0x2b3850, 0x1a2230);
grid.position.y = 0;
scene.add(grid);
const floorMat = new THREE.MeshStandardMaterial({
color: 0x232e40,
transparent: true,
opacity: 0.55,
roughness: 0.85,
metalness: 0.05,
});
const floor = new THREE.Mesh(new THREE.PlaneGeometry(360, 240), floorMat);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
const jointGeom = new THREE.SphereGeometry(1.35, 14, 10);
const nearJointMat = new THREE.MeshStandardMaterial({ color: 0xffff00 });
const farJointMat = new THREE.MeshStandardMaterial({ color: 0xffd95a });
const nearJoints = {};
const farJoints = {};
for (const key of JOINT_KEYS) {
nearJoints[key] = new THREE.Mesh(jointGeom, nearJointMat);
scene.add(nearJoints[key]);
farJoints[key] = new THREE.Mesh(jointGeom, farJointMat);
scene.add(farJoints[key]);
}
const nearGroup = [];
const farGroup = [];
for (const [from, to, color] of SIDE_LINKS) {
const nearLine = createLine(color);
nearLine.material.opacity = 1;
nearLine.material.transparent = false;
scene.add(nearLine);
nearGroup.push({ from, to, line: nearLine });
const farLine = createLine(color);
farLine.material.opacity = 0.55;
farLine.material.transparent = true;
scene.add(farLine);
farGroup.push({ from, to, line: farLine });
}
const sideOffset = 8;
function setVisible(visible) {
renderer.domElement.style.display = visible ? 'block' : 'none';
}
function resize() {
const width = Math.max(1, mountEl.clientWidth || 1);
const height = Math.max(1, mountEl.clientHeight || 1);
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
function resetCamera() {
camera.position.set(0, 95, 170);
controls.target.set(0, 0, 0);
controls.update();
}
function render(state) {
const near = state.pose?.near;
const far = state.pose?.far;
if (!near || !far) return;
for (const key of JOINT_KEYS) {
nearJoints[key].position.copy(toVec3(near[key], -sideOffset));
farJoints[key].position.copy(toVec3(far[key], sideOffset));
nearJoints[key].visible = state.showNear;
farJoints[key].visible = state.showFar;
}
for (const item of nearGroup) {
const a = toVec3(near[item.from], -sideOffset);
const b = toVec3(near[item.to], -sideOffset);
setLinePoints(item.line, a, b);
item.line.visible = state.showNear;
}
for (const item of farGroup) {
const a = toVec3(far[item.from], sideOffset);
const b = toVec3(far[item.to], sideOffset);
setLinePoints(item.line, a, b);
item.line.visible = state.showFar;
}
const floorY = state.ground?.floorY;
if (Number.isFinite(floorY)) {
floor.position.y = -floorY;
grid.position.y = -floorY;
}
controls.update();
renderer.render(scene, camera);
}
resize();
return {
resize,
render,
setVisible,
resetCamera,
};
}