@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
350 lines (324 loc) • 10.9 kB
text/typescript
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;
}
}