UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

345 lines (279 loc) • 8.31 kB
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 { query_edge_is_boundary } from "../query/query_edge_is_boundary.js"; import { query_edge_other_vertex } from "../query/query_edge_other_vertex.js"; import { query_vertex_in_edge } from "../query/query_vertex_in_edge.js"; import { query_vertices_in_edge } from "../query/query_vertices_in_edge.js"; let index_counter = 0; export class TopoEdge { /** * Unique ID * @type {number} */ index = index_counter++; /** * * @type {TopoVertex} */ v0 = null; /** * * @type {TopoVertex} */ v1 = null; /** * * @type {TopoTriangle[]} */ faces = []; /** * * @type {number} */ lengthSqr = -1; get byteSize() { return 80 + 5 * 4 + 4 + 8 + 8 + 8 + this.faces.length * 8 + 10; } /** * * @param {number} i * @returns {TopoVertex} */ getVertexByIndex(i) { if (i === 0) { return this.v0; } else if (i === 1) { return this.v1; } else { throw new Error('Index out of bounds'); } } /** * * @param {TopoEdge} other */ copy(other) { this.v0 = other.v0; this.v1 = other.v1; this.lengthSqr = other.lengthSqr; this.faces = other.faces.slice(); } clone() { const r = new TopoEdge(); r.copy(this); return r; } /** * * @param {function(reason:string)} error_consumer * @return {boolean} */ validate(error_consumer) { let valid = true; const faces = this.faces; const face_count = faces.length; // if (face_count > 2) { // error_consumer(`Too many faces, a single edge can have at most two attached edge, instead got '${face_count}'`); // valid = false; // } for (let i = 0; i < face_count; i++) { const face = faces[i]; if (!face.containsEdge(this)) { error_consumer(`Missing back-link from face[${i}]`); valid = false; } } if (!this.v0.containsEdge(this)) { error_consumer(`Missing back-link from vertex v0`); valid = false; } if (!this.v1.containsEdge(this)) { error_consumer(`Missing back-link from vertex v1`); valid = false; } return valid; } /** * Returns false if edge is not licked back to by it's vertices (v0 is used for the check) * @return {boolean} */ isLinked() { return this.v0.containsEdge(this); } /** * Remove self from the topology graph */ unlink() { const v0 = this.v0; const v1 = this.v1; v0.removeEdge(this); if (v0 !== v1) { // special check for degenerate edges where both vertices are the same v1.removeEdge(this); } const faces = this.faces; const face_count = faces.length; for (let i = 0; i < face_count; i++) { const face = faces[i]; face.removeEdge(this); } } /** * * @returns {boolean} */ isDegenerateEdge() { return this.v0 === this.v1; } /** * @deprecated use {@link query_edge_is_boundary} instead * Is this a an edge of topology as a whole? * @return {boolean} */ isTopologyBorder() { throw new Error(`deprecated, use "query_edge_is_boundary" instead`) } /** * Note: requires length square to be pre-computed * @return {number} */ get length() { if (this.lengthSqr < 0) { this.computeSquaredLength(); } return Math.sqrt(this.lengthSqr); } computeSquaredLength() { const v0 = this.v0; const v1 = this.v1; const x0 = v0.x; const y0 = v0.y; const z0 = v0.z; const x1 = v1.x; const y1 = v1.y; const z1 = v1.z; const dx = x0 - x1; const dy = y0 - y1; const dz = z0 - z1; this.lengthSqr = dx * dx + dy * dy + dz * dz; } /** * NOTE: does not update faces * @param {TopoVertex} existing * @param {TopoVertex} replacement */ replaceVertex(existing, replacement) { if (existing === this.v0) { this.v0 = replacement; } // NOTE: we're supporting case where both vertices are the same (degenerate edge) if (existing === this.v1) { this.v1 = replacement; } } /** * Absorb other edge, as a result other edge becomes redundant * NOTE: edges MUST share vertices * @param {TopoEdge} other */ merge(other) { assert.equal(other.isTopoEdge, true, "other.isTopoEdge !== true"); assert.notEqual(this, other, "other === this"); //absorb faces const other_faces = other.faces; const own_faces = this.faces; const face_count = other_faces.length; for (let i = 0; i < face_count; i++) { const face = other_faces[i]; //remove references to old edge from the face face.removeEdge(other); if (own_faces.indexOf(face) === -1) { // not patched yet own_faces.push(face); //add this edge to face face.addUniqueEdge(this); } } //patch vertices const other_v0 = other.v0; assert.equal(this.v0 === other_v0 || this.v1 === other_v0, true, 'this edge does not include other.v0'); //cut old edge from vertex 0 other_v0.removeEdge(other); const other_v1 = other.v1; assert.equal(this.v0 === other_v1 || this.v1 === other_v1, true, 'this edge does not include other.v1'); //cut old edge from vertex 1 other_v1.removeEdge(other); } /** * * @param {TopoTriangle} face * @return {boolean} */ containsFace(face) { return this.faces.indexOf(face) !== -1; } /** * * @param {TopoTriangle} face */ addFace(face) { assert.equal(face.isTopoFace, true, "face.isTopoFace !== true"); assert.arrayHasNo(this.faces, face, 'already contains this face'); this.faces.push(face); } /** * * @param {TopoTriangle} face */ addUniqueFace(face) { assert.equal(face.isTopoFace, true, "face.isTopoFace !== true"); array_push_if_unique(this.faces, face); } /** * * @param {TopoTriangle} f */ removeFace(f) { array_remove_first(this.faces, f); assert.arrayHasNo(this.faces, f, 'found an instance of removed face'); } /** * @deprecated use {@link query_edge_other_vertex} instead * @param {TopoVertex} v * @return {TopoVertex} */ getOtherVertex(v) { return query_edge_other_vertex(this, v); } /** * @deprecated use {@link @query_vertex_in_edge} instead * @param {TopoVertex} v * @return {boolean} */ containsVertex(v) { return query_vertex_in_edge(this, v); } /** * @deprecated use {@link query_vertices_in_edge} instead * @param {TopoVertex} a * @param {TopoVertex} b * @return {boolean} */ containsBothVertices(a, b) { return query_vertices_in_edge(this, a, b); } /** * * @param {TopoEdge} other * @returns {boolean} */ containsSameVerticesAs(other) { return query_vertices_in_edge(this, other.v0, other.v1); } } /** * @readonly * @type {boolean} */ TopoEdge.prototype.isTopoEdge = true;