@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
211 lines (194 loc) • 7.02 kB
text/typescript
/**
* custom
*
*
*
*/
import {TypedSopNode} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {BufferAttribute, Vector3, Mesh, Object3D, Box3} from 'three';
import {triangleGraphFromGeometry} from '../../../core/geometry/modules/three/graph/triangle/TriangleGraphUtils';
import {setToArray} from '../../../core/SetUtils';
import {TriangleGraph} from '../../../core/geometry/modules/three/graph/triangle/TriangleGraph';
import {arrayCopy} from '../../../core/ArrayUtils';
import {triangleEdgePositions} from '../../../core/geometry/modules/three/graph/triangle/TriangleEdge';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {TypeAssert} from '../../poly/Assert';
const _p0 = new Vector3();
const _p1 = new Vector3();
const _box = new Box3();
const _n = new Vector3();
export enum ExtrudeOpenEdgesFilterMethod {
BELOW_Y = 'belowY',
IN_BBOX = 'inBbox',
}
const FILTER_METHODS: ExtrudeOpenEdgesFilterMethod[] = [
ExtrudeOpenEdgesFilterMethod.BELOW_Y,
ExtrudeOpenEdgesFilterMethod.IN_BBOX,
];
class ExtrudeOpenEdgesSopParamConfig extends NodeParamsConfig {
offset = ParamConfig.VECTOR3([0, 1, 0]);
filterEdges = ParamConfig.BOOLEAN(false);
/** @param filter method */
filterMethod = ParamConfig.INTEGER(FILTER_METHODS.indexOf(ExtrudeOpenEdgesFilterMethod.BELOW_Y), {
menu: {
entries: FILTER_METHODS.map((name, value) => ({name, value})),
},
visibleIf: {
filterEdges: true,
},
});
bboxMin = ParamConfig.VECTOR3([-1, -1, -1], {
visibleIf: {
filterEdges: true,
filterMethod: FILTER_METHODS.indexOf(ExtrudeOpenEdgesFilterMethod.IN_BBOX),
},
});
bboxMax = ParamConfig.VECTOR3([1, 1, 1], {
visibleIf: {
filterEdges: true,
filterMethod: FILTER_METHODS.indexOf(ExtrudeOpenEdgesFilterMethod.IN_BBOX),
},
});
yThreshold = ParamConfig.FLOAT(0, {
visibleIf: {
filterEdges: true,
filterMethod: FILTER_METHODS.indexOf(ExtrudeOpenEdgesFilterMethod.BELOW_Y),
},
range: [-1, 1],
rangeLocked: [false, false],
});
}
const ParamsConfig = new ExtrudeOpenEdgesSopParamConfig();
export class ExtrudeOpenEdgesSopNode extends TypedSopNode<ExtrudeOpenEdgesSopParamConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.EXTRUDE_OPEN_EDGES;
}
override initializeNode() {
this.io.inputs.setCount(1);
}
override cook(inputCoreGroups: CoreGroup[]) {
const coreGroup = inputCoreGroups[0];
const objects = coreGroup.threejsObjects();
for (const object of objects) {
this._findAndExtrudeOpenEdges(object);
}
this.setCoreGroup(coreGroup);
}
private _findAndExtrudeOpenEdges(object: Object3D) {
const mesh = object as Mesh;
const geometry = mesh.geometry;
if (!geometry) return;
const positionAttribute = geometry.getAttribute('position') as BufferAttribute;
const normalAttribute = geometry.getAttribute('normal') as BufferAttribute;
const index = geometry.getIndex();
if (!(positionAttribute && normalAttribute && index)) {
return;
}
const graph = triangleGraphFromGeometry(mesh.geometry);
if (!graph) {
return;
}
const edgeIds: string[] = [];
graph.edgeIds(edgeIds);
const unsharedEdgeIdsSet: Set<string> = new Set();
for (const edgeId of edgeIds) {
const edge = graph.edge(edgeId);
if (!edge) continue;
if (edge.triangleIds.length == 1) {
unsharedEdgeIdsSet.add(edgeId);
}
}
let unsharedEdgeIds: string[] = [];
setToArray(unsharedEdgeIdsSet, unsharedEdgeIds);
const newPositionValues: number[] = [];
const newNormalValues: number[] = [];
const newIndexValues: number[] = [];
arrayCopy(positionAttribute.array, newPositionValues);
arrayCopy(normalAttribute.array, newNormalValues);
arrayCopy(index.array, newIndexValues);
if (this.pv.filterEdges) {
const filterMethod = FILTER_METHODS[this.pv.filterMethod];
unsharedEdgeIds = this._filterEdgeIds(graph, unsharedEdgeIds, newPositionValues, filterMethod);
}
for (const edgeId of unsharedEdgeIds) {
this._extrudeEdge(graph, edgeId, newPositionValues, newNormalValues, newIndexValues);
}
geometry.setAttribute('position', new BufferAttribute(new Float32Array(newPositionValues), 3));
geometry.setAttribute('normal', new BufferAttribute(new Float32Array(newNormalValues), 3));
geometry.setIndex(newIndexValues);
}
private _filterEdgeIds(
graph: TriangleGraph,
edgeIds: string[],
positionValues: number[],
filterMethod: ExtrudeOpenEdgesFilterMethod
): string[] {
switch (filterMethod) {
case ExtrudeOpenEdgesFilterMethod.BELOW_Y: {
const yThreshold = this.pv.yThreshold;
return edgeIds.filter((edgeId) => {
const edge = graph.edge(edgeId);
if (!edge) return false;
triangleEdgePositions(edge, positionValues, _p0, _p1);
return _p0.y < yThreshold && _p1.y < yThreshold;
});
}
case ExtrudeOpenEdgesFilterMethod.IN_BBOX: {
_box.min.copy(this.pv.bboxMin);
_box.max.copy(this.pv.bboxMax);
return edgeIds.filter((edgeId) => {
const edge = graph.edge(edgeId);
if (!edge) return false;
triangleEdgePositions(edge, positionValues, _p0, _p1);
_p0.add(_p1).divideScalar(2);
return _box.containsPoint(_p0);
});
}
}
TypeAssert.unreachable(filterMethod);
}
private _extrudeEdge(
graph: TriangleGraph,
edgeId: string,
newPositionValues: number[],
newNormalValues: number[],
newIndexValues: number[]
) {
const edge = graph.edge(edgeId);
if (!edge) return;
const otherTriangleId = edge.triangleIds[0];
const otherTriangle = graph.triangle(otherTriangleId)!;
// we need to know what order we can use the 2 indices of this edge.
// In order to know this, we first look at the triangle that this edge belongs to.
// we then check if the 2 indices are in the same order as in otherTriangle.triangle.
// If they are, we need to invert the indices.
// If not, we don't invert.
const id0Index = otherTriangle.triangle.indexOf(edge.pointIdPair.id0);
const id1Index = otherTriangle.triangle.indexOf(edge.pointIdPair.id1);
const invertRequired =
(id0Index == 0 && id1Index == 1) || (id0Index == 1 && id1Index == 2) || (id0Index == 2 && id1Index == 0);
//
const pointIdPair = edge.pointIdPair;
triangleEdgePositions(edge, newPositionValues, _p0, _p1);
_p0.add(this.pv.offset);
_p1.add(this.pv.offset);
_n.copy(_p1).sub(_p0).cross(this.pv.offset);
const p0Index = newPositionValues.length / 3;
const p1Index = p0Index + 1;
newPositionValues.push(_p0.x, _p0.y, _p0.z);
newPositionValues.push(_p1.x, _p1.y, _p1.z);
if (invertRequired) {
_n.multiplyScalar(-1);
newIndexValues.push(pointIdPair.id1, pointIdPair.id0, p0Index);
newIndexValues.push(pointIdPair.id1, p0Index, p1Index);
} else {
newIndexValues.push(pointIdPair.id0, pointIdPair.id1, p0Index);
newIndexValues.push(p0Index, pointIdPair.id1, p1Index);
}
newNormalValues.push(_n.x, _n.y, _n.z);
newNormalValues.push(_n.x, _n.y, _n.z);
}
}