@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
661 lines (632 loc) • 21.2 kB
text/typescript
import {AttribValue, Number3, NumericAttribValue, PolyDictionary} from '../../../../types/GlobalTypes';
import {Box3, Color, Matrix4, Sphere, Vector2, Vector3, Vector4} from 'three';
import {Attribute, CoreAttribute} from '../../Attribute';
import {AttribType, AttribSize, ObjectData, GroupString, AttribClass} from '../../Constant';
import {CoreEntity, CoreEntityWithObject} from '../../CoreEntity';
import {isArray,isVector,isColor,isString} from '../../../Type';
import {
ObjectContent,
CoreObjectType,
ObjectGeometryMap,
MergeCompactOptions,
ObjectBuilder,
} from '../../ObjectContent';
import {TransformTargetType} from '../../../Transform';
import {ObjectTransformMode, ObjectTransformSpace} from '../../../TransformSpace';
import {EntityGroupCollection} from '../../EntityGroupCollection';
import {_updateObjectAttribRef} from '../../../reactivity/ObjectAttributeReactivityUpdateRef';
import {AttributeDictionary} from '../object/BaseCoreObjectUtils';
import {attribValueNonPrimitive, copyAttribValue, cloneAttribValue, uniqRelatedEntityIds} from '../utils/Common';
import {getOrCreateObjectAttributeRef} from '../../../reactivity/ObjectAttributeReactivityCreateRef';
import {
JsIConnectionPointTypeToDataTypeMap,
ParamConvertibleJsType,
} from '../../../../engine/nodes/utils/io/connections/Js';
import {watch} from '../../../reactivity/CoreReactivity';
import {objectData} from './BaseCoreObjectUtils';
import {TypeAssert} from '../../../../engine/poly/Assert';
// import {uniqRelatedEntityIds} from '../utils/Common';
import type {CoreGroup} from '../../Group';
import type {CorePrimitive} from '../primitive/CorePrimitive';
import {CoreVertex} from '../vertex/CoreVertex';
import {CorePoint} from '../point/CorePoint';
import {TraversedRelatedEntityData} from '../utils/TraversedRelatedEntities';
enum PropertyName {
NAME = 'name',
POSITION = 'position',
}
const ATTRIBUTES = 'attributes';
// const _relatedPoints: CorePoint<CoreObjectType>[] = [];
// const _relatedVertices: CoreVertex<CoreObjectType>[] = [];
// const _relatedPrimitives: CorePrimitive<CoreObjectType>[] = [];
// const ATTRIBUTES_PREVIOUS_VALUES = 'attributesPreviousValues';
const ORIGIN = new Vector3(0, 0, 0);
function _convertArrayToVector(value: number[]) {
switch (value.length) {
case 1:
return value[0];
case 2:
return new Vector2(value[0], value[1]);
case 3:
return new Vector3(value[0], value[1], value[2]);
case 4:
return new Vector4(value[0], value[1], value[2], value[3]);
}
}
const tmpVec3 = new Vector3();
const tmpN3: Number3 = [0, 0, 0];
type OnAttribChange<T extends ParamConvertibleJsType> = (
newValue: JsIConnectionPointTypeToDataTypeMap[T],
oldValue: JsIConnectionPointTypeToDataTypeMap[T]
) => void;
const _relatedPrimitiveIds: number[] = [];
const _relatedVertexIds: number[] = [];
export abstract class BaseCoreObject<T extends CoreObjectType> extends CoreEntityWithObject<T> {
dispose() {}
// set_index(i: number) {
// this._index = i;
// }
geometry(): ObjectGeometryMap[T] | null {
return this._object?.geometry || null; //(this._object as Mesh).geometry as BufferGeometry | null;
}
builder<T extends CoreObjectType>(): ObjectBuilder<T> | undefined {
return undefined;
}
static attributeRef<OT extends CoreObjectType, T extends ParamConvertibleJsType>(
object: ObjectContent<OT>,
attribName: string,
type: T,
defaultValue: JsIConnectionPointTypeToDataTypeMap[T]
) {
return getOrCreateObjectAttributeRef(object, attribName, type, defaultValue);
}
attributeRef<T extends ParamConvertibleJsType>(
attribName: string,
type: T,
defaultValue: JsIConnectionPointTypeToDataTypeMap[T]
) {
if (!this._object) {
return;
}
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).attributeRef(
this._object,
attribName,
type,
defaultValue
);
}
static onAttribChange<OT extends CoreObjectType, T extends ParamConvertibleJsType>(
object: ObjectContent<OT>,
attribName: string,
type: T,
defaultValue: JsIConnectionPointTypeToDataTypeMap[T],
callback: OnAttribChange<T>
) {
const ref = this.attributeRef(object, attribName, type, defaultValue);
return watch(ref.current, callback);
}
onAttribChange<T extends ParamConvertibleJsType>(
attribName: string,
type: T,
defaultValue: JsIConnectionPointTypeToDataTypeMap[T],
callback: OnAttribChange<T>
) {
if (!this._object) {
return;
}
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).onAttribChange(
this._object,
attribName,
type,
defaultValue,
callback
);
}
static setAttribute<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string, value: AttribValue) {
this.addAttribute(object, attribName, value);
}
static addAttribute<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string, value: AttribValue) {
if (isArray(value)) {
const convertedValue = _convertArrayToVector(value);
if (!convertedValue) {
const message = `value invalid`;
console.error(message, value);
throw new Error(message);
}
}
const dict = this._attributesDictionary(object);
const currentValue = dict[attribName];
if (attribValueNonPrimitive(value)) {
if (currentValue == null) {
const cloned = cloneAttribValue(value);
if (cloned) {
dict[attribName] = cloned;
}
} else {
if (attribValueNonPrimitive(currentValue)) {
copyAttribValue(value, currentValue);
}
}
} else {
dict[attribName] = value;
}
_updateObjectAttribRef(object, attribName, value);
}
static addNumericAttribute<T extends CoreObjectType>(
object: ObjectContent<T>,
attribName: string,
size: AttribSize = 1,
defaultValue: NumericAttribValue = 0
) {
this.addAttribute(object, attribName, defaultValue);
}
addAttribute(name: string, value: AttribValue) {
if (!this._object) {
return;
}
(this.constructor as any as typeof BaseCoreObject<CoreObjectType>).addAttribute(this._object, name, value);
}
addNumericAttrib(name: string, value: NumericAttribValue) {
if (!this._object) {
return;
}
(this.constructor as any as typeof BaseCoreObject<CoreObjectType>).addNumericAttribute(
this._object,
name,
1,
value
);
}
setAttribValue(name: string, value: AttribValue) {
this.addAttribute(name, value);
}
// addNumericVertexAttrib(name: string, size: number, defaultValue: NumericAttribValue) {
// // if (defaultValue == null) {
// // defaultValue = CoreAttribute.default_value(size);
// // }
// // this.coreGeometry()?.addNumericAttrib(name, size, defaultValue);
// }
protected static _attributesDictionary<T extends CoreObjectType>(object: ObjectContent<T>) {
return (object.userData[ATTRIBUTES] as AttributeDictionary) || this._createAttributesDictionaryIfNone(object);
}
static attributesDictionaryEntry<T extends CoreObjectType>(
object: ObjectContent<T>,
attribName: string,
defaultValue?: AttribValue
) {
const dict =
(object.userData[ATTRIBUTES] as AttributeDictionary) || this._createAttributesDictionaryIfNone(object);
let entry: AttribValue | undefined = dict[attribName];
if (entry == null && defaultValue != null) {
entry = defaultValue;
dict[attribName] = entry;
}
return entry;
}
// static attributesPreviousValuesDictionary<T extends CoreObjectType>(object: ObjectContent<T>) {
// return (
// (object.userData[ATTRIBUTES_PREVIOUS_VALUES] as AttributeDictionary) ||
// this._createAttributesPreviousValuesDictionaryIfNone(object)
// );
// }
private static _createAttributesDictionaryIfNone<T extends CoreObjectType>(object: ObjectContent<T>) {
if (!object.userData[ATTRIBUTES]) {
return (object.userData[ATTRIBUTES] = {});
}
}
// private static _createAttributesPreviousValuesDictionaryIfNone<T extends CoreObjectType>(object: ObjectContent<T>) {
// if (!object.userData[ATTRIBUTES_PREVIOUS_VALUES]) {
// return (object.userData[ATTRIBUTES_PREVIOUS_VALUES] = {});
// }
// }
private _attributesDictionary() {
return (this.constructor as typeof BaseCoreObject<CoreObjectType>)._attributesDictionary(this._object);
}
static attributes<T extends CoreObjectType>(object: ObjectContent<T>): AttributeDictionary | undefined {
return this._attributesDictionary(object);
}
attributes(): AttributeDictionary | undefined {
if (!this._object) {
return;
}
return (this.constructor as typeof BaseCoreObject<T>).attributes(this._object);
}
attributeNames(): string[] {
return this.attribNames();
}
static attribNames<T extends CoreObjectType>(object: ObjectContent<T>): string[] {
return Object.keys(this._attributesDictionary(object));
}
attribNames(): string[] {
return (this.constructor as typeof BaseCoreObject<CoreObjectType>).attribNames(this._object);
}
static hasAttribute<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string) {
return attribName in this._attributesDictionary(object);
}
hasAttribute(attribName: string): boolean {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).hasAttribute(
this._object,
attribName
);
}
static attributeNames<T extends CoreObjectType>(object: ObjectContent<T>): string[] {
const attributes = this.attributes(object);
if (!attributes) {
return [];
}
return Object.keys(attributes);
}
static attributeNamesMatchingMask<T extends CoreObjectType>(object: ObjectContent<T>, masksString: GroupString) {
return CoreAttribute.attribNamesMatchingMask(masksString, this.attributeNames(object));
}
renameAttribute(oldName: string, newName: string) {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).renameAttribute(
this._object,
oldName,
newName
);
}
static renameAttribute<T extends CoreObjectType>(object: ObjectContent<T>, oldName: string, newName: string) {
const currentValue = this.attribValue(object, oldName);
if (currentValue != null) {
this.addAttribute(object, newName, currentValue);
this.deleteAttribute(object, oldName);
} else {
console.warn(`attribute ${oldName} not found`);
}
}
deleteAttribute(name: string) {
delete this._attributesDictionary()[name];
}
static deleteAttribute<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string) {
delete this._attributesDictionary(object)[attribName];
}
// static position:PositionStaticMethod<CoreObjectType> = DEFAULT_POSITION_STATIC_METHOD
static position(object: ObjectContent<CoreObjectType>, target: Vector3) {
target.copy(ORIGIN);
}
position(target: Vector3): Vector3 {
(this.constructor as typeof BaseCoreObject<CoreObjectType>).position(this._object, target);
return target;
}
static boundingBox(object: ObjectContent<CoreObjectType>, target: Box3) {
target.makeEmpty();
}
boundingBox(target: Box3) {
(this.constructor as typeof BaseCoreObject<CoreObjectType>).boundingBox(this._object, target);
}
static geometryBoundingBox(object: ObjectContent<CoreObjectType>, target: Box3) {
this.boundingBox(object, target);
}
geometryBoundingBox(target: Box3) {
(this.constructor as typeof BaseCoreObject<CoreObjectType>).geometryBoundingBox(this._object, target);
}
static boundingSphere(object: ObjectContent<CoreObjectType>, target: Sphere) {
target.makeEmpty();
}
boundingSphere(target: Sphere) {
(this.constructor as typeof BaseCoreObject<CoreObjectType>).boundingSphere(this._object, target);
}
static geometryBoundingSphere(object: ObjectContent<CoreObjectType>, target: Sphere) {
target.makeEmpty();
}
geometryBoundingSphere(target: Sphere) {
(this.constructor as typeof BaseCoreObject<CoreObjectType>).geometryBoundingSphere(this._object, target);
}
static attribValue<T extends CoreObjectType>(
object: ObjectContent<T>,
attribName: string,
index: number = 0,
target?: Color | Vector2 | Vector3 | Vector4
): AttribValue | undefined {
const _attribFromProperty = () => {
if (attribName == PropertyName.NAME) {
return object.name;
}
if (attribName == PropertyName.POSITION) {
const _target = target instanceof Vector3 ? target : tmpVec3;
this.position(object, _target);
_target.toArray(tmpN3);
return tmpN3;
}
};
if (attribName === Attribute.OBJECT_INDEX) {
return index;
}
if (attribName === Attribute.OBJECT_NAME) {
return object.name;
}
if (object.userData) {
const val = this.attributesDictionaryEntry(object, attribName);
// const val = attribRef.value; //dict[attribName];
if (val == null) {
return _attribFromProperty();
} else {
// const val = _ref.value;
if (isVector(val) && target) {
if (val instanceof Vector2 && target instanceof Vector2) {
return target.copy(val);
}
if (val instanceof Vector3 && target instanceof Vector3) {
return target.copy(val);
}
if (val instanceof Vector4 && target instanceof Vector4) {
return target.copy(val);
}
}
if (isColor(val) && target) {
if (val instanceof Color && target instanceof Color) {
return target.copy(val);
}
}
if (isArray(val) && target) {
target.fromArray(val);
return target;
}
}
// console.log(attribName, _ref, _ref.value);
return val; //_ref.value;
}
return _attribFromProperty();
}
// static previousAttribValue<T extends CoreObjectType>(
// object: ObjectContent<T>,
// attribName: string
// ): AttribValue | undefined {
// const dict = this.attributesPreviousValuesDictionary(object);
// return dict[attribName];
// }
static stringAttribValue<T extends CoreObjectType>(
object: ObjectContent<T>,
attribName: string,
index: number = 0
): string | null {
const str = this.attribValue(object, attribName, index);
if (str != null) {
if (isString(str)) {
return str;
} else {
return `${str}`;
}
}
return null;
}
// static makeAttribReactive<V extends AttribValue, T extends CoreObjectType>(
// object: ObjectContent<T>,
// attribName: string,
// callback: AttributeReactiveCallback<V>
// ) {
// const attributesDict = this.attributesDictionary(object);
// // const attributesPreviousValuesDict = this.attributesPreviousValuesDictionary(object);
// const currentValue = attributesDict[attribName];
// if (currentValue instanceof Vector4) {
// return makeAttribReactiveVector4(
// object,
// attribName,
// (<unknown>callback) as AttributeReactiveCallback<Vector4>
// );
// }
// if (currentValue instanceof Vector3) {
// return makeAttribReactiveVector3(
// object,
// attribName,
// (<unknown>callback) as AttributeReactiveCallback<Vector3>
// );
// }
// if (currentValue instanceof Vector2) {
// return makeAttribReactiveVector2(
// object,
// attribName,
// (<unknown>callback) as AttributeReactiveCallback<Vector2>
// );
// }
// return makeAttribReactiveSimple(
// object,
// attribName,
// (<unknown>callback) as AttributeReactiveCallback<string | number>
// );
// // // create a dummy val in case there is no attribute yet
// // if (attributesDict[attribName] == null) {
// // attributesDict[attribName] = 0;
// // }
// // const proxy: AttributeProxy<V> = {
// // value: attributesDict[attribName] as V,
// // previousValue: attributesDict[attribName] as V,
// // };
// // Object.defineProperties(attributesDict, {
// // [attribName]: {
// // get: function () {
// // return proxy.value;
// // },
// // set: function (x) {
// // if (x != proxy.value) {
// // proxy.previousValue = proxy.value;
// // proxy.value = x;
// // callback(proxy.value, proxy.previousValue);
// // }
// // return proxy.value;
// // },
// // configurable: true,
// // },
// // });
// // Object.defineProperties(attributesPreviousValuesDict, {
// // [attribName]: {
// // get: function () {
// // return proxy.previousValue;
// // },
// // configurable: true,
// // },
// // });
// }
attribValue(attribName: string, target?: Vector2 | Vector3 | Vector4): AttribValue | undefined {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).attribValue(
this._object,
attribName,
this._index,
target
);
}
stringAttribValue(name: string) {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).stringAttribValue(
this._object,
name,
this._index
);
}
name(): string {
return this.attribValue(PropertyName.NAME) as string;
}
humanType(): string {
return this._object.type;
}
attribTypes() {
const h: PolyDictionary<AttribType> = {};
for (const attrib_name of this.attribNames()) {
const type = this.attribType(attrib_name);
if (type != null) {
h[attrib_name] = type;
}
}
return h;
}
static attribType<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string) {
const val = this.attribValue(object, attribName);
if (isString(val)) {
return AttribType.STRING;
} else {
return AttribType.NUMERIC;
}
}
attribType(attribName: string) {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).attribType(this._object, attribName);
}
attribSizes() {
const h: PolyDictionary<AttribSize> = {};
const attribNames = this.attribNames();
for (const attribName of attribNames) {
const size = this.attribSize(attribName);
if (size != null) {
h[attribName] = size;
}
}
return h;
}
static attribSize<T extends CoreObjectType>(object: ObjectContent<T>, attribName: string): AttribSize | null {
const val = this.attribValue(object, attribName);
if (val == null) {
return null;
}
return CoreAttribute.attribSizeFromValue(val);
}
attribSize(attribName: string) {
return (this.constructor as any as typeof BaseCoreObject<CoreObjectType>).attribSize(this._object, attribName);
}
static objectData<T extends CoreObjectType>(object: ObjectContent<T>): ObjectData {
return objectData(object);
}
clone(): BaseCoreObject<T> {
const clonedObject = (this.constructor as typeof BaseCoreObject<CoreObjectType>).clone(this._object);
const cloned = new (this.constructor as any)(clonedObject, this._index);
return cloned;
}
static clone<T extends CoreObjectType>(srcObject: ObjectContent<T>): ObjectContent<T> {
return srcObject.clone() as ObjectContent<T>;
}
static applyMatrix(
object: ObjectContent<CoreObjectType>,
matrix: Matrix4,
transformTargetType: TransformTargetType,
transformSpace: ObjectTransformSpace,
transformMode: ObjectTransformMode
) {
console.warn('applyMatrix.override required', this);
}
static mergeCompact(options: MergeCompactOptions) {
console.warn('mergeCompact.override required', this);
}
//
//
// ENTITY GROUPS
//
//
groupCollection() {
return new EntityGroupCollection(this._object);
}
//
//
// RELATED ENTITIES
//
//
static override relatedVertexIds<T extends CoreObjectType>(
object: ObjectContent<T>,
objectIndex: number,
target: number[],
traversedRelatedEntityData?: TraversedRelatedEntityData
): void {
const ids = traversedRelatedEntityData
? traversedRelatedEntityData[AttribClass.PRIMITIVE].ids
: _relatedPrimitiveIds;
this.relatedPrimitiveIds(object, objectIndex, ids, traversedRelatedEntityData);
uniqRelatedEntityIds(
ids,
(primitiveId, relatedEntityIds) => {
this.relatedPrimitiveClass(object).relatedVertexIds(object, primitiveId, relatedEntityIds);
},
target
);
}
static override relatedPointIds<T extends CoreObjectType>(
object: ObjectContent<T>,
objectIndex: number,
target: number[],
traversedRelatedEntityData?: TraversedRelatedEntityData
): void {
const ids = traversedRelatedEntityData ? traversedRelatedEntityData[AttribClass.VERTEX].ids : _relatedVertexIds;
this.relatedVertexIds(object, objectIndex, ids, traversedRelatedEntityData);
uniqRelatedEntityIds(
ids,
(vertexId, relatedEntityIds) => {
this.relatedVertexClass(object).relatedPointIds(object, vertexId, relatedEntityIds);
},
target
);
}
relatedEntities(
attribClass: AttribClass,
coreGroup: CoreGroup,
target: CoreEntity[],
traversedRelatedEntityData?: TraversedRelatedEntityData
): void {
switch (attribClass) {
case AttribClass.POINT: {
this.relatedPoints(target as CorePoint<T>[], traversedRelatedEntityData);
return;
}
case AttribClass.VERTEX: {
this.relatedVertices(target as CoreVertex<T>[], traversedRelatedEntityData);
return;
}
case AttribClass.PRIMITIVE: {
this.relatedPrimitives(target as CorePrimitive<T>[], traversedRelatedEntityData);
return;
}
case AttribClass.OBJECT: {
target.length = 1;
target[0] = this;
return;
}
case AttribClass.CORE_GROUP: {
target.length = 1;
target[0] = coreGroup;
return;
}
}
TypeAssert.unreachable(attribClass);
}
static override relatedPointClass<T extends CoreObjectType>(object: ObjectContent<T>) {
return this.relatedVertexClass(object).relatedPointClass(object);
}
static override relatedVertexClass<T extends CoreObjectType>(object: ObjectContent<T>) {
return this.relatedPrimitiveClass(object).relatedVertexClass(object);
}
}