@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
153 lines (144 loc) • 5.14 kB
text/typescript
import {BaseSopOperation} from './_Base';
import {Group, Material, Mesh} from 'three';
import {CoreGroup, Object3DWithGeometry} from '../../../core/geometry/Group';
import {pushOnArrayAtEntry,addToSetAtEntry} from '../../../core/MapUtils';
import {InputCloneMode} from '../../../engine/poly/InputCloneMode';
import {isBooleanTrue} from '../../../core/BooleanValue';
import {DefaultOperationParams} from '../../../core/operations/_Base';
import {CoreObjectType, MergeCompactOptions, ObjectContent} from '../../../core/geometry/ObjectContent';
import {coreObjectClassFactory} from '../../../core/geometry/CoreObjectFactory';
import {setToArray} from '../../../core/SetUtils';
import {NodeErrorState} from '../../nodes/utils/states/Error';
import {NodeContext} from '../../poly/NodeContext';
interface PreserveMaterialOptions {
preserveMaterials: boolean;
}
interface MergeSopParams extends PreserveMaterialOptions, DefaultOperationParams {
compact: boolean;
}
export class MergeSopOperation extends BaseSopOperation {
static override readonly DEFAULT_PARAMS: MergeSopParams = {
compact: false,
preserveMaterials: true,
};
static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE;
static override type(): Readonly<'merge'> {
return 'merge';
}
// TODO: improvement:
// for compact, I should really keep track of geometry ids,
// to make sure I am not including a geometry twice, if there is a hierarchy
override cook(inputCoreGroups: CoreGroup[], params: MergeSopParams) {
let allObjects: ObjectContent<CoreObjectType>[] = [];
for (const inputCoreGroup of inputCoreGroups) {
if (inputCoreGroup) {
const objects = inputCoreGroup.allObjects();
if (isBooleanTrue(params.compact)) {
for (const object of objects) {
object.traverse((child) => {
allObjects.push(child as Object3DWithGeometry);
});
}
} else {
// if we are not compact,
// we only use the current level, not children
for (const object of objects) {
allObjects.push(object);
}
}
}
}
if (isBooleanTrue(params.compact)) {
allObjects = MergeSopOperation.makeCompact(allObjects, params, this.states?.error);
}
return this.createCoreGroupFromObjects(allObjects);
}
static makeCompact(
allObjects: ObjectContent<CoreObjectType>[],
options: PreserveMaterialOptions,
errorState?: NodeErrorState<NodeContext>
): ObjectContent<CoreObjectType>[] {
const {preserveMaterials} = options;
const materialsByObjectType: Map<string, Material> = new Map();
const objectsByType: Map<string, ObjectContent<CoreObjectType>[]> = new Map();
const orderedObjectTypes: string[] = [];
for (let object of allObjects) {
object.traverse((object3d) => {
if (object3d instanceof Group) {
// we do not want groups,
// as their children will end up being duplicated
return;
}
const objectType: string | undefined = object.type; //objectTypeFromConstructor(object.constructor);
if (objectType) {
if (!orderedObjectTypes.includes(objectType)) {
orderedObjectTypes.push(objectType);
}
if (objectType) {
const foundMat = materialsByObjectType.get(objectType);
if (!foundMat) {
materialsByObjectType.set(objectType, (object as Mesh).material as Material);
}
pushOnArrayAtEntry(objectsByType, objectType, object);
}
}
});
}
const mergedObjects: ObjectContent<CoreObjectType>[] = [];
orderedObjectTypes.forEach((objectType) => {
const material = materialsByObjectType.get(objectType);
const objects = objectsByType.get(objectType);
if (objects && objects.length != 0) {
// even with just 1 geometry,
// we should still perform the merge,
// to make sure the output is consistent.
// The main discrepency notices is that if not merged,
// any non-identity matrix will be preserved, when it should not
// if (objects.length == 1) {
// mergedObjects.push(objects[0]);
// } else {
if (isBooleanTrue(preserveMaterials)) {
_makeCompactWithPreservedMaterials({
objects,
material,
objectType,
mergedObjects,
onError: (message) => {
errorState?.set(message);
},
});
} else {
const coreObjectClass = coreObjectClassFactory(objects[0]);
coreObjectClass.mergeCompact({
objects,
material,
objectType,
mergedObjects,
onError: (message) => {
errorState?.set(message);
},
});
}
}
});
return mergedObjects;
}
}
const objectsByMaterial: Map<Material, Set<ObjectContent<CoreObjectType>>> = new Map();
function _makeCompactWithPreservedMaterials(options: MergeCompactOptions) {
const {objects, objectType, mergedObjects, onError} = options;
const coreObjectClass = coreObjectClassFactory(objects[0]);
objectsByMaterial.clear();
for (let object of objects) {
addToSetAtEntry(objectsByMaterial, object.material, object);
}
objectsByMaterial.forEach((objectSet, material) => {
coreObjectClass.mergeCompact({
objects: setToArray(objectSet, []),
material,
objectType,
mergedObjects,
onError,
});
});
}