UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

350 lines (324 loc) 10.9 kB
import {CoreMath} from './../../../core/math/_Module'; import {BaseCoreObject} from './../../../core/geometry/entities/object/BaseCoreObject'; import {TypeAssert} from './../../poly/Assert'; import {AttribClass} from './../../../core/geometry/Constant'; import {BaseSopOperation} from './_Base'; import {CoreGroup, Object3DWithGeometry} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {Vector3} from 'three'; import {pushOnArrayAtEntry} from '../../../core/MapUtils'; import {BufferAttribute} from 'three'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import {CoreObjectType, ObjectContent} from '../../../core/geometry/ObjectContent'; import {isBooleanTrue} from '../../../core/Type'; import {setToArray} from '../../../core/SetUtils'; import {isNumber} from '../../../core/Type'; import {pointsFromObject} from '../../../core/geometry/entities/point/CorePointUtils'; import {corePointClassFactory} from '../../../core/geometry/CoreObjectFactory'; import { CorePoint } from '../../../core/geometry/entities/point/CorePoint'; const tmpPos = new Vector3(); export enum SortMode { RANDOM = 'random', AXIS = 'axis', ATTRIBUTE = 'attribute', } export const SORT_MODES: SortMode[] = [SortMode.AXIS, SortMode.RANDOM, SortMode.ATTRIBUTE]; export type SortTargetType = AttribClass.POINT | AttribClass.OBJECT; export const SORT_TARGET_TYPES: Array<SortTargetType> = [AttribClass.POINT, AttribClass.OBJECT]; export enum Axis { X = 'x', Y = 'y', Z = 'z', } export const AXISES: Axis[] = [Axis.X, Axis.Y, Axis.Z]; const _points:CorePoint<CoreObjectType>[]=[] interface SortSopParams extends DefaultOperationParams { targetType: number; mode: number; // random seed: number; // axis axis: number; // attribute attribute: string; // common invert: boolean; } export class SortSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: SortSopParams = { mode: SORT_MODES.indexOf(SortMode.AXIS), targetType: SORT_TARGET_TYPES.indexOf(AttribClass.POINT), seed: 0, axis: AXISES.indexOf(Axis.X), attribute: '', invert: false, }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<'sort'> { return 'sort'; } override cook(inputCoreGroups: CoreGroup[], params: SortSopParams) { const coreGroup = inputCoreGroups[0]; this._sort(coreGroup, params); return coreGroup; } private _sort(coreGroup: CoreGroup, params: SortSopParams) { const targetType = SORT_TARGET_TYPES[params.targetType]; switch (targetType) { case AttribClass.POINT: return this._sortPoints(coreGroup, params); case AttribClass.OBJECT: return this._sortObjects(coreGroup, params); } } private _sortObjects(coreGroup: CoreGroup, params: SortSopParams) { const sortMode = SORT_MODES[params.mode]; switch (sortMode) { case SortMode.AXIS: return this._sortObjectsByAxis(coreGroup, params); case SortMode.RANDOM: return this._sortObjectsByRandom(coreGroup, params); case SortMode.ATTRIBUTE: return this._sortObjectsByAttribute(coreGroup, params); } TypeAssert.unreachable(sortMode); } private _sortObjectsByAxis(coreGroup: CoreGroup, params: SortSopParams) { const coreObjects = coreGroup.allCoreObjects(); const objectsByPos: Map<number, BaseCoreObject<CoreObjectType>[]> = new Map(); const positions: Set<number> = new Set(); // accumulate axisValue const axis = AXISES[params.axis]; let axisValue: number = 0; for (let coreObject of coreObjects) { coreObject.position(tmpPos); switch (axis) { case Axis.X: { axisValue = tmpPos.x; break; } case Axis.Y: { axisValue = tmpPos.y; break; } case Axis.Z: { axisValue = tmpPos.z; break; } } positions.add(axisValue); pushOnArrayAtEntry(objectsByPos, axisValue, coreObject); } // sort let sortedPositions: number[] = setToArray(positions, []).sort((a, b) => a - b); if (isBooleanTrue(params.invert)) { sortedPositions.reverse(); } const sortedObjects: ObjectContent<CoreObjectType>[] = []; for (let position of sortedPositions) { const coreObjectsForPosition = objectsByPos.get(position); if (coreObjectsForPosition) { for (let coreObjectForPosition of coreObjectsForPosition) { const object = coreObjectForPosition.object(); if (object) { sortedObjects.push(object); } } } } coreGroup.setAllObjects(sortedObjects); } private _sortObjectsByRandom(coreGroup: CoreGroup, params: SortSopParams) { const coreObjects = coreGroup.allCoreObjects(); const objectsByPos: Map<number, BaseCoreObject<CoreObjectType>[]> = new Map(); const positions: number[] = []; // accumulate axisValue let sortValue: number = 0; let i = 0; for (let coreObject of coreObjects) { sortValue = CoreMath.randFloat(params.seed, i); positions[i] = sortValue; pushOnArrayAtEntry(objectsByPos, sortValue, coreObject); i++; } // sort let sortedPositions: number[] = positions.sort((a, b) => a - b); if (params.invert) { sortedPositions.reverse(); } const sortedObjects: ObjectContent<CoreObjectType>[] = []; for (let position of sortedPositions) { const coreObjectsForPosition = objectsByPos.get(position); if (coreObjectsForPosition) { for (let coreObjectForPosition of coreObjectsForPosition) { const object = coreObjectForPosition.object(); if (object) { sortedObjects.push(object); } } } } coreGroup.setAllObjects(sortedObjects); } private _sortObjectsByAttribute(coreGroup: CoreGroup, params: SortSopParams) { const coreObjects = coreGroup.allCoreObjects(); const objectsByAttribValue: Map<number, BaseCoreObject<CoreObjectType>[]> = new Map(); const attribValues: number[] = []; // accumulate attribValue let i = 0; for (let coreObject of coreObjects) { const attribValue = coreObject.attribValue(params.attribute); const sortValue = isNumber(attribValue) ? attribValue : 0; attribValues[i] = sortValue; pushOnArrayAtEntry(objectsByAttribValue, sortValue, coreObject); i++; } // sort let sortedValues: number[] = attribValues.sort((a, b) => a - b); if (params.invert) { sortedValues.reverse(); } const sortedObjects: ObjectContent<CoreObjectType>[] = []; for (let sortedValue of sortedValues) { const coreObjectsForPosition = objectsByAttribValue.get(sortedValue); if (coreObjectsForPosition) { for (let coreObjectForPosition of coreObjectsForPosition) { const object = coreObjectForPosition.object(); if (object) { sortedObjects.push(object); } } } } coreGroup.setAllObjects(sortedObjects); } private _sortPoints(coreGroup: CoreGroup, params: SortSopParams) { const sortMode = SORT_MODES[params.mode]; switch (sortMode) { case SortMode.AXIS: return this._sortPointsByAxis(coreGroup, params); case SortMode.RANDOM: return this._sortPointsByRandom(coreGroup, params); case SortMode.ATTRIBUTE: return this._sortPointsByAttribute(coreGroup, params); } TypeAssert.unreachable(sortMode); } private _sortPointsByAxis(coreGroup: CoreGroup, params: SortSopParams) { const objects = coreGroup.threejsObjectsWithGeo(); for (let object of objects) { this._sortPointsForObject(object, params); } } private _sortPointsByRandom(coreGroup: CoreGroup, params: SortSopParams) { this.states?.error.set('sorting points in random mode is not yet implemented'); } private _sortPointsByAttribute(coreGroup: CoreGroup, params: SortSopParams) { this.states?.error.set('sorting points by attribute is not yet implemented'); } private _pointPos = new Vector3(); private _positions: number[] = []; private _indicesByPos: Map<number, number[]> = new Map(); private _indexDest: Map<number, number> = new Map(); private _debugActive = false; private _debug(a: any) { if (!this._debugActive) { return; } } private _sortPointsForObject(object: Object3DWithGeometry, params: SortSopParams) { pointsFromObject(object,_points); const oldIndexAttribute = object.geometry.getIndex(); if (!oldIndexAttribute) { console.warn('geometry cannot be sorted since it has no index'); return; } const oldIndices = oldIndexAttribute.array; // reset this._positions = new Array(_points.length); this._indicesByPos.clear(); this._indexDest.clear(); // accumulate axisValue const axis = AXISES[params.axis]; let axisValue: number = 0; let i = 0; for (let point of _points) { point.position(this._pointPos); switch (axis) { case Axis.X: { axisValue = this._pointPos.x; break; } case Axis.Y: { axisValue = this._pointPos.y; break; } case Axis.Z: { axisValue = this._pointPos.z; break; } } this._positions[i] = axisValue; pushOnArrayAtEntry(this._indicesByPos, axisValue, point.index()); i++; } // sort let sortedPositions: number[] = this._positions.sort((a, b) => a - b); if (params.invert) { sortedPositions.reverse(); } // update the index attribute const newIndices: number[] = new Array(_points.length); i = 0; // const uniqSortedPositions = ArrayUtils.uniq(sortedPositions); for (let position of sortedPositions) { const indices = this._indicesByPos.get(position); if (indices) { this._indicesByPos.delete(position); for (let index of indices) { newIndices[i] = index; this._indexDest.set(index, i); i++; } } } const newIndexAttrib = new Array(oldIndices.length); for (let i = 0; i < oldIndices.length; i++) { const oldIndex = oldIndices[i]; const newI = this._indexDest.get(oldIndex); newIndexAttrib[i] = newI; } object.geometry.setIndex(newIndexAttrib); // update every attribute const corePointClass = corePointClassFactory(object); const attributeNames = corePointClass.attributeNames(object); for (let attributeName of attributeNames) { if (attributeName == 'id') { this._debugActive = true; } const attribute = object.geometry.getAttribute(attributeName); this._updateAttribute(attribute as BufferAttribute, newIndices); this._debugActive = false; } } private _updateAttribute(attribute: BufferAttribute, newIndices: number[]) { const clonedAttribute = attribute.clone(); const srcArray = attribute.array; const clonedArray = clonedAttribute.array; const itemSize = clonedAttribute.itemSize; this._debug(newIndices); for (let newIndex of newIndices) { const oldIndex = this._indexDest.get(newIndex); this._debug(`${newIndex} -> ${oldIndex}`); if (oldIndex != null) { for (let i = 0; i < itemSize; i++) { clonedArray[oldIndex * itemSize + i] = srcArray[newIndex * itemSize + i]; } } else { console.warn('no old index found'); } } attribute.array = clonedArray; attribute.needsUpdate = true; } }