@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
186 lines (164 loc) • 6.47 kB
text/typescript
import {BaseSopOperation} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {Vector3, Matrix4, LineSegments, BufferGeometry, Object3D} from 'three';
import {ObjectType} from '../../../core/geometry/Constant';
import {CircleCreateOptions, CoreGeometryUtilCircle} from '../../../core/geometry/util/Circle';
import {CoreGeometryUtilCurve} from '../../../core/geometry/util/Curve';
import {CoreGeometryOperationSkin} from '../../../core/geometry/operation/Skin';
import {BaseCorePoint, CorePoint} from '../../../core/geometry/entities/point/CorePoint';
import {isBooleanTrue} from '../../../core/Type';
import {DefaultOperationParams} from '../../../core/operations/_Base';
import {CoreGeometryBuilderMerge} from '../../../core/geometry/modules/three/builders/Merge';
import {addAttributesFromPoint} from '../../../core/geometry/util/addAttributesFromPoint';
import {pointsFromObject} from '../../../core/geometry/entities/point/CorePointUtils';
import {corePointClassFactory} from '../../../core/geometry/CoreObjectFactory';
import {copyObject3DProperties} from '../../../core/geometry/modules/three/ThreejsObjectUtils';
import {CoreObjectType} from '../../../core/geometry/ObjectContent';
import {SopType} from '../../poly/registers/nodes/types/Sop';
interface PolywireSopParams extends DefaultOperationParams {
radius: number;
segmentsRadial: number;
closed: boolean;
attributesToCopy: string;
}
const ORIGIN = new Vector3(0, 0, 0);
const UP = new Vector3(0, 1, 0);
const lookAtMat = new Matrix4();
const translateMat = new Matrix4();
const currentPos = new Vector3();
const prevPos = new Vector3();
const nextPos = new Vector3();
const delta = new Vector3();
const deltaNext = new Vector3();
const deltaPrev = new Vector3();
const _points: CorePoint<CoreObjectType>[] = [];
export class PolywireSopOperation extends BaseSopOperation {
static override readonly DEFAULT_PARAMS: PolywireSopParams = {
radius: 1,
segmentsRadial: 8,
closed: true,
attributesToCopy: '*',
};
static override type(): Readonly<SopType.POLYWIRE> {
return SopType.POLYWIRE;
}
override cook(inputCoreGroups: CoreGroup[], params: PolywireSopParams) {
const coreGroup = inputCoreGroups[0];
const inputObjects = coreGroup.threejsObjects();
const newObjects: Object3D[] = [];
for (const inputObject of inputObjects) {
const geometries: BufferGeometry[] = [];
if ((inputObject as LineSegments).isLineSegments) {
this._createTube(inputObject as LineSegments, params, geometries);
}
const mergedGeometry = CoreGeometryBuilderMerge.merge(geometries);
if (mergedGeometry) {
const newObject = this.createObject(mergedGeometry, ObjectType.MESH);
copyObject3DProperties(inputObject, newObject);
newObjects.push(newObject);
}
}
// const mergedGeometry = CoreGeometryBuilderMerge.merge(geometries);
// for (const geometry of geometries) {
// geometry.dispose();
// }
// if (mergedGeometry) {
// const object = this.createObject(mergedGeometry, ObjectType.MESH);
return this.createCoreGroupFromObjects(newObjects);
// } else {
// return this.createCoreGroupFromObjects([]);
// }
}
private _createTube(lineSegment: LineSegments, params: PolywireSopParams, geometries: BufferGeometry[]) {
const geometry = lineSegment.geometry as BufferGeometry;
const corePointClass = corePointClassFactory(lineSegment);
const attributeNames = corePointClass.attributeNamesMatchingMask(lineSegment, params.attributesToCopy);
pointsFromObject(lineSegment, _points);
const indices = geometry.getIndex()?.array;
if (!indices) {
return;
}
const accumulatedCurvePointIndices = CoreGeometryUtilCurve.accumulatedCurvePointIndices(indices);
for (let curvePointIndices of accumulatedCurvePointIndices) {
const currentPoints = curvePointIndices.map((index) => _points[index]);
this._createTubeFromPoints(currentPoints, attributeNames, params, geometries);
}
}
private _createTubeFromPoints(
points: BaseCorePoint[],
attributeNames: string[],
params: PolywireSopParams,
geometries: BufferGeometry[]
) {
if (points.length <= 1) {
return;
}
const options: CircleCreateOptions = {
radius: params.radius,
segments: params.segmentsRadial,
arcAngle: 360,
connectLastPoint: true,
};
const circleTemplate = CoreGeometryUtilCircle.create(options);
const circles: BufferGeometry[] = [];
// const scale = 1;
let i = 0;
for (let point of points) {
point.position(currentPos);
let prevPoint = points[i - 1];
let nextPoint = points[i + 1];
if (prevPoint && nextPoint) {
// if we have both prev and next points, the dir is a blend between the dir to the prev and to the next
nextPoint.position(nextPos);
deltaNext.copy(nextPos).sub(currentPos);
prevPoint.position(prevPos);
deltaPrev.copy(prevPos).sub(currentPos).multiplyScalar(-1);
delta.lerpVectors(deltaNext, deltaPrev, 0.5);
} else {
if (nextPoint) {
nextPoint.position(nextPos);
delta.copy(nextPos).sub(currentPos);
} else {
nextPoint = points[i - 1];
nextPoint.position(nextPos);
delta.copy(nextPos).sub(currentPos).multiplyScalar(-1);
}
}
lookAtMat.identity();
lookAtMat.lookAt(ORIGIN, delta.multiplyScalar(-1), UP);
translateMat.identity();
translateMat.makeTranslation(currentPos.x, currentPos.y, currentPos.z);
const newCircle = circleTemplate.clone();
newCircle.applyMatrix4(lookAtMat);
newCircle.applyMatrix4(translateMat);
// remove position before transfering the attribute
const positionIndex = attributeNames.indexOf('position');
if (positionIndex >= 0) {
attributeNames.splice(positionIndex, 1);
}
addAttributesFromPoint(newCircle, point, attributeNames);
circles.push(newCircle);
i++;
}
for (let i = 0; i < circles.length; i++) {
if (i > 0) {
const circle = circles[i];
const prevCircle = circles[i - 1];
const geometry = this._skin(prevCircle, circle);
geometries.push(geometry);
}
}
if (isBooleanTrue(params.closed)) {
const circle = circles[0];
const prevCircle = circles[circles.length - 1];
const geometry = this._skin(prevCircle, circle);
geometries.push(geometry);
}
}
_skin(geometry1: BufferGeometry, geometry0: BufferGeometry) {
const geometry = new BufferGeometry();
const operation = new CoreGeometryOperationSkin(geometry, geometry1, geometry0);
operation.process();
return geometry;
}
}