175 lines
4.8 KiB
JavaScript
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,
|
|
};
|
|
}
|