@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
265 lines (264 loc) • 10.4 kB
JavaScript
"use strict";
import { BufferAttribute, Vector2, Vector3, Vector4 } from "three";
import { addToSetAtEntry } from "../../MapUtils";
import { triangleGraphFromGeometry } from "../modules/three/graph/triangle/TriangleGraphUtils";
import { setToArray } from "../../SetUtils";
const tmpV2 = new Vector2();
const tmpV3 = new Vector3();
const tmpV4 = new Vector4();
class FusePosition {
constructor(positionAttribute, index, tolerance) {
this.positionAttribute = positionAttribute;
this.index = index;
this.originalPosition = new Vector3();
this.snappedPosition = new Vector3();
this.originalPosition.fromBufferAttribute(positionAttribute, this.index);
roundedPos(positionAttribute, this.index, this.snappedPosition, tolerance);
this.snappedKey = `${this.snappedPosition.x}:${this.snappedPosition.y}:${this.snappedPosition.z}`;
}
addAttribValue(geometry, attribName, targetArray) {
const attribute = geometry.getAttribute(attribName);
switch (attribute.itemSize) {
case 1: {
const val = attribute.getX(this.index);
targetArray.push(val);
break;
}
case 2: {
tmpV2.fromBufferAttribute(attribute, this.index);
tmpV2.toArray(targetArray, targetArray.length);
break;
}
case 3: {
tmpV3.fromBufferAttribute(attribute, this.index);
tmpV3.toArray(targetArray, targetArray.length);
break;
}
case 4: {
tmpV4.fromBufferAttribute(attribute, this.index);
tmpV4.toArray(targetArray, targetArray.length);
break;
}
}
}
}
class FuseFace {
constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
}
function averagePosition(positions, target) {
target.set(0, 0, 0);
positions.forEach((position) => {
target.add(position.originalPosition);
});
target.divideScalar(positions.size);
}
function roundedPos(position, index, target, tolerance) {
target.fromBufferAttribute(position, index);
if (tolerance > 0) {
target.x = Math.round(target.x / tolerance) * tolerance;
target.y = Math.round(target.y / tolerance) * tolerance;
target.z = Math.round(target.z / tolerance) * tolerance;
}
}
function isFaceCollapsed(face) {
return face.a.snappedKey == face.b.snappedKey || face.a.snappedKey == face.c.snappedKey || face.b.snappedKey == face.c.snappedKey;
}
const tmpAttribute = new BufferAttribute(new Float32Array(0), 0);
const _positions = [
new FusePosition(tmpAttribute, 0, 0.1),
new FusePosition(tmpAttribute, 0, 0.1),
new FusePosition(tmpAttribute, 0, 0.1)
];
export function mergeFacesWithUnsharedEdges(geometry, tolerance) {
function getUnsharedPointAndFaceIndices(geometry2, pointIndices) {
const graph = triangleGraphFromGeometry(geometry2);
const edgeIds = [];
graph.edgeIds(edgeIds);
const unsharedPointIndicesSet = /* @__PURE__ */ new Set();
const unsharedFaceIndicesSet = /* @__PURE__ */ new Set();
for (const edgeId of edgeIds) {
const edge = graph.edge(edgeId);
if (edge.triangleIds.length == 1) {
unsharedPointIndicesSet.add(edge.pointIdPair.id0);
unsharedPointIndicesSet.add(edge.pointIdPair.id1);
unsharedFaceIndicesSet.add(edge.triangleIds[0]);
}
}
setToArray(unsharedPointIndicesSet, pointIndices);
}
const index = geometry.getIndex();
if (!index) {
return;
}
const fusablePointIndices = [];
getUnsharedPointAndFaceIndices(geometry, fusablePointIndices);
const indexArray = index.array;
const positionAttribute = geometry.getAttribute("position");
const positionsCount = positionAttribute.count;
const facesCount = indexArray.length / 3;
const fusedPositions = new Array(positionsCount);
const pointsBySnappedPos = /* @__PURE__ */ new Map();
const firstPointBySnappedPos = /* @__PURE__ */ new Map();
const averagePosBySnappedKey = /* @__PURE__ */ new Map();
const newIndexBySnappedKey = /* @__PURE__ */ new Map();
const newPositions = [];
const newIndices = [];
const newAttributeValues = {};
const otherAttributeNames = Object.keys(geometry.attributes).filter((attribName) => attribName != "position");
const otherAttributeNamesCount = otherAttributeNames.length;
for (let k = 0; k < otherAttributeNamesCount; k++) {
const otherAttributeName = otherAttributeNames[k];
newAttributeValues[otherAttributeName] = [];
}
for (let i = 0; i < fusablePointIndices.length; i++) {
const index2 = fusablePointIndices[i];
const position = new FusePosition(positionAttribute, index2, tolerance);
fusedPositions[index2] = position;
addToSetAtEntry(pointsBySnappedPos, position.snappedKey, position);
if (!firstPointBySnappedPos.has(position.snappedKey)) {
firstPointBySnappedPos.set(position.snappedKey, position);
}
}
const snappedIndicesSet = /* @__PURE__ */ new Set();
pointsBySnappedPos.forEach((points, snappedKey) => {
const averageV3 = new Vector3();
if (points.size > 1) {
const pointIndices = [];
let i = 0;
points.forEach((point) => {
pointIndices.push(point.index);
snappedIndicesSet.add(point.index);
i++;
});
pointIndices.sort((a, b) => a - b);
averagePosition(points, averageV3);
averagePosBySnappedKey.set(snappedKey, averageV3);
}
});
const newIndexByOldIndex = [];
const indicesToReplace = /* @__PURE__ */ new Set();
const indicesInUse = /* @__PURE__ */ new Set();
for (let fi = 0; fi < facesCount; fi++) {
for (let vi = 0; vi < 3; vi++) {
const currentIndexIndex = fi * 3 + vi;
const index2 = indexArray[currentIndexIndex];
if (snappedIndicesSet.has(index2)) {
const fusedPosition = fusedPositions[index2];
let newIndex = newIndexBySnappedKey.get(fusedPosition.snappedKey);
const averagePos = averagePosBySnappedKey.get(fusedPosition.snappedKey);
if (newIndex == null) {
newIndex = newPositions.length / 3;
newIndexBySnappedKey.set(fusedPosition.snappedKey, newIndex);
}
newIndices.push(newIndex);
averagePos.toArray(newPositions, newIndex * 3);
if (newIndex != index2 && indicesInUse.has(index2) == false) {
indicesToReplace.add(index2);
}
indicesInUse.add(newIndex);
} else {
let newIndex = newIndexByOldIndex[index2];
if (newIndex == null) {
newIndex = newPositions.length / 3;
newIndexByOldIndex[index2] = newIndex;
}
indicesInUse.add(newIndex);
if (newIndex != index2 && indicesInUse.has(index2) == false) {
indicesToReplace.add(index2);
}
tmpV3.fromBufferAttribute(positionAttribute, index2);
newIndices.push(newIndex);
tmpV3.toArray(newPositions, newIndex * 3);
}
}
}
geometry.setAttribute("position", new BufferAttribute(new Float32Array(newPositions), 3));
geometry.setIndex(newIndices);
}
export function mergeFaces(geometry, tolerance) {
const index = geometry.getIndex();
if (!index) {
return;
}
const indexArray = index.array;
const positionAttribute = geometry.getAttribute("position");
const positionsCount = positionAttribute.count;
const facesCount = indexArray.length / 3;
const positions = new Array(positionsCount);
const faces = new Array(facesCount);
const pointsBySnappedPos = /* @__PURE__ */ new Map();
const firstPointBySnappedPos = /* @__PURE__ */ new Map();
const averagePosBySnappedKey = /* @__PURE__ */ new Map();
const newIndexBySnappedKey = /* @__PURE__ */ new Map();
const newPositions = [];
const newIndices = [];
const newAttributeValues = {};
const otherAttributeNames = Object.keys(geometry.attributes).filter((attribName) => attribName != "position");
const otherAttributeNamesCount = otherAttributeNames.length;
for (let k = 0; k < otherAttributeNamesCount; k++) {
const otherAttributeName = otherAttributeNames[k];
newAttributeValues[otherAttributeName] = [];
}
for (let i = 0; i < positionsCount; i++) {
const position = new FusePosition(positionAttribute, i, tolerance);
positions[i] = position;
addToSetAtEntry(pointsBySnappedPos, position.snappedKey, position);
if (!firstPointBySnappedPos.has(position.snappedKey)) {
firstPointBySnappedPos.set(position.snappedKey, position);
}
}
pointsBySnappedPos.forEach((points, snappedKey) => {
const averageV3 = new Vector3();
averagePosition(points, averageV3);
averagePosBySnappedKey.set(snappedKey, averageV3);
});
for (let i = 0; i < facesCount; i++) {
const a = positions[indexArray[i * 3]];
const b = positions[indexArray[i * 3 + 1]];
const c = positions[indexArray[i * 3 + 2]];
const face = new FuseFace(a, b, c);
faces[i] = face;
}
const fusedFacesToKeep = [];
for (let i = 0; i < facesCount; i++) {
const face = faces[i];
if (!isFaceCollapsed(face)) {
fusedFacesToKeep.push(face);
}
}
const fusedFacesToKeepCount = fusedFacesToKeep.length;
for (let i = 0; i < fusedFacesToKeepCount; i++) {
const face = fusedFacesToKeep[i];
_positions[0] = face.a;
_positions[1] = face.b;
_positions[2] = face.c;
for (let j = 0; j < 3; j++) {
const position = _positions[j];
let newIndex = newIndexBySnappedKey.get(position.snappedKey);
const averagePos = averagePosBySnappedKey.get(position.snappedKey);
if (newIndex == null) {
newIndex = newPositions.length / 3;
newIndexBySnappedKey.set(position.snappedKey, newIndex);
averagePos.toArray(newPositions, newPositions.length);
const firstPoint = firstPointBySnappedPos.get(position.snappedKey);
for (let k = 0; k < otherAttributeNamesCount; k++) {
const otherAttribName = otherAttributeNames[k];
firstPoint.addAttribValue(geometry, otherAttribName, newAttributeValues[otherAttribName]);
}
}
newIndices.push(newIndex);
}
}
geometry.setAttribute("position", new BufferAttribute(new Float32Array(newPositions), 3));
for (let k = 0; k < otherAttributeNamesCount; k++) {
const attribName = otherAttributeNames[k];
const attribute = geometry.getAttribute(attribName);
const newValues = newAttributeValues[attribName];
geometry.setAttribute(attribName, new BufferAttribute(new Float32Array(newValues), attribute.itemSize));
}
geometry.setIndex(newIndices);
}