@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
345 lines (279 loc) • 8.31 kB
JavaScript
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;