diff --git a/client/src/backend/dto/mesh.ts b/client/src/backend/dto/mesh.ts index 3407704..1e3e6c3 100644 --- a/client/src/backend/dto/mesh.ts +++ b/client/src/backend/dto/mesh.ts @@ -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 = { vertices: Float32Array; normals: Float32Array indices: Uint16Array; - vertexIds: Vertex['id'][]; + // vertexIds: Vertex['id'][]; faceId: Face['id']; surfaceId: Surface['id']; solidId: Solid['id']; + loop: { edge: Edge['id'], halfEdge: HalfEdge['id'], vertex: Vertex['id'], vertex2: Vertex['id'] }[]; }; export function meshToDto(mesh: Mesh): MeshDto { @@ -16,9 +17,10 @@ export function meshToDto(mesh: Mesh): MeshDto { vertices: new Float32Array(mesh.vertices.flat()), normals: new Float32Array(mesh.normals.flat()), indices: new Uint16Array(mesh.indices.flat()), - vertexIds: mesh.vertexIds, + // vertexIds: mesh.vertexIds, faceId: mesh.faceId, surfaceId: mesh.surfaceId, solidId: mesh.solidId, + loop: mesh.loop, }; } \ No newline at end of file diff --git a/client/src/backend/geometry/planeTesselator.ts b/client/src/backend/geometry/planeTesselator.ts index e92f531..9615aed 100644 --- a/client/src/backend/geometry/planeTesselator.ts +++ b/client/src/backend/geometry/planeTesselator.ts @@ -1,6 +1,6 @@ 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"; export type Basis3D = { origin: V3; u: V3; v: V3; normal: V3; }; @@ -48,7 +48,10 @@ function getNormal(vertices: V3[]): V3 { export class PlaneTessellator { public static tessellate(face: Face): Omit[] { - 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 vertices2d = PlaneTessellator.projectVertices(vertices3d, basis); @@ -60,11 +63,12 @@ export class PlaneTessellator { vertices: vertices3d.map((v) => [v.x, v.y, v.z]), normals: vertices3d.map(() => basis.normal), 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) .map((he) => ({ halfedge: he, @@ -74,7 +78,11 @@ export class PlaneTessellator { throw new Error(`Loop ${loopId}: only linear half-edges are supported for plane tesselation`); 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[] { diff --git a/client/src/helpers/circularFrustumIntersect.ts b/client/src/helpers/circularFrustumIntersect.ts index cfdff46..c469618 100644 --- a/client/src/helpers/circularFrustumIntersect.ts +++ b/client/src/helpers/circularFrustumIntersect.ts @@ -2,6 +2,7 @@ import * as THREE from 'three'; import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh'; import { CircularFrustum } from './circularFrustum'; import type { Id } from '../types'; +import type { MeshDto } from '../backend/dto'; export type TriangleVertexHitDetail = { kind: 'vertex', @@ -11,7 +12,8 @@ export type TriangleVertexHitDetail = { export type TriangleEdgeHitDetail = { kind: 'edge', index: 0 | 1 | 2, // edge 0=AB, 1=BC, 2=CA - id?: Id, + aId?: Id, + bId?: Id, } export type TriangleFaceHitDetail = { kind: 'face', @@ -113,9 +115,9 @@ function classifyTriangleHit( const onBC = u < BARYCENTRIC_EPSILON; const onCA = v < BARYCENTRIC_EPSILON; - if (onAB) return { kind: 'edge', index: 0, id: `${vertexIds?.[0]}-${vertexIds?.[1]}` }; - if (onBC) return { kind: 'edge', index: 1, id: `${vertexIds?.[1]}-${vertexIds?.[2]}` }; - if (onCA) return { kind: 'edge', index: 2, id: `${vertexIds?.[2]}-${vertexIds?.[0]}` }; + if (onAB) return { kind: 'edge', index: 0, aId: vertexIds?.[0], bId: vertexIds?.[1] }; + if (onBC) return { kind: 'edge', index: 1, aId: vertexIds?.[1], bId: vertexIds?.[2] }; + if (onCA) return { kind: 'edge', index: 2, aId: vertexIds?.[2], bId: vertexIds?.[0] }; return { kind: 'face' }; } @@ -380,7 +382,9 @@ export class CircularFrustumIntersection { } 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[] = [{ kind: 'face', @@ -392,15 +396,18 @@ export class CircularFrustumIntersection { }, }]; if (intersection.triHit?.kind === 'edge') { - results.unshift({ - kind: 'edge', - id: intersection.triHit?.id, - faceId, - intersection: { - ...intersection, - triHit: undefined, - }, - }); + const triHit = intersection.triHit; + const edge = loop.find((v) => (v.vertex === triHit.aId) && (v.vertex2 === triHit.bId))?.edge; + if (edge !== undefined) + results.unshift({ + kind: 'edge', + id: edge, + faceId, + intersection: { + ...intersection, + triHit: undefined, + }, + }); } if (intersection.triHit?.kind === 'vertex') { results.unshift({ diff --git a/client/src/layers/sceneSync.ts b/client/src/layers/sceneSync.ts index 7c3810f..b07ff68 100644 --- a/client/src/layers/sceneSync.ts +++ b/client/src/layers/sceneSync.ts @@ -16,6 +16,7 @@ export class SceneSync { side: THREE.DoubleSide, transparent: true, depthWrite: false, + wireframe: true, uniforms: { frontColor: { value: new THREE.Color(0x88ccff) }, frontOpacity: { value: 0.6 }, @@ -90,10 +91,8 @@ export class SceneSync { const geoVCount = geo.attributes.position.count; geo.setAttribute('selected', new THREE.BufferAttribute(new Float32Array(geoVCount), 1)); const mesh = new THREE.Mesh(geo, this.baseMaterial); - mesh.userData.faceId = faceId; - mesh.userData.vertexIds = dto.vertexIds; - mesh.userData.surfaceId = dto.surfaceId; - mesh.userData.solidId = dto.solidId; + mesh.userData = dto + mesh.userData.vertexIds = dto.loop.map((v) => v.vertex); this.scene.add(mesh); this.meshByFace[faceId] = mesh; diff --git a/client/src/model/model.ts b/client/src/model/model.ts index 2023f5a..885d2ee 100644 --- a/client/src/model/model.ts +++ b/client/src/model/model.ts @@ -54,6 +54,14 @@ export class Model { 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[] { const loop = this.loopById(loopId)!; const startHalfEdgeId = loop.start; @@ -76,6 +84,10 @@ export class Model { 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 { return this.loops[id]; } @@ -91,6 +103,19 @@ export class Model { public solidById(id: Solid['id']): Solid | undefined { 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(); diff --git a/client/src/types/geometry.ts b/client/src/types/geometry.ts index c1bdebe..60c32a5 100644 --- a/client/src/types/geometry.ts +++ b/client/src/types/geometry.ts @@ -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 V3 = [x: number, y: number, z: number]; -// export type V3 = [number, number, number]; export type Mesh = { vertices: V3[]; normals: V3[]; indices: number[]; - vertexIds: Vertex['id'][]; faceId: Face['id']; surfaceId: Surface['id']; solidId: Solid['id']; + loop: { edge: Edge['id'], halfEdge: HalfEdge['id'], vertex: Vertex['id'] }[]; };