diff --git a/client/src/App.scss b/client/src/App.scss index b033e62..713e48a 100644 --- a/client/src/App.scss +++ b/client/src/App.scss @@ -7,11 +7,11 @@ height: 600px; } -#hit-test { +.hit-test-info { position: absolute; - top: 600px; - left: 000px; - width: 600px; + top: 0px; + left: 0px; + font-size: 75%; pointer-events: none; color: white; diff --git a/client/src/App.tsx b/client/src/App.tsx index 6018f83..e66e7be 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -9,7 +9,7 @@ export const App = function () {
- {
- `${top},${left}`
- }
{
state.hitResults.hits.map((hit) =>
-
+
{yaml.stringify(
{
- hit: { ...hit, object: undefined, triangle: undefined },
- userData: hit.object.userData,
+ hit: { ...hit, intersection: { ...hit.intersection, point: formatPoint(hit.intersection.point), object: undefined, triangle: undefined } },
+ // userData: hit.intersection.object.userData,
},
undefined,
2,
diff --git a/client/src/components/Viewport.tsx b/client/src/components/Viewport.tsx
index ad655d5..8acded9 100644
--- a/client/src/components/Viewport.tsx
+++ b/client/src/components/Viewport.tsx
@@ -12,15 +12,15 @@ export const Viewport = function () {
sceneHelper.clearHints();
if (e.hitResults.hits.length) {
e.hitResults.hits.forEach((hit) => {
- sceneHelper.showPointHint(hit.object.uuid, hit.intersection.point);
- })
+ sceneHelper.showPointHint(hit.intersection.object.uuid, hit.intersection.point);
+ })
// console.log(e.position);
// console.log(e.hitTest.objects.map((o) => o));
// console.log(e.hitTest.objects.flatMap((o) => o.point.toArray()));
}
// raycaster.setFromCamera(new THREE.Vector2(e.x, e.y), camera);
// const hits = raycaster.intersectObjects(sync.meshes);
- const hoveredFaceIds = e.hitResults.hits.map((hit) => hit.object.userData.faceId);
+ const hoveredFaceIds = e.hitResults.hits.map((hit) => hit.intersection.object.userData.faceId);
// if (hoveredFaceIds.length)
// console.log(hoveredFaceIds);
diff --git a/client/src/helpers/circularFrustumIntersect.ts b/client/src/helpers/circularFrustumIntersect.ts
index 3b13341..b1fbbd8 100644
--- a/client/src/helpers/circularFrustumIntersect.ts
+++ b/client/src/helpers/circularFrustumIntersect.ts
@@ -3,33 +3,59 @@ import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three
import { CircularFrustum } from './circularFrustum';
import type { Id } from '../types';
-export type HitResults = {
- hits: HitResult[];
-}
-
export type TriangleVertexHitDetail = {
kind: 'vertex',
index: 0 | 1 | 2,
- id: Id,
+ id?: Id,
}
export type TriangleEdgeHitDetail = {
kind: 'edge',
index: 0 | 1 | 2, // edge 0=AB, 1=BC, 2=CA
- id: Id,
+ id?: Id,
}
export type TriangleFaceHitDetail = {
kind: 'face',
+ id?: Id,
}
export type TriangleHitDetail = TriangleVertexHitDetail | TriangleEdgeHitDetail | TriangleFaceHitDetail;
-export type HitResult = {
- object: THREE.Object3D;
- point: THREE.Vector3; // world-space closest hit point
- depth: number; // depth along frustum axis
- triangle: { a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3 };
- triHit?: TriangleHitDetail;
- vertexIds: [Id, Id, Id] | undefined; // undefined is when geometry does not have .index
+export type Visibility = 'visible' | 'backface'; // | 'occluded'
+
+export type Intersection = {
+ object: THREE.Object3D,
+ point: THREE.Vector3, // world-space closest hit point
+ depth: number, // depth along frustum axis
+ triangle: { a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3 },
+ triHit?: TriangleHitDetail,
+ visibility: Visibility,
+}
+
+export type BaseHitResult = {
+ intersection: Intersection,
+}
+
+export type FaceHitResult = BaseHitResult & {
+ kind: 'face',
+ id?: Id,
+}
+
+export type EdgeHitResult = BaseHitResult & {
+ kind: 'edge',
+ id?: Id,
+ faceId: Id,
+}
+
+export type VertexHitResult = BaseHitResult & {
+ kind: 'vertex',
+ id?: Id,
+ faceId: Id,
+}
+
+export type HitResult = FaceHitResult | EdgeHitResult | VertexHitResult;
+
+export type HitResults = {
+ hits: HitResult[];
}
export type CircularFrustumIntersectionOptions = {
@@ -55,7 +81,7 @@ const BARYCENTRIC_EPSILON = 1e-1;
function classifyTriangleHit(
point: THREE.Vector3,
tri: ExtendedTriangle,
- vertexIds: [Id, Id, Id],
+ vertexIds?: [Id, Id, Id],
): TriangleHitDetail {
// Compute barycentric coords via areas
const ab = tri.b.clone().sub(tri.a);
@@ -78,9 +104,9 @@ function classifyTriangleHit(
const onB = v > eps;
const onC = w > eps;
- if (onA) return { kind: 'vertex', index: 0, id: vertexIds[0] };
- if (onB) return { kind: 'vertex', index: 1, id: vertexIds[1] };
- if (onC) return { kind: 'vertex', index: 2, id: vertexIds[2] };
+ if (onA) return { kind: 'vertex', index: 0, id: vertexIds?.[0] };
+ if (onB) return { kind: 'vertex', index: 1, id: vertexIds?.[1] };
+ if (onC) return { kind: 'vertex', index: 2, id: vertexIds?.[2] };
const onAB = w < BARYCENTRIC_EPSILON; // u+v≈1, w≈0
const onBC = u < BARYCENTRIC_EPSILON;
@@ -180,7 +206,7 @@ export class CircularFrustumIntersection {
public intersectMesh(
mesh: THREE.Mesh,
findAll: boolean,
- ): HitResult[] {
+ ): Intersection[] {
const geometry = mesh.geometry;
if (!geometry)
return [];
@@ -201,14 +227,14 @@ export class CircularFrustumIntersection {
return mesh.userData.vertexIds[vertexIndex];
}
- function getGeometryVertextIds(triIndex: number): HitResult['vertexIds'] {
+ function getGeometryVertextIds(triIndex: number): [Id, Id, Id] {
return geometry.index
? [
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3]),
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 1]),
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 2]),
]
- : undefined;
+ : ['', '', ''];
}
const axisRay = new THREE.Ray(localFrustum.apex, localFrustum.axisNormalized);
@@ -221,7 +247,7 @@ export class CircularFrustumIntersection {
}
};
- const results: HitResult[] = [];
+ const results: Intersection[] = [];
if (!geometry.boundsTree)
geometry.computeBoundsTree();
@@ -232,7 +258,13 @@ 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);
+
+ const normal = new THREE.Vector3();
+ tri.getNormal(normal);
+ const facingRatio = normal.dot(localFrustum.axisNormalized);
+ // normal orientation is same as frusum axis means triangle is faced way from camera
+ const visibility: Visibility = facingRatio >= 0 ? 'backface' : 'visible';
if (contained) {
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
@@ -243,7 +275,8 @@ export class CircularFrustumIntersection {
depth,
triangle: tri,
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds),
- vertexIds: tiangleVertexIds,
+ visibility,
+ // vertexIds: tiangleVertexIds,
});
return !findAll;
}
@@ -310,7 +343,7 @@ export class CircularFrustumIntersection {
depth: worldDepth,
triangle: { a, b, c },
triHit: classifyTriangleHit(bestPoint.local, tri, tiangleVertexIds),
- vertexIds: tiangleVertexIds,
+ visibility,
});
return !findAll;
}
@@ -334,11 +367,41 @@ export class CircularFrustumIntersection {
if (!(object instanceof THREE.Mesh))
return;
- results.push(...this.intersectMesh(object, !!options.findAll));
+ results.push(
+ ...this.intersectMesh(object, !!options.findAll)
+ .flatMap((i) => this.intersectionToHitResult(i)),
+ );
});
// sort closest first
- results.sort((a, b) => a.depth - b.depth);
+ results.sort((a, b) => a.intersection.depth - b.intersection.depth);
+ return results;
+ }
+
+ private intersectionToHitResult(intersection: Intersection): HitResult[] {
+ const faceId = intersection.object.userData.faceId;
+
+ const results: HitResult[] = [{
+ kind: 'face',
+ intersection,
+ id: faceId,
+ }];
+ if (intersection.triHit?.kind === 'edge') {
+ results.unshift({
+ kind: 'edge',
+ intersection,
+ id: intersection.triHit?.id,
+ faceId,
+ });
+ }
+ if (intersection.triHit?.kind === 'vertex') {
+ results.unshift({
+ kind: 'vertex',
+ intersection,
+ id: intersection.triHit?.id,
+ faceId,
+ });
+ }
return results;
}
}
\ No newline at end of file
diff --git a/client/src/layers/sceneSync.ts b/client/src/layers/sceneSync.ts
index 93a86c7..9b38830 100644
--- a/client/src/layers/sceneSync.ts
+++ b/client/src/layers/sceneSync.ts
@@ -38,7 +38,8 @@ export class SceneSync {
const meshes = Geometry
.tessellateSolid(id)
.map(meshToDto);
- this.addSolid(meshes[2]);
+ this.addSolid(meshes[0]); // bottom
+ this.addSolid(meshes[3]); // front
// for (const mesh of meshes)
// this.addSolid(mesh);
}