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 } {
|
||||
THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
|
||||
|
||||
// --- Scene & Camera ---
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x0a0a12);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(55, size.w / size.h, 0.1, 100);
|
||||
camera.position.set(4, 3, 6);
|
||||
|
||||
// --- Lights ---
|
||||
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);
|
||||
scene.add(pt);
|
||||
|
||||
// const plane = new THREE.Mesh(
|
||||
// new THREE.PlaneGeometry(14, 14),
|
||||
// new THREE.MeshStandardMaterial({ color: 0x080810 })
|
||||
// );
|
||||
// plane.rotation.x = -Math.PI / 2;
|
||||
// plane.receiveShadow = true;
|
||||
// --- Floor & Grid ---
|
||||
const plane = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(14, 14),
|
||||
new THREE.MeshStandardMaterial({ color: 0x080810 })
|
||||
);
|
||||
plane.rotation.x = -Math.PI / 2;
|
||||
plane.receiveShadow = true;
|
||||
// scene.add(plane);
|
||||
|
||||
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);
|
||||
// scene.add(new THREE.GridHelper(14, 14, 0x222233, 0x111122));
|
||||
|
||||
return { scene, camera };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ export const Viewport = function () {
|
|||
function handleMouseMove(e: ThreeViewMouseEventArgs) {
|
||||
state.setHitTest(e.screenPosition, e.hitResults);
|
||||
|
||||
sceneHelper.clearHints();
|
||||
sceneHelper.clear();
|
||||
if (e.hitResults.hits.length) {
|
||||
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.hitTest.objects.map((o) => o));
|
||||
|
|
@ -26,7 +26,7 @@ export const Viewport = function () {
|
|||
|
||||
sceneHelper.setSelection(hoveredFaceIds);
|
||||
|
||||
sceneHelper.showMouseFrustumHint();
|
||||
sceneHelper.showMouseFrustum();
|
||||
}
|
||||
|
||||
function handleDispose(e: ThreeViewEventArgs): void {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const BARYCENTRIC_EPSILON = 1e-1;
|
|||
function classifyTriangleHit(
|
||||
point: THREE.Vector3,
|
||||
tri: ExtendedTriangle,
|
||||
vertexIds: [Id, Id, Id],
|
||||
vertexIds: Id[],
|
||||
): TriangleHitDetail {
|
||||
// Compute barycentric coords via areas
|
||||
const ab = tri.b.clone().sub(tri.a);
|
||||
|
|
@ -232,7 +232,7 @@ export class CircularFrustumIntersection {
|
|||
intersectsBounds: (box: THREE.Box3) => intersectionResultToBvh(CircularFrustumIntersection.intersectsBox(box, localFrustum)),
|
||||
|
||||
intersectsTriangle: (tri: ExtendedTriangle, triIndex: number, contained: boolean) => {
|
||||
const tiangleVertexIds = getGeometryVertextIds(triIndex) ?? ['','',''];
|
||||
const tiangleVertexIds = getGeometryVertextIds(triIndex);
|
||||
|
||||
if (contained) {
|
||||
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
|
||||
|
|
@ -242,7 +242,7 @@ export class CircularFrustumIntersection {
|
|||
point: worldPoint,
|
||||
depth,
|
||||
triangle: tri,
|
||||
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds),
|
||||
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds ?? []),
|
||||
vertexIds: tiangleVertexIds,
|
||||
});
|
||||
return !findAll;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
import { useEffect, type RefObject } from "react";
|
||||
import * as THREE from "three";
|
||||
import { normalizeScreenPosition } from "../normalizeScreenPosition";
|
||||
import { formatPoint } from "../stringFormat";
|
||||
|
||||
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 = {
|
||||
screenPosition: THREE.Vector2Like,
|
||||
position: THREE.Vector2Like,
|
||||
|
|
@ -41,22 +37,14 @@ export function useInteraction(
|
|||
let isRightDrag = false;
|
||||
let startX = 0, startY = 0;
|
||||
let lastX = 0, lastY = 0;
|
||||
let azimuth = 0, elevation = 1, radius = 8;
|
||||
let targetPoint = new THREE.Vector3();
|
||||
|
||||
let rotationSpeed = 0.005;
|
||||
|
||||
// prevent flip at poles
|
||||
const epsilon = 0.0001;
|
||||
let theta = 0.8, phi = 0.9, radius = 8;
|
||||
let targetX = 0, targetY = 0;
|
||||
|
||||
function updateCamera() {
|
||||
const cosEl = Math.cos(elevation);
|
||||
const x = targetPoint.x + radius * cosEl * Math.cos(azimuth);
|
||||
const y = targetPoint.y + radius * cosEl * Math.sin(azimuth);
|
||||
const z = targetPoint.z + radius * Math.sin(elevation);
|
||||
camera.position.set(x, y, z)
|
||||
camera.lookAt(targetPoint);
|
||||
console.log(`${formatPoint(camera.position)} ${azimuth} ${elevation}`);
|
||||
camera.position.x = targetX + radius * Math.sin(phi) * Math.sin(theta);
|
||||
camera.position.y = radius * Math.cos(phi);
|
||||
camera.position.z = targetY + radius * Math.sin(phi) * Math.cos(theta);
|
||||
camera.lookAt(targetX, 0, targetY);
|
||||
}
|
||||
updateCamera();
|
||||
|
||||
|
|
@ -89,12 +77,11 @@ export function useInteraction(
|
|||
lastX = e.clientX;
|
||||
lastY = e.clientY;
|
||||
if (isRightDrag) {
|
||||
targetPoint.x -= dx * 0.01;
|
||||
targetPoint.y += dy * 0.01;
|
||||
targetX -= dx * 0.01;
|
||||
targetY += dy * 0.01;
|
||||
} else {
|
||||
azimuth = azimuth - dx * rotationSpeed;
|
||||
elevation = elevation + dy * rotationSpeed;
|
||||
elevation = Math.max(-Math.PI / 2 + epsilon, Math.min(Math.PI / 2 - epsilon, elevation + dy * rotationSpeed));
|
||||
theta -= dx * 0.005;
|
||||
phi = Math.max(0.15, Math.min(Math.PI - 0.15, phi - dy * 0.005));
|
||||
}
|
||||
updateCamera();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { ColorRepresentation, Object3D, Object3DEventMap, OrthographicCamer
|
|||
import { SceneSync } from "../layers/sceneSync";
|
||||
import { GeometryCache } from "../layers/geometryCache";
|
||||
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 './bvh';
|
||||
import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper";
|
||||
|
|
@ -71,15 +71,15 @@ export class SceneHelper {
|
|||
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 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
public showMouseFrustumHint() {
|
||||
public showMouseFrustum() {
|
||||
if (!this.camera)
|
||||
throw new Error('Camera is not initialized');
|
||||
|
||||
|
|
@ -90,17 +90,17 @@ export class SceneHelper {
|
|||
const near = frustum.getCircleAtDepth(nearDepth);
|
||||
const far = frustum.getCircleAtDepth(farDepth);
|
||||
|
||||
this.showCircleHint('hittest_near', near.center, near.radius, 0.05);
|
||||
this.showCircleHint('hittest_far', far.center, far.radius, 0.1, 'red');
|
||||
this.showCircle('hittest_near', near.center, near.radius, 0.05);
|
||||
this.showCircle('hittest_far', far.center, far.radius, 0.1, 'red');
|
||||
}
|
||||
|
||||
public clearHints() {
|
||||
public clear() {
|
||||
this.hints?.dispose();
|
||||
}
|
||||
|
||||
public 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
|
||||
addSolid(dto: MeshDto) {
|
||||
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])
|
||||
return;
|
||||
|
|
@ -58,10 +61,23 @@ export class SceneSync {
|
|||
mesh.userData.surfaceId = dto.surfaceId;
|
||||
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.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[]) {
|
||||
this._selectedFaceIds = faceIds;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue