Compare commits
No commits in common. "e15f831abcbb28419e7f94756be394be26eede22" and "5de79305b78b103c68c5e42601d3b09c1df870fb" have entirely different histories.
e15f831abc
...
5de79305b7
|
|
@ -31,13 +31,12 @@ export type ThreeViewProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupScene(size: { w: number, h: number }): { scene: THREE.Scene, camera: THREE.PerspectiveCamera } {
|
function setupScene(size: { w: number, h: number }): { scene: THREE.Scene, camera: THREE.PerspectiveCamera } {
|
||||||
THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
|
|
||||||
|
|
||||||
// --- Scene & Camera ---
|
// --- Scene & Camera ---
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x0a0a12);
|
scene.background = new THREE.Color(0x0a0a12);
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(55, size.w / size.h, 0.1, 100);
|
const camera = new THREE.PerspectiveCamera(55, size.w / size.h, 0.1, 100);
|
||||||
|
camera.position.set(4, 3, 6);
|
||||||
|
|
||||||
// --- Lights ---
|
// --- Lights ---
|
||||||
scene.add(new THREE.AmbientLight(0xffffff, 0.3));
|
scene.add(new THREE.AmbientLight(0xffffff, 0.3));
|
||||||
|
|
@ -51,20 +50,15 @@ function setupScene(size: { w: number, h: number }): { scene: THREE.Scene, camer
|
||||||
pt.position.set(-3, 2, -3);
|
pt.position.set(-3, 2, -3);
|
||||||
scene.add(pt);
|
scene.add(pt);
|
||||||
|
|
||||||
// const plane = new THREE.Mesh(
|
// --- Floor & Grid ---
|
||||||
// new THREE.PlaneGeometry(14, 14),
|
const plane = new THREE.Mesh(
|
||||||
// new THREE.MeshStandardMaterial({ color: 0x080810 })
|
new THREE.PlaneGeometry(14, 14),
|
||||||
// );
|
new THREE.MeshStandardMaterial({ color: 0x080810 })
|
||||||
// plane.rotation.x = -Math.PI / 2;
|
);
|
||||||
// plane.receiveShadow = true;
|
plane.rotation.x = -Math.PI / 2;
|
||||||
|
plane.receiveShadow = true;
|
||||||
// scene.add(plane);
|
// scene.add(plane);
|
||||||
|
// scene.add(new THREE.GridHelper(14, 14, 0x222233, 0x111122));
|
||||||
const xyGrid = new THREE.GridHelper(14, 14, 0x222233, 0x111122);
|
|
||||||
xyGrid.rotation.x = Math.PI / 2
|
|
||||||
scene.add(xyGrid);
|
|
||||||
|
|
||||||
const axesHelper = new THREE.AxesHelper(2).setColors('red', 'green', 'blue');
|
|
||||||
scene.add(axesHelper);
|
|
||||||
|
|
||||||
return { scene, camera };
|
return { scene, camera };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ export const Viewport = function () {
|
||||||
function handleMouseMove(e: ThreeViewMouseEventArgs) {
|
function handleMouseMove(e: ThreeViewMouseEventArgs) {
|
||||||
state.setHitTest(e.screenPosition, e.hitResults);
|
state.setHitTest(e.screenPosition, e.hitResults);
|
||||||
|
|
||||||
sceneHelper.clearHints();
|
sceneHelper.clear();
|
||||||
if (e.hitResults.hits.length) {
|
if (e.hitResults.hits.length) {
|
||||||
e.hitResults.hits.forEach((hit) => {
|
e.hitResults.hits.forEach((hit) => {
|
||||||
sceneHelper.showPointHint(hit.object.uuid, hit.intersection.point);
|
sceneHelper.showPoint(hit.object.uuid, hit.point);
|
||||||
})
|
})
|
||||||
// console.log(e.position);
|
// console.log(e.position);
|
||||||
// console.log(e.hitTest.objects.map((o) => o));
|
// console.log(e.hitTest.objects.map((o) => o));
|
||||||
|
|
@ -26,7 +26,7 @@ export const Viewport = function () {
|
||||||
|
|
||||||
sceneHelper.setSelection(hoveredFaceIds);
|
sceneHelper.setSelection(hoveredFaceIds);
|
||||||
|
|
||||||
sceneHelper.showMouseFrustumHint();
|
sceneHelper.showMouseFrustum();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDispose(e: ThreeViewEventArgs): void {
|
function handleDispose(e: ThreeViewEventArgs): void {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const BARYCENTRIC_EPSILON = 1e-1;
|
||||||
function classifyTriangleHit(
|
function classifyTriangleHit(
|
||||||
point: THREE.Vector3,
|
point: THREE.Vector3,
|
||||||
tri: ExtendedTriangle,
|
tri: ExtendedTriangle,
|
||||||
vertexIds: [Id, Id, Id],
|
vertexIds: Id[],
|
||||||
): TriangleHitDetail {
|
): TriangleHitDetail {
|
||||||
// Compute barycentric coords via areas
|
// Compute barycentric coords via areas
|
||||||
const ab = tri.b.clone().sub(tri.a);
|
const ab = tri.b.clone().sub(tri.a);
|
||||||
|
|
@ -232,7 +232,7 @@ export class CircularFrustumIntersection {
|
||||||
intersectsBounds: (box: THREE.Box3) => intersectionResultToBvh(CircularFrustumIntersection.intersectsBox(box, localFrustum)),
|
intersectsBounds: (box: THREE.Box3) => intersectionResultToBvh(CircularFrustumIntersection.intersectsBox(box, localFrustum)),
|
||||||
|
|
||||||
intersectsTriangle: (tri: ExtendedTriangle, triIndex: number, contained: boolean) => {
|
intersectsTriangle: (tri: ExtendedTriangle, triIndex: number, contained: boolean) => {
|
||||||
const tiangleVertexIds = getGeometryVertextIds(triIndex) ?? ['','',''];
|
const tiangleVertexIds = getGeometryVertextIds(triIndex);
|
||||||
|
|
||||||
if (contained) {
|
if (contained) {
|
||||||
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
|
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
|
||||||
|
|
@ -242,7 +242,7 @@ export class CircularFrustumIntersection {
|
||||||
point: worldPoint,
|
point: worldPoint,
|
||||||
depth,
|
depth,
|
||||||
triangle: tri,
|
triangle: tri,
|
||||||
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds),
|
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds ?? []),
|
||||||
vertexIds: tiangleVertexIds,
|
vertexIds: tiangleVertexIds,
|
||||||
});
|
});
|
||||||
return !findAll;
|
return !findAll;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
import { useEffect, type RefObject } from "react";
|
import { useEffect, type RefObject } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { normalizeScreenPosition } from "../normalizeScreenPosition";
|
import { normalizeScreenPosition } from "../normalizeScreenPosition";
|
||||||
import { formatPoint } from "../stringFormat";
|
|
||||||
|
|
||||||
const CLICK_THRESHOLD = 2; // px
|
const CLICK_THRESHOLD = 2; // px
|
||||||
|
|
||||||
function clamp(v: number, min: number, max: number): number {
|
|
||||||
return Math.max(min, Math.min(max, v))
|
|
||||||
}
|
|
||||||
export type InteractionMouseEventArgs = {
|
export type InteractionMouseEventArgs = {
|
||||||
screenPosition: THREE.Vector2Like,
|
screenPosition: THREE.Vector2Like,
|
||||||
position: THREE.Vector2Like,
|
position: THREE.Vector2Like,
|
||||||
|
|
@ -41,22 +37,14 @@ export function useInteraction(
|
||||||
let isRightDrag = false;
|
let isRightDrag = false;
|
||||||
let startX = 0, startY = 0;
|
let startX = 0, startY = 0;
|
||||||
let lastX = 0, lastY = 0;
|
let lastX = 0, lastY = 0;
|
||||||
let azimuth = 0, elevation = 1, radius = 8;
|
let theta = 0.8, phi = 0.9, radius = 8;
|
||||||
let targetPoint = new THREE.Vector3();
|
let targetX = 0, targetY = 0;
|
||||||
|
|
||||||
let rotationSpeed = 0.005;
|
|
||||||
|
|
||||||
// prevent flip at poles
|
|
||||||
const epsilon = 0.0001;
|
|
||||||
|
|
||||||
function updateCamera() {
|
function updateCamera() {
|
||||||
const cosEl = Math.cos(elevation);
|
camera.position.x = targetX + radius * Math.sin(phi) * Math.sin(theta);
|
||||||
const x = targetPoint.x + radius * cosEl * Math.cos(azimuth);
|
camera.position.y = radius * Math.cos(phi);
|
||||||
const y = targetPoint.y + radius * cosEl * Math.sin(azimuth);
|
camera.position.z = targetY + radius * Math.sin(phi) * Math.cos(theta);
|
||||||
const z = targetPoint.z + radius * Math.sin(elevation);
|
camera.lookAt(targetX, 0, targetY);
|
||||||
camera.position.set(x, y, z)
|
|
||||||
camera.lookAt(targetPoint);
|
|
||||||
console.log(`${formatPoint(camera.position)} ${azimuth} ${elevation}`);
|
|
||||||
}
|
}
|
||||||
updateCamera();
|
updateCamera();
|
||||||
|
|
||||||
|
|
@ -89,12 +77,11 @@ export function useInteraction(
|
||||||
lastX = e.clientX;
|
lastX = e.clientX;
|
||||||
lastY = e.clientY;
|
lastY = e.clientY;
|
||||||
if (isRightDrag) {
|
if (isRightDrag) {
|
||||||
targetPoint.x -= dx * 0.01;
|
targetX -= dx * 0.01;
|
||||||
targetPoint.y += dy * 0.01;
|
targetY += dy * 0.01;
|
||||||
} else {
|
} else {
|
||||||
azimuth = azimuth - dx * rotationSpeed;
|
theta -= dx * 0.005;
|
||||||
elevation = elevation + dy * rotationSpeed;
|
phi = Math.max(0.15, Math.min(Math.PI - 0.15, phi - dy * 0.005));
|
||||||
elevation = Math.max(-Math.PI / 2 + epsilon, Math.min(Math.PI / 2 - epsilon, elevation + dy * rotationSpeed));
|
|
||||||
}
|
}
|
||||||
updateCamera();
|
updateCamera();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { ColorRepresentation, Object3D, Object3DEventMap, OrthographicCamer
|
||||||
import { SceneSync } from "../layers/sceneSync";
|
import { SceneSync } from "../layers/sceneSync";
|
||||||
import { GeometryCache } from "../layers/geometryCache";
|
import { GeometryCache } from "../layers/geometryCache";
|
||||||
import type { Id } from "../types";
|
import type { Id } from "../types";
|
||||||
import { CircularFrustumIntersection, type Intersection, type HitResults, type HitResult } from "./circularFrustumIntersect";
|
import { CircularFrustumIntersection, type HitResult, type HitResults } from "./circularFrustumIntersect";
|
||||||
import { CircularFrustum } from "./circularFrustum";
|
import { CircularFrustum } from "./circularFrustum";
|
||||||
import './bvh';
|
import './bvh';
|
||||||
import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper";
|
import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper";
|
||||||
|
|
@ -71,15 +71,15 @@ export class SceneHelper {
|
||||||
this.hints?.set(id, position, options);
|
this.hints?.set(id, position, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public showPointHint(id: string, position: Vector3Like, size: number = 0.05, color: ColorRepresentation = 0xffffff) {
|
public showPoint(id: string, position: Vector3Like, size: number = 0.05, color: ColorRepresentation = 0xffffff) {
|
||||||
this.hints?.set(id, position, { kind: "point", size, color });
|
this.hints?.set(id, position, { kind: "point", size, color });
|
||||||
}
|
}
|
||||||
|
|
||||||
public showCircleHint(id: string, position: Vector3Like, radius: number, thickness: number = 0.1, color: ColorRepresentation = 0xffffff) {
|
public showCircle(id: string, position: Vector3Like, radius: number, thickness: number = 0.1, color: ColorRepresentation = 0xffffff) {
|
||||||
this.hints?.set(id, position, { kind: "circle", radius, thickness, color });
|
this.hints?.set(id, position, { kind: "circle", radius, thickness, color });
|
||||||
}
|
}
|
||||||
|
|
||||||
public showMouseFrustumHint() {
|
public showMouseFrustum() {
|
||||||
if (!this.camera)
|
if (!this.camera)
|
||||||
throw new Error('Camera is not initialized');
|
throw new Error('Camera is not initialized');
|
||||||
|
|
||||||
|
|
@ -90,17 +90,17 @@ export class SceneHelper {
|
||||||
const near = frustum.getCircleAtDepth(nearDepth);
|
const near = frustum.getCircleAtDepth(nearDepth);
|
||||||
const far = frustum.getCircleAtDepth(farDepth);
|
const far = frustum.getCircleAtDepth(farDepth);
|
||||||
|
|
||||||
this.showCircleHint('hittest_near', near.center, near.radius, 0.05);
|
this.showCircle('hittest_near', near.center, near.radius, 0.05);
|
||||||
this.showCircleHint('hittest_far', far.center, far.radius, 0.1, 'red');
|
this.showCircle('hittest_far', far.center, far.radius, 0.1, 'red');
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearHints() {
|
public clear() {
|
||||||
this.hints?.dispose();
|
this.hints?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this.sync?.dispose();
|
this.sync?.dispose();
|
||||||
|
|
||||||
this.clearHints();
|
this.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import type { Vector3Like } from "three";
|
|
||||||
|
|
||||||
export function formatPoint(point: Vector3Like): string {
|
|
||||||
return [point.x, point.y, point.z].map((v) => v.toFixed(3)).join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -47,6 +47,9 @@ export class SceneSync {
|
||||||
// Called when FE scene graph syncs from BE
|
// Called when FE scene graph syncs from BE
|
||||||
addSolid(dto: MeshDto) {
|
addSolid(dto: MeshDto) {
|
||||||
const faceId = dto.faceId;
|
const faceId = dto.faceId;
|
||||||
|
const transform = new Float32Array([
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1
|
||||||
|
]);
|
||||||
|
|
||||||
if (this.meshByFace[faceId])
|
if (this.meshByFace[faceId])
|
||||||
return;
|
return;
|
||||||
|
|
@ -58,10 +61,23 @@ export class SceneSync {
|
||||||
mesh.userData.surfaceId = dto.surfaceId;
|
mesh.userData.surfaceId = dto.surfaceId;
|
||||||
mesh.userData.solidId = dto.solidId;
|
mesh.userData.solidId = dto.solidId;
|
||||||
|
|
||||||
|
// Apply transform (col-major mat4 from BE)
|
||||||
|
const m = new THREE.Matrix4();
|
||||||
|
m.fromArray(transform); // THREE expects col-major
|
||||||
|
mesh.applyMatrix4(m);
|
||||||
|
|
||||||
this.scene.add(mesh);
|
this.scene.add(mesh);
|
||||||
this.meshByFace[faceId] = mesh;
|
this.meshByFace[faceId] = mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTransform(faceId: Id, colMajorMat4: number[]) {
|
||||||
|
const mesh = this.meshByFace[faceId];
|
||||||
|
if (!mesh) return;
|
||||||
|
const m = new THREE.Matrix4().fromArray(colMajorMat4);
|
||||||
|
mesh.matrix.copy(m);
|
||||||
|
mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
|
||||||
|
}
|
||||||
|
|
||||||
setSelected(faceIds: Id[]) {
|
setSelected(faceIds: Id[]) {
|
||||||
this._selectedFaceIds = faceIds;
|
this._selectedFaceIds = faceIds;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue