@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
603 lines (584 loc) • 18.4 kB
text/typescript
/**
* Creates object attributes used to create a physics object.
*
*/
import {StringParam} from './../../params/String';
import {TypeAssert} from './../../poly/Assert';
import {FloatParam} from './../../params/Float';
import {IntegerParam} from './../../params/Integer';
import {
PHYSICS_RBD_COLLIDER_TYPES,
PHYSICS_RBD_TYPES,
CorePhysicsAttribute,
PhysicsRBDColliderType,
PHYSICS_RBD_TYPE_MENU_ENTRIES,
PHYSICS_RBD_COLLIDER_TYPE_MENU_ENTRIES,
PhysicsRBDType,
} from './../../../core/physics/PhysicsAttribute';
import {TypedSopNode} from './_Base';
import {CoreGroup} from '../../../core/geometry/Group';
import {
PhysicsRBDAttributesSopOperation,
SIZE_COMPUTATION_METHOD_MENU_ENTRIES,
SizeComputationMethod,
SIZE_COMPUTATION_METHODS,
} from '../../operations/sop/PhysicsRBDAttributes';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {BaseCoreObject} from '../../../core/geometry/entities/object/BaseCoreObject';
import {Vector3, Box3, Sphere} from 'three';
import {Vector3Param} from '../../params/Vector3';
import {isBooleanTrue} from '../../../core/Type';
import {BooleanParam} from '../../params/Boolean';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {CoreObjectType, ObjectContent} from '../../../core/geometry/ObjectContent';
const tmpBox = new Box3();
const tmpSphere = new Sphere();
const DEFAULT = PhysicsRBDAttributesSopOperation.DEFAULT_PARAMS;
type Vector3Component = 'x' | 'y' | 'z';
const VECTOR3_COMPONENT_NAMES: Array<Vector3Component> = ['x', 'y', 'z'];
const tmpV3 = new Vector3();
const SIZE_METHOD_CUSTOM = {sizeMethod: SIZE_COMPUTATION_METHODS.indexOf(SizeComputationMethod.MANUAL)};
const VISIBLE_OPTIONS = {
CAPSULE: {
...SIZE_METHOD_CUSTOM,
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.CAPSULE),
},
CONE: {
...SIZE_METHOD_CUSTOM,
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.CONE),
},
CUBOID: {
...SIZE_METHOD_CUSTOM,
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.CUBOID),
},
CYLINDER: {
...SIZE_METHOD_CUSTOM,
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.CYLINDER),
},
SPHERE: {
...SIZE_METHOD_CUSTOM,
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.SPHERE),
},
HEIGHT_FIELD: {
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(PhysicsRBDColliderType.HEIGHT_FIELD),
},
};
const SIZE_METHOD_AVAILABLE: PhysicsRBDColliderType[] = [
PhysicsRBDColliderType.CAPSULE,
PhysicsRBDColliderType.CONE,
PhysicsRBDColliderType.CUBOID,
PhysicsRBDColliderType.CYLINDER,
PhysicsRBDColliderType.SPHERE,
];
export const BORDER_RADIUS_AVAILABLE: PhysicsRBDColliderType[] = [
PhysicsRBDColliderType.CONE,
PhysicsRBDColliderType.CUBOID,
PhysicsRBDColliderType.CYLINDER,
];
class PhysicsRBDAttributesSopParamsConfig extends NodeParamsConfig {
main = ParamConfig.FOLDER();
/** @param Rigid body type */
RBDType = ParamConfig.INTEGER(DEFAULT.RBDType, {
menu: {
entries: PHYSICS_RBD_TYPE_MENU_ENTRIES,
},
});
/** @param collider type */
colliderType = ParamConfig.INTEGER(DEFAULT.colliderType, {
menu: {
entries: PHYSICS_RBD_COLLIDER_TYPE_MENU_ENTRIES,
},
});
/** @param Rigid body type */
sizeMethod = ParamConfig.INTEGER(DEFAULT.sizeMethod, {
visibleIf: SIZE_METHOD_AVAILABLE.map((colliderType) => ({
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(colliderType),
})),
menu: {
entries: SIZE_COMPUTATION_METHOD_MENU_ENTRIES,
},
});
/** @param border radius */
borderRadius = ParamConfig.FLOAT(DEFAULT.borderRadius, {
visibleIf: BORDER_RADIUS_AVAILABLE.map((colliderType) => ({
colliderType: PHYSICS_RBD_COLLIDER_TYPES.indexOf(colliderType),
})),
expression: {forEntities: true},
});
/** @param sizes */
sizes = ParamConfig.VECTOR3(DEFAULT.sizes.toArray(), {
visibleIf: VISIBLE_OPTIONS.CUBOID,
expression: {forEntities: true},
});
/** @param sizes */
size = ParamConfig.FLOAT(DEFAULT.size, {
visibleIf: VISIBLE_OPTIONS.CUBOID,
expression: {forEntities: true},
});
/** @param radius */
radius = ParamConfig.FLOAT(DEFAULT.radius, {
range: [0, 1],
rangeLocked: [true, false],
visibleIf: [VISIBLE_OPTIONS.CAPSULE, VISIBLE_OPTIONS.CONE, VISIBLE_OPTIONS.CYLINDER, VISIBLE_OPTIONS.SPHERE],
expression: {forEntities: true},
});
/** @param half height */
height = ParamConfig.FLOAT(DEFAULT.height, {
range: [0, 1],
rangeLocked: [true, false],
visibleIf: [VISIBLE_OPTIONS.CAPSULE, VISIBLE_OPTIONS.CONE, VISIBLE_OPTIONS.CYLINDER],
expression: {forEntities: true},
});
/** @param heightField rows */
rows = ParamConfig.INTEGER(DEFAULT.rows, {
range: [1, 100],
rangeLocked: [true, false],
visibleIf: VISIBLE_OPTIONS.HEIGHT_FIELD,
expression: {forEntities: true},
});
/** @param heightField cols */
cols = ParamConfig.INTEGER(DEFAULT.cols, {
range: [1, 100],
rangeLocked: [true, false],
visibleIf: VISIBLE_OPTIONS.HEIGHT_FIELD,
expression: {forEntities: true},
});
/** @param density */
density = ParamConfig.FLOAT(DEFAULT.density, {
range: [0, 10],
rangeLocked: [true, false],
expression: {forEntities: true},
separatorBefore: true,
});
/** @param friction */
friction = ParamConfig.FLOAT(DEFAULT.friction, {
range: [0, 1],
rangeLocked: [true, false],
expression: {forEntities: true},
});
/** @param restitution */
restitution = ParamConfig.FLOAT(DEFAULT.restitution, {
range: [0, 2],
rangeLocked: [true, false],
expression: {forEntities: true},
});
/** @param linear damping (affects velocity) */
linearDamping = ParamConfig.FLOAT(DEFAULT.linearDamping, {
range: [0, 10],
rangeLocked: [true, false],
expression: {forEntities: true},
});
/** @param angular damping (affects rotations) */
angularDamping = ParamConfig.FLOAT(DEFAULT.angularDamping, {
range: [0, 10],
rangeLocked: [true, false],
expression: {forEntities: true},
});
/** @param linear velocity */
linearVelocity = ParamConfig.VECTOR3(DEFAULT.linearVelocity, {
expression: {forEntities: true},
});
/** @param angular velocity */
angularVelocity = ParamConfig.VECTOR3(DEFAULT.angularVelocity, {
expression: {forEntities: true},
});
/** @param gravity Scale */
gravityScale = ParamConfig.FLOAT(DEFAULT.gravityScale, {
range: [-10, 10],
rangeLocked: [false, false],
expression: {forEntities: true},
});
/** @param can sleep */
canSleep = ParamConfig.BOOLEAN(DEFAULT.canSleep, {
expression: {forEntities: true},
});
details = ParamConfig.FOLDER();
/** @param add id */
addId = ParamConfig.BOOLEAN(1);
/** @param id */
id = ParamConfig.STRING('`$OS`-`@objnum`', {
visibleIf: {addId: true},
expression: {forEntities: true},
});
}
const ParamsConfig = new PhysicsRBDAttributesSopParamsConfig();
export class PhysicsRBDAttributesSopNode extends TypedSopNode<PhysicsRBDAttributesSopParamsConfig> {
override readonly paramsConfig = ParamsConfig;
static override type() {
return SopType.PHYSICS_RBD_ATTRIBUTES;
}
protected override initializeNode() {
this.io.inputs.setCount(0, 1);
this.io.inputs.initInputsClonedState(PhysicsRBDAttributesSopOperation.INPUT_CLONED_STATE);
}
setRBDType(RBDtype: PhysicsRBDType) {
this.p.RBDType.set(PHYSICS_RBD_TYPES.indexOf(RBDtype));
}
RBDType() {
return PHYSICS_RBD_TYPES[this.pv.RBDType];
}
setColliderType(colliderType: PhysicsRBDColliderType) {
this.p.colliderType.set(PHYSICS_RBD_COLLIDER_TYPES.indexOf(colliderType));
}
colliderType() {
return PHYSICS_RBD_COLLIDER_TYPES[this.pv.colliderType];
}
setSizeMethod(sizeMethod: SizeComputationMethod) {
this.p.sizeMethod.set(SIZE_COMPUTATION_METHODS.indexOf(sizeMethod));
}
sizeMethod() {
return SIZE_COMPUTATION_METHODS[this.pv.sizeMethod];
}
override async cook(inputCoreGroups: CoreGroup[]) {
const coreGroup = inputCoreGroups[0];
const RBDType = this.RBDType();
const colliderType = this.colliderType();
const sizeMethod = this.sizeMethod();
const coreObjects = coreGroup.allCoreObjects();
for (const coreObject of coreObjects) {
CorePhysicsAttribute.setRBDType(coreObject.object(), RBDType);
CorePhysicsAttribute.setColliderType(coreObject.object(), colliderType);
}
const promises: Array<Promise<void>> = [];
this._applyColliderType(colliderType, sizeMethod, coreObjects, promises);
if (BORDER_RADIUS_AVAILABLE.includes(colliderType)) {
promises.push(
this._computeNumberParam(
this.p.borderRadius,
coreObjects,
CorePhysicsAttribute.setBorderRadius.bind(CorePhysicsAttribute)
)
);
}
promises.push(
this._computeNumberParam(
this.p.density,
coreObjects,
CorePhysicsAttribute.setDensity.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.friction,
coreObjects,
CorePhysicsAttribute.setFriction.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.linearDamping,
coreObjects,
CorePhysicsAttribute.setLinearDamping.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.angularDamping,
coreObjects,
CorePhysicsAttribute.setAngularDamping.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeVector3Param(
this.p.linearVelocity,
coreObjects,
CorePhysicsAttribute.setLinearVelocity.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeVector3Param(
this.p.angularVelocity,
coreObjects,
CorePhysicsAttribute.setAngularVelocity.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.gravityScale,
coreObjects,
CorePhysicsAttribute.setGravityScale.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.restitution,
coreObjects,
CorePhysicsAttribute.setRestitution.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeBooleanParam(
this.p.canSleep,
coreObjects,
CorePhysicsAttribute.setCanSleep.bind(CorePhysicsAttribute)
)
);
if (isBooleanTrue(this.pv.addId)) {
promises.push(
this._computeStringParam(
this.p.id,
coreObjects,
CorePhysicsAttribute.setRBDId.bind(CorePhysicsAttribute)
)
);
}
await Promise.all(promises);
// this._operation = this._operation || new PhysicsRBDAttributesSopOperation(this._scene, this.states);
// const core_group = this._operation.cook(input_contents, this.pv);
this.setCoreGroup(coreGroup);
}
protected _applyColliderType<T extends CoreObjectType>(
colliderType: PhysicsRBDColliderType,
sizeMethod: SizeComputationMethod,
coreObjects: BaseCoreObject<T>[], //BaseCoreObject<CoreObjectType>[],
promises: Array<Promise<void>>
) {
switch (colliderType) {
case PhysicsRBDColliderType.CUBOID: {
switch (sizeMethod) {
case SizeComputationMethod.AUTO: {
for (const coreObject of coreObjects) {
coreObject.geometryBoundingBox(tmpBox);
tmpBox.getSize(tmpV3);
CorePhysicsAttribute.setCuboidSizes(coreObject.object(), tmpV3);
CorePhysicsAttribute.setCuboidSize(coreObject.object(), 1);
}
return;
}
case SizeComputationMethod.MANUAL: {
promises.push(
this._computeVector3Param(
this.p.sizes,
coreObjects,
CorePhysicsAttribute.setCuboidSizes.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.size,
coreObjects,
CorePhysicsAttribute.setCuboidSize.bind(CorePhysicsAttribute)
)
);
return;
}
}
return;
}
case PhysicsRBDColliderType.SPHERE: {
switch (sizeMethod) {
case SizeComputationMethod.AUTO: {
for (const coreObject of coreObjects) {
coreObject.geometryBoundingSphere(tmpSphere);
const radius = tmpSphere.radius;
CorePhysicsAttribute.setRadius(coreObject.object(), radius);
}
return;
}
case SizeComputationMethod.MANUAL: {
promises.push(
this._computeNumberParam(
this.p.radius,
coreObjects,
CorePhysicsAttribute.setRadius.bind(CorePhysicsAttribute)
)
);
return;
}
}
return;
}
case PhysicsRBDColliderType.CAPSULE: {
switch (sizeMethod) {
case SizeComputationMethod.AUTO: {
for (const coreObject of coreObjects) {
coreObject.geometryBoundingBox(tmpBox);
tmpBox.getSize(tmpV3);
const radius = 0.5 * tmpV3.x;
const height = tmpV3.y - 2 * radius;
CorePhysicsAttribute.setHeight(coreObject.object(), height);
CorePhysicsAttribute.setRadius(coreObject.object(), radius);
}
return;
}
case SizeComputationMethod.MANUAL: {
promises.push(
this._computeNumberParam(
this.p.height,
coreObjects,
CorePhysicsAttribute.setHeight.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.radius,
coreObjects,
CorePhysicsAttribute.setRadius.bind(CorePhysicsAttribute)
)
);
return;
}
}
return;
}
case PhysicsRBDColliderType.CONE:
case PhysicsRBDColliderType.CYLINDER: {
switch (sizeMethod) {
case SizeComputationMethod.AUTO: {
for (const coreObject of coreObjects) {
coreObject.geometryBoundingBox(tmpBox);
tmpBox.getSize(tmpV3);
CorePhysicsAttribute.setHeight(coreObject.object(), tmpV3.y);
CorePhysicsAttribute.setRadius(coreObject.object(), 0.5 * tmpV3.x);
}
return;
}
case SizeComputationMethod.MANUAL: {
promises.push(
this._computeNumberParam(
this.p.height,
coreObjects,
CorePhysicsAttribute.setHeight.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.radius,
coreObjects,
CorePhysicsAttribute.setRadius.bind(CorePhysicsAttribute)
)
);
return;
}
}
return;
}
case PhysicsRBDColliderType.HEIGHT_FIELD: {
promises.push(
this._computeNumberParam(
this.p.rows,
coreObjects,
CorePhysicsAttribute.setHeightFieldRows.bind(CorePhysicsAttribute)
)
);
promises.push(
this._computeNumberParam(
this.p.cols,
coreObjects,
CorePhysicsAttribute.setHeightFieldCols.bind(CorePhysicsAttribute)
)
);
return;
}
case PhysicsRBDColliderType.CONVEX_HULL:
case PhysicsRBDColliderType.TRIMESH: {
return;
}
}
TypeAssert.unreachable(colliderType);
}
protected async _computeStringParam<T extends CoreObjectType>(
param: StringParam,
coreObjects: BaseCoreObject<T>[],
applyMethod: (object: ObjectContent<CoreObjectType>, value: string) => void
) {
if (param.expressionController && param.expressionController.entitiesDependent()) {
await param.expressionController.computeExpressionForObjects(coreObjects, (coreObject, value: string) => {
applyMethod(coreObject.object(), value);
});
} else {
for (const coreObject of coreObjects) {
applyMethod(coreObject.object(), param.value);
}
}
}
protected async _computeVector3Param(
vectorParam: Vector3Param,
coreObjects: BaseCoreObject<CoreObjectType>[],
applyMethod: (object: ObjectContent<CoreObjectType>, value: Vector3) => void
) {
// if (param.expressionController && param.expressionController.entitiesDependent()) {
// await param.expressionController.computeExpressionForObjects(coreObjects, (coreObject, value) => {
// applyMethod(coreObject.object(), value);
// });
// } else {
// for (let coreObject of coreObjects) {
// applyMethod(coreObject.object(), param.value);
// }
// }
const components = vectorParam.components;
const valuesByCoreObjectIndex: Map<number, Vector3> = new Map();
// for (let component_param of components) {
// values.push(component_param.value);
// }
// const initVector = this._vectorByAttribSize(this.pv.size);
// if (initVector) {
for (const coreObject of coreObjects) {
valuesByCoreObjectIndex.set(coreObject.index(), new Vector3());
}
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
const component_param = components[componentIndex];
const component_name = VECTOR3_COMPONENT_NAMES[componentIndex];
if (
component_param.hasExpression() &&
component_param.expressionController &&
component_param.expressionController.entitiesDependent()
) {
await component_param.expressionController.computeExpressionForObjects(
coreObjects,
(coreObject, value) => {
const vector = valuesByCoreObjectIndex.get(coreObject.index());
if (vector) {
vector[component_name] = value;
}
}
);
} else {
for (const coreObject of coreObjects) {
const vector = valuesByCoreObjectIndex.get(coreObject.index());
if (vector) {
vector[component_name] = component_param.value;
}
}
}
}
for (let i = 0; i < coreObjects.length; i++) {
const coreObject = coreObjects[i];
const value = valuesByCoreObjectIndex.get(coreObject.index());
if (value != null) {
applyMethod(coreObject.object(), value);
}
}
// }
}
protected async _computeNumberParam(
param: FloatParam | IntegerParam,
coreObjects: BaseCoreObject<CoreObjectType>[],
applyMethod: (object: ObjectContent<CoreObjectType>, value: number) => void
) {
if (param.expressionController && param.expressionController.entitiesDependent()) {
await param.expressionController.computeExpressionForObjects(coreObjects, (coreObject, value) => {
applyMethod(coreObject.object(), value);
});
} else {
for (const coreObject of coreObjects) {
applyMethod(coreObject.object(), param.value);
}
}
}
protected async _computeBooleanParam(
param: BooleanParam,
coreObjects: BaseCoreObject<CoreObjectType>[],
applyMethod: (object: ObjectContent<CoreObjectType>, value: boolean) => void
) {
if (param.expressionController && param.expressionController.entitiesDependent()) {
await param.expressionController.computeExpressionForObjects(coreObjects, (coreObject, value) => {
applyMethod(coreObject.object(), value);
});
} else {
for (const coreObject of coreObjects) {
applyMethod(coreObject.object(), param.value);
}
}
}
}