UNPKG

@polygonjs/polygonjs

Version:

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

208 lines (195 loc) 7.96 kB
import {TypeAssert} from './../../poly/Assert'; import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {AttribClass, ATTRIBUTE_CLASSES} from '../../../core/geometry/Constant'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import {coreObjectClassFactory, coreObjectInstanceFactory} from '../../../core/geometry/CoreObjectFactory'; import {filterObjectsFromCoreGroup} from '../../../core/geometry/Mask'; import {CoreEntity} from '../../../core/geometry/CoreEntity'; import {ENTITY_CLASS_FACTORY} from '../../../core/geometry/CoreObjectFactory'; import {arrayMin, arrayMax, ArrayToItemFunction} from '../../../core/ArrayUtils'; import {AttribValue, Constructor} from '../../../types/GlobalTypes'; import {isNumber, isString, isBoolean} from '../../../core/Type'; import {Vector2, Vector3, Vector4, Color} from 'three'; import {TraversedRelatedEntityData} from '../../../core/geometry/entities/utils/TraversedRelatedEntities'; import {pushOnArrayAtEntry} from '../../../core/MapUtils'; import {CorePoint} from '../../../core/geometry/entities/point/CorePoint'; import {CoreObjectType} from '../../../core/geometry/ObjectContent'; const _v2 = new Vector2(); const _v3 = new Vector3(); const _v4 = new Vector4(); const _c = new Color(); interface AttribPromoteSopParams extends DefaultOperationParams { group: string; classFrom: number; classTo: number; mode: number; name: string; } export enum AttribPromoteMode { MIN = 'min', MAX = 'max', FIRST_FOUND = 'first found', } export const ATTRIB_PROMOTE_MODES: AttribPromoteMode[] = [ AttribPromoteMode.MIN, AttribPromoteMode.MAX, AttribPromoteMode.FIRST_FOUND, ]; export class AttribPromoteSopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: AttribPromoteSopParams = { group: '', classFrom: ATTRIBUTE_CLASSES.indexOf(AttribClass.POINT), classTo: ATTRIBUTE_CLASSES.indexOf(AttribClass.OBJECT), mode: ATTRIB_PROMOTE_MODES.indexOf(AttribPromoteMode.FIRST_FOUND), name: '', }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<'attribPromote'> { return 'attribPromote'; } override cook(inputCoreGroups: CoreGroup[], params: AttribPromoteSopParams) { const coreGroup = inputCoreGroups[0]; const classFrom = ATTRIBUTE_CLASSES[params.classFrom]; const classTo = ATTRIBUTE_CLASSES[params.classTo]; const mode = ATTRIB_PROMOTE_MODES[params.mode]; const objects = filterObjectsFromCoreGroup(coreGroup, params); for (const object of objects) { const factoryFrom = ENTITY_CLASS_FACTORY[classFrom]; const factoryTo = ENTITY_CLASS_FACTORY[classTo]; const attribNames = factoryFrom ? factoryFrom(object).attributeNamesMatchingMask(object, params.name) : coreGroup.attributeNamesMatchingMask(params.name); // add attribute if it does not exist on the dest entities for (const attribName of attribNames) { const hasAttribute = factoryTo ? factoryTo(object).hasAttribute(object, attribName) : coreGroup.hasAttribute(attribName); if (!hasAttribute) { const srcAttribSize: number | null = factoryFrom ? factoryFrom(object).attribSize(object, attribName) : coreGroup.attribSize(attribName); if (srcAttribSize != null) { if (factoryTo) { factoryTo(object).addNumericAttribute(object, attribName, srcAttribSize); } else { coreGroup.addNumericAttribute(attribName, srcAttribSize); } } } } // promote attribute const destEntities: CoreEntity[] = []; const srcEntities: CoreEntity[] = []; const traversedRelatedEntityData: TraversedRelatedEntityData = { [AttribClass.CORE_GROUP]: {ids: []}, [AttribClass.OBJECT]: {ids: []}, [AttribClass.POINT]: {ids: []}, [AttribClass.PRIMITIVE]: {ids: []}, [AttribClass.VERTEX]: {ids: []}, }; const coreObjectClass = coreObjectClassFactory(object); coreObjectInstanceFactory(object).relatedEntities( classTo, coreGroup, destEntities, traversedRelatedEntityData ); const traversedClassFromEntityIds = traversedRelatedEntityData[classFrom].ids; const classFromEntitiesByClassToEntitytIndex: Map<number, CoreEntity[]> = new Map(); if (traversedClassFromEntityIds.length > 0 && classFrom != AttribClass.CORE_GROUP) { const entityClassFrom = coreObjectClass.relatedEntityClass(object, classFrom) as any as Constructor< CorePoint<CoreObjectType> >; for (const classFromEntityId of traversedClassFromEntityIds) { const classToEntities: CoreEntity[] = []; const classFromEntity = new entityClassFrom(object, classFromEntityId); classFromEntity.relatedEntities(classTo, coreGroup, classToEntities); for (const classToEntity of classToEntities) { const index = classToEntity.index(); pushOnArrayAtEntry(classFromEntitiesByClassToEntitytIndex, index, classFromEntity); } } } for (const destEntity of destEntities) { const cachedEntities = classFromEntitiesByClassToEntitytIndex.get(destEntity.index()); if (cachedEntities) { for (const attribName of attribNames) { this._promoteAttribute(attribName, cachedEntities, destEntity, mode); } } else { destEntity.relatedEntities(classFrom, coreGroup, srcEntities); for (const attribName of attribNames) { this._promoteAttribute(attribName, srcEntities, destEntity, mode); } } } } return coreGroup; } private _promoteAttribute( attribName: string, srcEntities: CoreEntity[], destEntity: CoreEntity, mode: AttribPromoteMode ) { const srcValues = srcEntities.map((entity) => entity.attribValue(attribName)!); const destValue = this._convertSrcValues(srcValues, mode); destEntity.setAttribValue(attribName, destValue as any); } private _convertSrcValues(srcValues: AttribValue[], mode: AttribPromoteMode): AttribValue { switch (mode) { case AttribPromoteMode.MIN: { return this._convertValuesMin(srcValues); } case AttribPromoteMode.MAX: { return this._convertValuesMax(srcValues); } case AttribPromoteMode.FIRST_FOUND: { return this._convertValuesFirstFound(srcValues); } } TypeAssert.unreachable(mode); } private _convertValuesMin(srcValues: AttribValue[]): AttribValue { return this._convertValuesMinMax(srcValues, arrayMin); } private _convertValuesMax(srcValues: AttribValue[]): AttribValue { return this._convertValuesMinMax(srcValues, arrayMax); } private _convertValuesMinMax(srcValues: AttribValue[], arrayFunc: ArrayToItemFunction<number>): AttribValue { const firstValue = srcValues[0]; if (isNumber(firstValue) || isString(firstValue) || isBoolean(firstValue)) { return arrayFunc(srcValues as number[]); } if (firstValue instanceof Vector2) { _v2.x = arrayFunc((srcValues as Vector2[]).map((v) => v.x)); _v2.y = arrayFunc((srcValues as Vector2[]).map((v) => v.y)); return _v2; } if (firstValue instanceof Vector3) { _v3.x = arrayFunc((srcValues as Vector3[]).map((v) => v.x)); _v3.y = arrayFunc((srcValues as Vector3[]).map((v) => v.y)); _v3.z = arrayFunc((srcValues as Vector3[]).map((v) => v.z)); return _v3; } if (firstValue instanceof Color) { _c.r = arrayFunc((srcValues as Color[]).map((c) => c.r)); _c.g = arrayFunc((srcValues as Color[]).map((c) => c.g)); _c.b = arrayFunc((srcValues as Color[]).map((c) => c.b)); return _c; } if (firstValue instanceof Vector4) { _v4.x = arrayFunc((srcValues as Vector4[]).map((v) => v.x)); _v4.y = arrayFunc((srcValues as Vector4[]).map((v) => v.y)); _v4.z = arrayFunc((srcValues as Vector4[]).map((v) => v.z)); _v4.w = arrayFunc((srcValues as Vector4[]).map((v) => v.w)); return _v4; } return 0; } private _convertValuesFirstFound(srcValues: AttribValue[]): AttribValue { return srcValues[0]; } }