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