Compare commits

..

No commits in common. "df90158cb0989bbe980e354f4ba25906e92c4060" and "1f607e4a6c7df7f547cf52fa145d9f4593e4fbef" have entirely different histories.

7 changed files with 47 additions and 119 deletions

View File

@ -7,11 +7,11 @@
height: 600px; height: 600px;
} }
.hit-test-info { #hit-test {
position: absolute; position: absolute;
top: 0px; top: 600px;
left: 0px; left: 000px;
width: 600px;
font-size: 75%; font-size: 75%;
pointer-events: none; pointer-events: none;
color: white; color: white;
@ -20,6 +20,7 @@
#blob-view { #blob-view {
margin-top: 20px; margin-top: 20px;
padding: 10px; padding: 10px;
background: #eee;
font-family: monospace; font-family: monospace;
white-space: pre-wrap; white-space: pre-wrap;
@ -29,7 +30,7 @@
width: 400px; width: 400px;
text-align: left; text-align: left;
font-size: 75%; font-size: 10pt;
& .primitive { & .primitive {

View File

@ -9,7 +9,7 @@ export const App = function () {
<div> <div>
<Viewport /> <Viewport />
<div className="side-panel"> <div className="side-panel">
<HitTestView float/> <HitTestView />
<DbView /> <DbView />
</div> </div>
</div> </div>

View File

@ -1,29 +1,25 @@
import * as yaml from 'yaml'; import * as yaml from 'yaml';
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { state } from "../state/root"; import { state } from "../state/root";
import type { CSSProperties } from 'react';
import { formatPoint } from '../helpers/stringFormat';
export const HitTestView = observer(function ({ float }: { float: boolean }) { export const HitTestView = observer(function () {
const style: CSSProperties = {}; const left = state.mousePosition.x;
if (float) { const top = state.mousePosition.y;
style.left = state.mousePosition.x;
style.top = state.mousePosition.y;
style.width = 'auto';
style.height = 'auto';
}
return ( return (
<div className="hit-test-info" style={style}> <div id="hit-test" style={{ top, left }}>
<pre style={{ textWrap: 'wrap' }}> <pre style={{ textWrap: 'wrap' }}>
{
`${top},${left}`
}
{ {
state.hitResults.hits.map((hit) => state.hitResults.hits.map((hit) =>
<div key={hit.id}> <div key={hit.object.uuid}>
<div>{yaml.stringify( <div>{yaml.stringify(
{ {
hit: { ...hit, intersection: { ...hit.intersection, point: formatPoint(hit.intersection.point), object: undefined, triangle: undefined } }, hit: { ...hit, object: undefined, triangle: undefined },
// userData: hit.intersection.object.userData, userData: hit.object.userData,
}, },
undefined, undefined,
2, 2,

View File

@ -12,7 +12,7 @@ export const Viewport = function () {
sceneHelper.clearHints(); sceneHelper.clearHints();
if (e.hitResults.hits.length) { if (e.hitResults.hits.length) {
e.hitResults.hits.forEach((hit) => { e.hitResults.hits.forEach((hit) => {
sceneHelper.showPointHint(hit.intersection.object.uuid, hit.intersection.point); sceneHelper.showPointHint(hit.object.uuid, hit.intersection.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));
@ -20,7 +20,7 @@ export const Viewport = function () {
} }
// raycaster.setFromCamera(new THREE.Vector2(e.x, e.y), camera); // raycaster.setFromCamera(new THREE.Vector2(e.x, e.y), camera);
// const hits = raycaster.intersectObjects(sync.meshes); // const hits = raycaster.intersectObjects(sync.meshes);
const hoveredFaceIds = e.hitResults.hits.map((hit) => hit.intersection.object.userData.faceId); const hoveredFaceIds = e.hitResults.hits.map((hit) => hit.object.userData.faceId);
// if (hoveredFaceIds.length) // if (hoveredFaceIds.length)
// console.log(hoveredFaceIds); // console.log(hoveredFaceIds);

View File

@ -3,59 +3,33 @@ import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three
import { CircularFrustum } from './circularFrustum'; import { CircularFrustum } from './circularFrustum';
import type { Id } from '../types'; import type { Id } from '../types';
export type HitResults = {
hits: HitResult[];
}
export type TriangleVertexHitDetail = { export type TriangleVertexHitDetail = {
kind: 'vertex', kind: 'vertex',
index: 0 | 1 | 2, index: 0 | 1 | 2,
id?: Id, id: Id,
} }
export type TriangleEdgeHitDetail = { export type TriangleEdgeHitDetail = {
kind: 'edge', kind: 'edge',
index: 0 | 1 | 2, // edge 0=AB, 1=BC, 2=CA index: 0 | 1 | 2, // edge 0=AB, 1=BC, 2=CA
id?: Id, id: Id,
} }
export type TriangleFaceHitDetail = { export type TriangleFaceHitDetail = {
kind: 'face', kind: 'face',
id?: Id,
} }
export type TriangleHitDetail = TriangleVertexHitDetail | TriangleEdgeHitDetail | TriangleFaceHitDetail; export type TriangleHitDetail = TriangleVertexHitDetail | TriangleEdgeHitDetail | TriangleFaceHitDetail;
export type Visibility = 'visible' | 'backface'; // | 'occluded' export type HitResult = {
object: THREE.Object3D;
export type Intersection = { point: THREE.Vector3; // world-space closest hit point
object: THREE.Object3D, depth: number; // depth along frustum axis
point: THREE.Vector3, // world-space closest hit point triangle: { a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3 };
depth: number, // depth along frustum axis triHit?: TriangleHitDetail;
triangle: { a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3 }, vertexIds: [Id, Id, Id] | undefined; // undefined is when geometry does not have .index
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 = { export type CircularFrustumIntersectionOptions = {
@ -81,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, Id, 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);
@ -104,9 +78,9 @@ function classifyTriangleHit(
const onB = v > eps; const onB = v > eps;
const onC = w > eps; const onC = w > eps;
if (onA) return { kind: 'vertex', index: 0, id: vertexIds?.[0] }; if (onA) return { kind: 'vertex', index: 0, id: vertexIds[0] };
if (onB) return { kind: 'vertex', index: 1, id: vertexIds?.[1] }; if (onB) return { kind: 'vertex', index: 1, id: vertexIds[1] };
if (onC) return { kind: 'vertex', index: 2, id: vertexIds?.[2] }; if (onC) return { kind: 'vertex', index: 2, id: vertexIds[2] };
const onAB = w < BARYCENTRIC_EPSILON; // u+v≈1, w≈0 const onAB = w < BARYCENTRIC_EPSILON; // u+v≈1, w≈0
const onBC = u < BARYCENTRIC_EPSILON; const onBC = u < BARYCENTRIC_EPSILON;
@ -206,7 +180,7 @@ export class CircularFrustumIntersection {
public intersectMesh( public intersectMesh(
mesh: THREE.Mesh, mesh: THREE.Mesh,
findAll: boolean, findAll: boolean,
): Intersection[] { ): HitResult[] {
const geometry = mesh.geometry; const geometry = mesh.geometry;
if (!geometry) if (!geometry)
return []; return [];
@ -227,14 +201,14 @@ export class CircularFrustumIntersection {
return mesh.userData.vertexIds[vertexIndex]; return mesh.userData.vertexIds[vertexIndex];
} }
function getGeometryVertextIds(triIndex: number): [Id, Id, Id] { function getGeometryVertextIds(triIndex: number): HitResult['vertexIds'] {
return geometry.index return geometry.index
? [ ? [
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3]), getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3]),
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 1]), getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 1]),
getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 2]), getGeometryVertextIdByIndex(geometry.index.array[triIndex * 3 + 2]),
] ]
: ['', '', '']; : undefined;
} }
const axisRay = new THREE.Ray(localFrustum.apex, localFrustum.axisNormalized); const axisRay = new THREE.Ray(localFrustum.apex, localFrustum.axisNormalized);
@ -247,7 +221,7 @@ export class CircularFrustumIntersection {
} }
}; };
const results: Intersection[] = []; const results: HitResult[] = [];
if (!geometry.boundsTree) if (!geometry.boundsTree)
geometry.computeBoundsTree(); geometry.computeBoundsTree();
@ -258,13 +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) ?? ['','',''];
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) { if (contained) {
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld); const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
@ -275,8 +243,7 @@ export class CircularFrustumIntersection {
depth, depth,
triangle: tri, triangle: tri,
triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds), triHit: classifyTriangleHit(tri.a, tri, tiangleVertexIds),
visibility, vertexIds: tiangleVertexIds,
// vertexIds: tiangleVertexIds,
}); });
return !findAll; return !findAll;
} }
@ -343,7 +310,7 @@ export class CircularFrustumIntersection {
depth: worldDepth, depth: worldDepth,
triangle: { a, b, c }, triangle: { a, b, c },
triHit: classifyTriangleHit(bestPoint.local, tri, tiangleVertexIds), triHit: classifyTriangleHit(bestPoint.local, tri, tiangleVertexIds),
visibility, vertexIds: tiangleVertexIds,
}); });
return !findAll; return !findAll;
} }
@ -367,41 +334,11 @@ export class CircularFrustumIntersection {
if (!(object instanceof THREE.Mesh)) if (!(object instanceof THREE.Mesh))
return; return;
results.push( results.push(...this.intersectMesh(object, !!options.findAll));
...this.intersectMesh(object, !!options.findAll)
.flatMap((i) => this.intersectionToHitResult(i)),
);
}); });
// sort closest first // sort closest first
results.sort((a, b) => a.intersection.depth - b.intersection.depth); results.sort((a, b) => a.depth - b.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; return results;
} }
} }

View File

@ -1,8 +1,3 @@
:root {
background: #101020;
color: white;
}
/* :root { /* :root {
--text: #6b6375; --text: #6b6375;
--text-h: #08060d; --text-h: #08060d;

View File

@ -38,8 +38,7 @@ export class SceneSync {
const meshes = Geometry const meshes = Geometry
.tessellateSolid(id) .tessellateSolid(id)
.map(meshToDto); .map(meshToDto);
this.addSolid(meshes[0]); // bottom this.addSolid(meshes[2]);
this.addSolid(meshes[3]); // front
// for (const mesh of meshes) // for (const mesh of meshes)
// this.addSolid(mesh); // this.addSolid(mesh);
} }