hit test edgeId detection
This commit is contained in:
parent
e8c635c139
commit
0354391f96
|
|
@ -1,14 +1,15 @@
|
||||||
import type { Face, Mesh, Solid, Surface, Vertex } from "../../types";
|
import type { Edge, Face, HalfEdge, Mesh, Solid, Surface, Vertex } from "../../types";
|
||||||
|
|
||||||
export type MeshDto = {
|
export type MeshDto = {
|
||||||
vertices: Float32Array;
|
vertices: Float32Array;
|
||||||
normals: Float32Array
|
normals: Float32Array
|
||||||
indices: Uint16Array;
|
indices: Uint16Array;
|
||||||
|
|
||||||
vertexIds: Vertex['id'][];
|
// vertexIds: Vertex['id'][];
|
||||||
faceId: Face['id'];
|
faceId: Face['id'];
|
||||||
surfaceId: Surface['id'];
|
surfaceId: Surface['id'];
|
||||||
solidId: Solid['id'];
|
solidId: Solid['id'];
|
||||||
|
loop: { edge: Edge['id'], halfEdge: HalfEdge['id'], vertex: Vertex['id'], vertex2: Vertex['id'] }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function meshToDto(mesh: Mesh): MeshDto {
|
export function meshToDto(mesh: Mesh): MeshDto {
|
||||||
|
|
@ -16,9 +17,10 @@ export function meshToDto(mesh: Mesh): MeshDto {
|
||||||
vertices: new Float32Array(mesh.vertices.flat()),
|
vertices: new Float32Array(mesh.vertices.flat()),
|
||||||
normals: new Float32Array(mesh.normals.flat()),
|
normals: new Float32Array(mesh.normals.flat()),
|
||||||
indices: new Uint16Array(mesh.indices.flat()),
|
indices: new Uint16Array(mesh.indices.flat()),
|
||||||
vertexIds: mesh.vertexIds,
|
// vertexIds: mesh.vertexIds,
|
||||||
faceId: mesh.faceId,
|
faceId: mesh.faceId,
|
||||||
surfaceId: mesh.surfaceId,
|
surfaceId: mesh.surfaceId,
|
||||||
solidId: mesh.solidId,
|
solidId: mesh.solidId,
|
||||||
|
loop: mesh.loop,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import earcut from 'earcut';
|
import earcut from 'earcut';
|
||||||
|
|
||||||
import type { Face, Mesh, V2, V3, Vertex } from "../../types";
|
import type { Edge, Face, HalfEdge, Mesh, V2, V3, Vertex } from "../../types";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
|
|
||||||
export type Basis3D = { origin: V3; u: V3; v: V3; normal: V3; };
|
export type Basis3D = { origin: V3; u: V3; v: V3; normal: V3; };
|
||||||
|
|
@ -48,7 +48,10 @@ function getNormal(vertices: V3[]): V3 {
|
||||||
export class PlaneTessellator {
|
export class PlaneTessellator {
|
||||||
public static tessellate(face: Face): Omit<Mesh, 'faceId' | 'surfaceId' | 'solidId'>[] {
|
public static tessellate(face: Face): Omit<Mesh, 'faceId' | 'surfaceId' | 'solidId'>[] {
|
||||||
|
|
||||||
const vertices3d = PlaneTessellator.getVerticesByLoop(face.outerLoop);
|
const loop = PlaneTessellator.getLoop(face.outerLoop);
|
||||||
|
const loopWithV2s = loop.map((v, idx) => ({ ...v, vertex2: loop[(idx + 1) % loop.length].vertex }));
|
||||||
|
|
||||||
|
const vertices3d = loop.map((v) => v.vertex);
|
||||||
|
|
||||||
const basis = PlaneTessellator.getBasis(vertices3d);
|
const basis = PlaneTessellator.getBasis(vertices3d);
|
||||||
const vertices2d = PlaneTessellator.projectVertices(vertices3d, basis);
|
const vertices2d = PlaneTessellator.projectVertices(vertices3d, basis);
|
||||||
|
|
@ -60,11 +63,12 @@ export class PlaneTessellator {
|
||||||
vertices: vertices3d.map((v) => [v.x, v.y, v.z]),
|
vertices: vertices3d.map((v) => [v.x, v.y, v.z]),
|
||||||
normals: vertices3d.map(() => basis.normal),
|
normals: vertices3d.map(() => basis.normal),
|
||||||
indices,
|
indices,
|
||||||
vertexIds: vertices3d.map((v) => v.id),
|
// vertexIds: vertices3d.map((v) => v.id),
|
||||||
|
loop: loopWithV2s.map((v) => ({ halfEdge: v.halfEdge.id, edge: v.edge.id, vertex: v.vertex.id, vertex2: v.vertex2.id })),
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getVerticesByLoop(loopId: string): Vertex[] {
|
private static getLoop(loopId: string): { halfEdge: HalfEdge, edge: Edge, vertex: Vertex }[] {
|
||||||
const halfEdges = db.halfEdgesByLoop(loopId)
|
const halfEdges = db.halfEdgesByLoop(loopId)
|
||||||
.map((he) => ({
|
.map((he) => ({
|
||||||
halfedge: he,
|
halfedge: he,
|
||||||
|
|
@ -74,7 +78,11 @@ export class PlaneTessellator {
|
||||||
throw new Error(`Loop ${loopId}: only linear half-edges are supported for plane tesselation`);
|
throw new Error(`Loop ${loopId}: only linear half-edges are supported for plane tesselation`);
|
||||||
|
|
||||||
return halfEdges
|
return halfEdges
|
||||||
.map((he) => db.vertexById(he.halfedge.origin)!);
|
.map((halfEdge, idx) => ({
|
||||||
|
halfEdge: halfEdge.halfedge,
|
||||||
|
edge: halfEdge.edge,
|
||||||
|
vertex: db.vertexById(halfEdge.halfedge.origin)!,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static projectVertices(vertices: Vertex[], basis: Basis3D): V2[] {
|
public static projectVertices(vertices: Vertex[], basis: Basis3D): V2[] {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as THREE from 'three';
|
||||||
import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh';
|
import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh';
|
||||||
import { CircularFrustum } from './circularFrustum';
|
import { CircularFrustum } from './circularFrustum';
|
||||||
import type { Id } from '../types';
|
import type { Id } from '../types';
|
||||||
|
import type { MeshDto } from '../backend/dto';
|
||||||
|
|
||||||
export type TriangleVertexHitDetail = {
|
export type TriangleVertexHitDetail = {
|
||||||
kind: 'vertex',
|
kind: 'vertex',
|
||||||
|
|
@ -11,7 +12,8 @@ export type TriangleVertexHitDetail = {
|
||||||
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,
|
aId?: Id,
|
||||||
|
bId?: Id,
|
||||||
}
|
}
|
||||||
export type TriangleFaceHitDetail = {
|
export type TriangleFaceHitDetail = {
|
||||||
kind: 'face',
|
kind: 'face',
|
||||||
|
|
@ -113,9 +115,9 @@ function classifyTriangleHit(
|
||||||
const onBC = u < BARYCENTRIC_EPSILON;
|
const onBC = u < BARYCENTRIC_EPSILON;
|
||||||
const onCA = v < BARYCENTRIC_EPSILON;
|
const onCA = v < BARYCENTRIC_EPSILON;
|
||||||
|
|
||||||
if (onAB) return { kind: 'edge', index: 0, id: `${vertexIds?.[0]}-${vertexIds?.[1]}` };
|
if (onAB) return { kind: 'edge', index: 0, aId: vertexIds?.[0], bId: vertexIds?.[1] };
|
||||||
if (onBC) return { kind: 'edge', index: 1, id: `${vertexIds?.[1]}-${vertexIds?.[2]}` };
|
if (onBC) return { kind: 'edge', index: 1, aId: vertexIds?.[1], bId: vertexIds?.[2] };
|
||||||
if (onCA) return { kind: 'edge', index: 2, id: `${vertexIds?.[2]}-${vertexIds?.[0]}` };
|
if (onCA) return { kind: 'edge', index: 2, aId: vertexIds?.[2], bId: vertexIds?.[0] };
|
||||||
|
|
||||||
return { kind: 'face' };
|
return { kind: 'face' };
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +382,9 @@ export class CircularFrustumIntersection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private intersectionToHitResult(intersection: Intersection): HitResult[] {
|
private intersectionToHitResult(intersection: Intersection): HitResult[] {
|
||||||
const faceId = intersection.object.userData.faceId;
|
const userData = intersection.object.userData as MeshDto;
|
||||||
|
const faceId = userData.faceId;
|
||||||
|
const loop = userData.loop;
|
||||||
|
|
||||||
const results: HitResult[] = [{
|
const results: HitResult[] = [{
|
||||||
kind: 'face',
|
kind: 'face',
|
||||||
|
|
@ -392,15 +396,18 @@ export class CircularFrustumIntersection {
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
if (intersection.triHit?.kind === 'edge') {
|
if (intersection.triHit?.kind === 'edge') {
|
||||||
results.unshift({
|
const triHit = intersection.triHit;
|
||||||
kind: 'edge',
|
const edge = loop.find((v) => (v.vertex === triHit.aId) && (v.vertex2 === triHit.bId))?.edge;
|
||||||
id: intersection.triHit?.id,
|
if (edge !== undefined)
|
||||||
faceId,
|
results.unshift({
|
||||||
intersection: {
|
kind: 'edge',
|
||||||
...intersection,
|
id: edge,
|
||||||
triHit: undefined,
|
faceId,
|
||||||
},
|
intersection: {
|
||||||
});
|
...intersection,
|
||||||
|
triHit: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (intersection.triHit?.kind === 'vertex') {
|
if (intersection.triHit?.kind === 'vertex') {
|
||||||
results.unshift({
|
results.unshift({
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export class SceneSync {
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
depthWrite: false,
|
depthWrite: false,
|
||||||
|
wireframe: true,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
frontColor: { value: new THREE.Color(0x88ccff) },
|
frontColor: { value: new THREE.Color(0x88ccff) },
|
||||||
frontOpacity: { value: 0.6 },
|
frontOpacity: { value: 0.6 },
|
||||||
|
|
@ -90,10 +91,8 @@ export class SceneSync {
|
||||||
const geoVCount = geo.attributes.position.count;
|
const geoVCount = geo.attributes.position.count;
|
||||||
geo.setAttribute('selected', new THREE.BufferAttribute(new Float32Array(geoVCount), 1));
|
geo.setAttribute('selected', new THREE.BufferAttribute(new Float32Array(geoVCount), 1));
|
||||||
const mesh = new THREE.Mesh(geo, this.baseMaterial);
|
const mesh = new THREE.Mesh(geo, this.baseMaterial);
|
||||||
mesh.userData.faceId = faceId;
|
mesh.userData = dto
|
||||||
mesh.userData.vertexIds = dto.vertexIds;
|
mesh.userData.vertexIds = dto.loop.map((v) => v.vertex);
|
||||||
mesh.userData.surfaceId = dto.surfaceId;
|
|
||||||
mesh.userData.solidId = dto.solidId;
|
|
||||||
|
|
||||||
this.scene.add(mesh);
|
this.scene.add(mesh);
|
||||||
this.meshByFace[faceId] = mesh;
|
this.meshByFace[faceId] = mesh;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,14 @@ export class Model {
|
||||||
return this.halfEdges[id];
|
return this.halfEdges[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public halfEdgesByFilter(filter: (halfEdge: HalfEdge) => boolean): HalfEdge[] {
|
||||||
|
return Object.values(this.halfEdges).filter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public halfEdgesByVertexId(id: Vertex['id']): HalfEdge[] {
|
||||||
|
return this.halfEdgesByFilter((he) => he.origin === id);
|
||||||
|
}
|
||||||
|
|
||||||
public halfEdgesByLoop(loopId: string): HalfEdge[] {
|
public halfEdgesByLoop(loopId: string): HalfEdge[] {
|
||||||
const loop = this.loopById(loopId)!;
|
const loop = this.loopById(loopId)!;
|
||||||
const startHalfEdgeId = loop.start;
|
const startHalfEdgeId = loop.start;
|
||||||
|
|
@ -76,6 +84,10 @@ export class Model {
|
||||||
return this.edges[id];
|
return this.edges[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public edgesByFilter(filter: (edge: Edge) => boolean): Edge[] {
|
||||||
|
return Object.values(this.edges).filter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
public loopById(id: Loop['id']): Loop | undefined {
|
public loopById(id: Loop['id']): Loop | undefined {
|
||||||
return this.loops[id];
|
return this.loops[id];
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +103,19 @@ export class Model {
|
||||||
public solidById(id: Solid['id']): Solid | undefined {
|
public solidById(id: Solid['id']): Solid | undefined {
|
||||||
return this.solids[id];
|
return this.solids[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public edgesByVertexIds(a: Vertex['id'], b: Vertex['id'], belongsToFaceId?: Face['id']): Edge[] {
|
||||||
|
let hesA = this.halfEdgesByVertexId(a);
|
||||||
|
let hesB = this.halfEdgesByVertexId(b);
|
||||||
|
|
||||||
|
const edges = this.edgesByFilter((e) => (
|
||||||
|
(e.a ? hesA.some((he) => he.id === a) : true) && (e.b ? hesB.some((he) => he.id === b) : true)
|
||||||
|
||
|
||||||
|
(e.a ? hesB.some((he) => he.id === a) : true) && (e.b ? hesA.some((he) => he.id === b) : true)
|
||||||
|
));
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const model = new Model();
|
export const model = new Model();
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import type { Face, Solid, Surface, Vertex } from "./brep";
|
import type { Edge, Face, HalfEdge, Solid, Surface, Vertex } from "./brep";
|
||||||
|
|
||||||
export type V2 = [x: number, y: number];
|
export type V2 = [x: number, y: number];
|
||||||
export type V3 = [x: number, y: number, z: number];
|
export type V3 = [x: number, y: number, z: number];
|
||||||
// export type V3 = [number, number, number];
|
|
||||||
|
|
||||||
export type Mesh = {
|
export type Mesh = {
|
||||||
vertices: V3[];
|
vertices: V3[];
|
||||||
normals: V3[];
|
normals: V3[];
|
||||||
indices: number[];
|
indices: number[];
|
||||||
|
|
||||||
vertexIds: Vertex['id'][];
|
|
||||||
faceId: Face['id'];
|
faceId: Face['id'];
|
||||||
surfaceId: Surface['id'];
|
surfaceId: Surface['id'];
|
||||||
solidId: Solid['id'];
|
solidId: Solid['id'];
|
||||||
|
loop: { edge: Edge['id'], halfEdge: HalfEdge['id'], vertex: Vertex['id'] }[];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue