@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
346 lines (328 loc) • 11.3 kB
text/typescript
/**
* Creates a quad plane.
*
*
*/
import {Vector3, Triangle, Quaternion} from 'three';
import {QuadSopNode} from './_BaseQuad';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {CoreGroup} from '../../../core/geometry/Group';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {QuadGeometry} from '../../../core/geometry/modules/quad/QuadGeometry';
import {Attribute} from '../../../core/geometry/Attribute';
import {setToArray} from '../../../core/SetUtils';
import {InputCloneMode} from '../../poly/InputCloneMode';
import {TypeAssert} from '../../poly/Assert';
import {typedArrayCopy} from '../../../core/ArrayUtils';
const _current = new Vector3();
const _neighbourAverage = new Vector3();
const _neighbour = new Vector3();
const _p0 = new Vector3();
const _p1 = new Vector3();
const _p2 = new Vector3();
const _p3 = new Vector3();
const _delta0 = new Vector3();
const _delta1 = new Vector3();
const _delta2 = new Vector3();
const _delta3 = new Vector3();
const _currentDelta0 = new Vector3();
const _currentDelta1 = new Vector3();
const _currentDelta2 = new Vector3();
const _currentDelta3 = new Vector3();
const _center = new Vector3();
const _triangle = new Triangle();
const _triangleNormal = new Vector3();
const _q = new Quaternion();
const _average = new Vector3();
export enum QuadSmoothMode {
SQUARIFY = 'squarify',
AVERAGE = 'average',
}
export const QUAD_SMOOTH_MODES: QuadSmoothMode[] = [QuadSmoothMode.AVERAGE, QuadSmoothMode.SQUARIFY];
class QuadSmoothSopParamsConfig extends NodeParamsConfig {
/** @param mode */
mode = ParamConfig.INTEGER(QUAD_SMOOTH_MODES.indexOf(QuadSmoothMode.SQUARIFY), {
menu: {
entries: QUAD_SMOOTH_MODES.map((name, value) => {
return {name, value};
}),
},
});
/** @param iterations */
iterations = ParamConfig.INTEGER(50, {
range: [0, 100],
rangeLocked: [true, false],
});
/** @param strength */
strength = ParamConfig.FLOAT(1, {
range: [0, 1],
rangeLocked: [true, true],
});
}
const ParamsConfig = new QuadSmoothSopParamsConfig();
export class QuadSmoothSopNode extends QuadSopNode<QuadSmoothSopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.QUAD_SMOOTH;
}
override initializeNode() {
this.io.inputs.setCount(1);
this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE);
}
override cook(inputCoreGroups: CoreGroup[]) {
const coreGroup = inputCoreGroups[0];
const quadObjects = coreGroup.quadObjects();
if (quadObjects) {
for (const object of quadObjects) {
this._smoothQuadGeometry(object.geometry);
}
}
this.setCoreGroup(coreGroup);
}
setMode(method: QuadSmoothMode) {
this.p.mode.set(QUAD_SMOOTH_MODES.indexOf(method));
}
mode() {
return QUAD_SMOOTH_MODES[this.pv.mode];
}
private _smoothQuadGeometry(geometry: QuadGeometry) {
const mode = this.mode();
switch (mode) {
case QuadSmoothMode.AVERAGE:
return this._smoothQuadGeometryWithAverage(geometry);
case QuadSmoothMode.SQUARIFY:
return this._smoothQuadGeometryWithSquarify(geometry);
}
TypeAssert.unreachable(mode);
}
private _smoothQuadGeometryWithAverage(geometry: QuadGeometry) {
const position = geometry.attributes[Attribute.POSITION];
if (!position) {
return;
}
const tmpPositionArray0 = typedArrayCopy(position.array, new Float32Array(position.array.length));
const tmpPositionArray1 = typedArrayCopy(tmpPositionArray0, new Float32Array(position.array.length));
const index = geometry.index;
const quadsCount = geometry.quadsCount();
const adjacentIdByIndexWithSets = new Map<number, Set<number>>();
const quadCountByEdge = new Map<number, Map<number, Set<number>>>();
const _addQuadCount = (quadIndex: number, i0: number, i1: number) => {
let quadCountByEdgeEntry = quadCountByEdge.get(i0);
if (!quadCountByEdgeEntry) {
quadCountByEdgeEntry = new Map();
quadCountByEdge.set(i0, quadCountByEdgeEntry);
}
let quadIndices = quadCountByEdgeEntry.get(i1);
if (!quadIndices) {
quadIndices = new Set();
}
quadIndices.add(quadIndex);
quadCountByEdgeEntry.set(i1, quadIndices);
};
const _addAdjacency = (quadIndex: number, current: number, adjacent: number) => {
let adjacentIds = adjacentIdByIndexWithSets.get(current);
if (!adjacentIds) {
adjacentIds = new Set<number>();
adjacentIdByIndexWithSets.set(current, adjacentIds);
}
adjacentIds.add(adjacent);
// add quad count
_addQuadCount(quadIndex, current, adjacent);
_addQuadCount(quadIndex, adjacent, current);
};
for (let i = 0; i < quadsCount; i++) {
const i4 = i * 4;
const i0 = index[i4 + 0];
const i1 = index[i4 + 1];
const i2 = index[i4 + 2];
const i3 = index[i4 + 3];
// i0
_addAdjacency(i, i0, i3);
_addAdjacency(i, i0, i1);
// i1
_addAdjacency(i, i1, i0);
_addAdjacency(i, i1, i2);
// i2
_addAdjacency(i, i2, i1);
_addAdjacency(i, i2, i3);
// i3
_addAdjacency(i, i3, i2);
_addAdjacency(i, i3, i0);
}
const adjacentIdByIndex = new Map<number, number[]>();
adjacentIdByIndexWithSets.forEach((adjacentIds, index) => {
adjacentIdByIndex.set(index, setToArray(adjacentIds, []));
});
adjacentIdByIndexWithSets.clear();
// keep track of points that are on unshared edges,
// since we don't want to smoot those
const pointsOnUnsharedEdges = new Set<number>();
adjacentIdByIndex.forEach((_, index) => {
const quadCountByEdgeEntry = quadCountByEdge.get(index);
if (!quadCountByEdgeEntry) {
return;
}
quadCountByEdgeEntry.forEach((quadIndices, key1) => {
if (quadIndices.size == 1) {
pointsOnUnsharedEdges.add(index);
}
});
});
// smooth
const iterations = this.pv.iterations;
const strength = this.pv.strength;
const lerp = 1 - strength;
let previousPositionArray = tmpPositionArray0;
let nextPositionArray = tmpPositionArray1;
for (let i = 0; i < iterations; i++) {
// let neighboursCount=0
adjacentIdByIndex.forEach((adjacentIds, index) => {
// do not smooth if on an unshared edge
if (pointsOnUnsharedEdges.has(index)) {
return;
}
//
_current.fromArray(previousPositionArray, index * 3);
// i=0
// _sphere.center.set(_current.x, _current.y, _current.z);
// _sphere.radius = 0;
_neighbourAverage.set(0, 0, 0);
for (const adjacentId of adjacentIds) {
// _neighbours[i] = _neighbours[i] || new Vector3()
// const _vA = _neighbours[i]
_neighbour.fromArray(previousPositionArray, adjacentId * 3);
_neighbourAverage.add(_neighbour);
// _sphere.expandByPoint(_neighbour);
// i++
}
_neighbourAverage.divideScalar(adjacentIds.length);
_neighbourAverage.lerp(_current, lerp);
_neighbourAverage.toArray(nextPositionArray, index * 3);
});
// swap arrays
const tmp = nextPositionArray;
nextPositionArray = previousPositionArray;
previousPositionArray = tmp;
}
// write back to geometry
position.array = previousPositionArray;
}
private _smoothQuadGeometryWithSquarify(geometry: QuadGeometry) {
const position = geometry.attributes[Attribute.POSITION];
if (!position) {
return;
}
const iterations = this.pv.iterations;
const strength = this.pv.strength;
// const lerp = 1 - strength;
const positionArray = position.array;
const pointsCount = positionArray.length / 3;
// const tmpPositionArray0 = [...position.array];
// const tmpPositionArray1 = [...tmpPositionArray0];
const deltas = new Array(positionArray.length).fill(0);
const deltasCount = new Array(pointsCount).fill(0);
//
const index = geometry.index;
const indicesCount = index.length;
let previousPositionArray = positionArray; //tmpPositionArray0;
let nextPositionArray = positionArray; //tmpPositionArray1;
// const quadsCount = geometry.quadsCount();
for (let i = 0; i < iterations; i++) {
deltas.fill(0);
for (let q = 0; q < indicesCount; q += 4) {
const i0 = index[q + 0];
const i1 = index[q + 1];
const i2 = index[q + 2];
const i3 = index[q + 3];
const i0_3 = i0 * 3;
const i1_3 = i1 * 3;
const i2_3 = i2 * 3;
const i3_3 = i3 * 3;
_p0.fromArray(previousPositionArray, i0_3);
_p1.fromArray(previousPositionArray, i1_3);
_p2.fromArray(previousPositionArray, i2_3);
_p3.fromArray(previousPositionArray, i3_3);
_delta0.copy(_p0);
_delta1.copy(_p1);
_delta2.copy(_p2);
_delta3.copy(_p3);
_triangle.a.copy(_p0);
_triangle.b.copy(_p1);
_triangle.c.copy(_p2);
_triangle.getNormal(_triangleNormal);
_center.copy(_p0).add(_p1).add(_p2).add(_p3).multiplyScalar(0.25);
// getCircumCenter(_p0, _p1, _p2, _p3, _center);
// console.log('_center', _center.toArray());
// rotate each vec
_p0.sub(_center);
_p1.sub(_center);
_p2.sub(_center);
_p3.sub(_center);
// align all 4 vectors
_q.setFromAxisAngle(_triangleNormal, -Math.PI * 0.5);
_p1.applyQuaternion(_q);
_q.setFromAxisAngle(_triangleNormal, -Math.PI * 1);
_p2.applyQuaternion(_q);
_q.setFromAxisAngle(_triangleNormal, -Math.PI * 1.5);
_p3.applyQuaternion(_q);
// get average
_average.copy(_p0).add(_p1).add(_p2).add(_p3).multiplyScalar(0.25);
_p0.lerp(_average, strength);
_p1.lerp(_average, strength);
_p2.lerp(_average, strength);
_p3.lerp(_average, strength);
// rotate back
_q.setFromAxisAngle(_triangleNormal, +Math.PI * 0.5);
_p1.applyQuaternion(_q);
_q.setFromAxisAngle(_triangleNormal, +Math.PI * 1);
_p2.applyQuaternion(_q);
_q.setFromAxisAngle(_triangleNormal, +Math.PI * 1.5);
_p3.applyQuaternion(_q);
_p0.add(_center);
_p1.add(_center);
_p2.add(_center);
_p3.add(_center);
// accumulate deltas
_delta0.sub(_p0).multiplyScalar(-1);
_delta1.sub(_p1).multiplyScalar(-1);
_delta2.sub(_p2).multiplyScalar(-1);
_delta3.sub(_p3).multiplyScalar(-1);
_currentDelta0.fromArray(deltas, i0_3);
_currentDelta1.fromArray(deltas, i1_3);
_currentDelta2.fromArray(deltas, i2_3);
_currentDelta3.fromArray(deltas, i3_3);
_delta0.add(_currentDelta0).toArray(deltas, i0_3);
_delta1.add(_currentDelta1).toArray(deltas, i1_3);
_delta2.add(_currentDelta2).toArray(deltas, i2_3);
_delta3.add(_currentDelta3).toArray(deltas, i3_3);
deltasCount[i0]++;
deltasCount[i1]++;
deltasCount[i2]++;
deltasCount[i3]++;
// write to array
// _p0.toArray(nextPositionArray, index[q + 0] * 3);
// _p1.toArray(nextPositionArray, index[q + 1] * 3);
// _p2.toArray(nextPositionArray, index[q + 2] * 3);
// _p3.toArray(nextPositionArray, index[q + 3] * 3);
}
// apply delta
// console.log(deltasCount);
for (let i = 0; i < pointsCount; i++) {
const deltaCount = deltasCount[i];
if (deltaCount > 0) {
_current.fromArray(previousPositionArray, i * 3);
_currentDelta0.fromArray(deltas, i * 3).divideScalar(deltaCount);
// console.log(i, _currentDelta0.toArray());
_current.add(_currentDelta0);
_current.toArray(nextPositionArray, i * 3);
}
}
// swap arrays
// const tmp = nextPositionArray;
// nextPositionArray = previousPositionArray;
// previousPositionArray = tmp;
}
// write back to geometry
position.array = previousPositionArray;
}
}