UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

350 lines (278 loc) • 8.61 kB
import { vec3 } from "gl-matrix"; import { assert } from "../../../../assert.js"; import { array_push_if_unique } from "../../../../collection/array/array_push_if_unique.js"; import { array_remove_first } from "../../../../collection/array/array_remove_first.js"; import { array_replace_all } from "../../../../collection/array/array_replace_all.js"; import { compute_triangle_normal } from "../../triangle/compute_triangle_normal.js"; import { TopoEdge } from "./TopoEdge.js"; import { TopoVertex } from "./TopoVertex.js"; let index_counter = 0; export class TopoTriangle { /** * Identifying index, can be used to link back to buffer-geometry face by index or any other useful ID purposes * @type {number} */ index = index_counter++; /** * * @type {TopoVertex[]} */ vertices = []; /** * * @type {TopoEdge[]} */ edges = []; /** * * @type {number[]|vec3} */ normal = [0, 0, 0]; get byteSize() { return 80 + 4 * 4 + 4 + this.vertices.length * 8 + 10 + this.edges.length * 8 + 10 + 8 * 3 + 10; } /** * * @param {TopoTriangle} other */ copy(other) { this.index = other.index; this.vertices = other.vertices.slice(); this.edges = other.edges.slice(); vec3.copy(this.normal, other.normal); } clone() { const r = new TopoTriangle(); r.copy(this); return r; } /** * * @param {function(reason:string)} error_consumer * @return {boolean} */ validate(error_consumer) { let valid = true; const vertices = this.vertices; const vertex_count = vertices.length; if (vertex_count !== 3) { error_consumer(`Expected number of vertices is 3, instead got ${vertex_count}`); valid = false; } for (let i = 0; i < vertex_count; i++) { const vertex = vertices[i]; if (!vertex.containsFace(this)) { error_consumer(`Missing back-link from vertex[${i}]`); valid = false; } } const edges = this.edges; const edge_count = edges.length; if (edge_count !== 3) { error_consumer(`Expected number of edges is 3, instead got ${edge_count}`); valid = false; } for (let i = 0; i < edge_count; i++) { const edge = edges[i]; if (!edge.containsFace(this)) { error_consumer(`Missing back-link from edge[${i}]`); valid = false; } } //TODO check uniqueness of vertices //TODO check uniqueness of edges return valid; } /** * * @return {boolean} */ isLinked() { const v0 = this.vertices[0]; return v0.containsFace(this); } /** * NOTE: does not update vertices * @param {TopoEdge} existing * @param {TopoEdge} replacement */ replaceEdge(existing, replacement) { array_replace_all(this.edges, existing, replacement); } /** * NOTE: does not update edges * @param {TopoVertex} existing * @param {TopoVertex} replacement */ replaceVertex(existing, replacement) { array_replace_all(this.vertices, existing, replacement); assert.arrayHasNo(this.vertices, existing, 'found removed element'); } /** * Some or all vertices are the same, resulting in line or point representation */ isDegenerateTopology() { const vertices = this.vertices; const vA = vertices[0]; const vB = vertices[1]; const vC = vertices[2]; return vA === vB || vA === vC || vB === vC; } /** * Remove self from the topology graph */ unlink() { // update references of vertices const vertices = this.vertices; const vertex_count = vertices.length; for (let i = 0; i < vertex_count; i++) { const vertex = vertices[i]; vertex.removeFace(this); } // update references of edges const edges = this.edges; const edge_count = edges.length; for (let i = 0; i < edge_count; i++) { const edge = edges[i]; edge.removeFace(this); } } /** * * @param {TopoEdge} e */ addEdge(e) { assert.arrayHasNo(this.edges, e, 'already contains this edge'); this.edges.push(e); } /** * * @param {TopoEdge} e * @return {boolean} */ addUniqueEdge(e) { return array_push_if_unique(this.edges, e); } /** * * @param {TopoEdge} e */ removeEdge(e) { assert.equal(e.isTopoEdge, true, 'e.isTopoEdge !== true'); array_remove_first(this.edges, e); assert.arrayHasNo(this.edges, e, 'contains removed edge'); } /** * * @param {TopoEdge} edge * @returns {boolean} */ containsEdge(edge) { assert.equal(edge.isTopoEdge, true, 'edge.isTopoEdge !== true'); return this.edges.indexOf(edge) !== -1; } computeNormal() { const vertices = this.vertices; const vA = vertices[0]; const vB = vertices[1]; const vC = vertices[2]; compute_triangle_normal(this.normal, vA, vB, vC); // if (this.normal.z === -1) { // debugger; // } } /** * * @param {number} index * @param {TopoVertex} vertex */ setVertexAt(index, vertex) { assert.equal(vertex.isTopoVertex, true, 'vertex.isTopoVertex !== true'); assert.isNonNegativeInteger(index, 'index'); this.vertices[index] = vertex; } /** * * @param {TopoVertex} vertex * @return {boolean} */ containsVertex(vertex) { return this.vertices.indexOf(vertex) !== -1; } /** * Get all neighbours that share an edge with this face * @param {TopoTriangle[]} result * @param {number} result_offset * @returns {number} number of found neighbours */ getEdgeNeighbours(result, result_offset) { let additions = 0; const edges = this.edges; const edge_count = edges.length; for (let i = 0; i < edge_count; i++) { const edge = edges[i]; const edge_faces = edge.faces; const edge_face_count = edge_faces.length; for (let j = 0; j < edge_face_count; j++) { const f = edge_faces[j]; if (f === this) { continue; } // NOTE: this is already de-duped, as edges only have 2 neighbours, and this face is one of the two result[result_offset + additions] = f; additions++; } } return additions; } /** * Utility constructor * @param {number} ax * @param {number} ay * @param {number} az * @param {number} bx * @param {number} by * @param {number} bz * @param {number} cx * @param {number} cy * @param {number} cz * @return {TopoTriangle} */ static fromPoints( ax, ay, az, bx, by, bz, cx, cy, cz ) { const v0 = TopoVertex.from(ax, ay, az); const v1 = TopoVertex.from(bx, by, bz); const v2 = TopoVertex.from(cx, cy, cz); const e0 = new TopoEdge(); const e1 = new TopoEdge(); const e2 = new TopoEdge(); e0.v0 = v0; e0.v1 = v1; e1.v0 = v1; e1.v1 = v2; e2.v0 = v2; e2.v1 = v0; const triangle = new TopoTriangle(); triangle.edges.push(e0, e1, e2); triangle.vertices.push(v0, v1, v2); // link v0.faces.push(triangle); v1.faces.push(triangle); v2.faces.push(triangle); e0.faces.push(triangle); e1.faces.push(triangle); e2.faces.push(triangle); v0.edges.push(e0, e2); v1.edges.push(e0, e1); v2.edges.push(e1, e2); return triangle; } } /** * @readonly * @type {boolean} */ TopoTriangle.prototype.isTopoFace = true;