mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00

* New actions are available to save & load the game state, making Saving & Loading as easy as adding 1 action to your game! * While it will work in most cases, it has a few limitations and hasn't been thoroughly tested on all types of objects/behaviors and games, so it is for the moment tagged as **Experimental** while we gather feedback and improve it * Check out the wiki for more info: https://wiki.gdevelop.io/gdevelop5/all-features/save-state
2068 lines
62 KiB
TypeScript
2068 lines
62 KiB
TypeScript
/// <reference path="./jolt-physics.d.ts" />
|
|
|
|
namespace Jolt {
|
|
export interface Body {
|
|
gdjsAssociatedBehavior: gdjs.Physics3DRuntimeBehavior | null;
|
|
}
|
|
}
|
|
|
|
namespace gdjs {
|
|
const loadJolt = async () => {
|
|
try {
|
|
const module = await import('./jolt-physics.wasm.js');
|
|
const initializeJoltPhysics = module.default;
|
|
if (!initializeJoltPhysics) {
|
|
throw new Error('No default export found in Jolt.');
|
|
}
|
|
|
|
const Jolt = await initializeJoltPhysics();
|
|
window.Jolt = Jolt;
|
|
} catch (err) {
|
|
console.error('Unable to load Jolt physics library.', err);
|
|
throw err;
|
|
}
|
|
};
|
|
gdjs.registerAsynchronouslyLoadingLibraryPromise(loadJolt());
|
|
|
|
export interface RuntimeScene {
|
|
physics3DSharedData: gdjs.Physics3DSharedData | null;
|
|
}
|
|
interface Physics3DNetworkSyncDataType {
|
|
px: number | undefined;
|
|
py: number | undefined;
|
|
pz: number | undefined;
|
|
rx: number | undefined;
|
|
ry: number | undefined;
|
|
rz: number | undefined;
|
|
rw: number | undefined;
|
|
lvx: number | undefined;
|
|
lvy: number | undefined;
|
|
lvz: number | undefined;
|
|
avx: number | undefined;
|
|
avy: number | undefined;
|
|
avz: number | undefined;
|
|
aw: boolean | undefined;
|
|
layers: number;
|
|
masks: number;
|
|
}
|
|
|
|
export interface Physics3DNetworkSyncData extends BehaviorNetworkSyncData {
|
|
props: Physics3DNetworkSyncDataType;
|
|
}
|
|
|
|
export class Physics3DSharedData {
|
|
gravityX: float;
|
|
gravityY: float;
|
|
gravityZ: float;
|
|
worldScale: float;
|
|
worldInvScale: float;
|
|
|
|
jolt: Jolt.JoltInterface;
|
|
physicsSystem: Jolt.PhysicsSystem;
|
|
bodyInterface: Jolt.BodyInterface;
|
|
/** Contact listener to keep track of current collisions */
|
|
contactListener: Jolt.ContactListenerJS;
|
|
/** Avoid creating new vectors all the time */
|
|
_tempVec3 = new Jolt.Vec3();
|
|
_tempRVec3 = new Jolt.RVec3();
|
|
_tempQuat = new Jolt.Quat();
|
|
|
|
stepped: boolean = false;
|
|
/**
|
|
* List of physics behavior in the runtimeScene. It should be updated
|
|
* when a new physics object is created (constructor), on destruction (onDestroy),
|
|
* on behavior activation (onActivate) and on behavior deactivation (onDeActivate).
|
|
*/
|
|
_registeredBehaviors: Set<Physics3DRuntimeBehavior>;
|
|
|
|
private _physics3DHooks: Array<gdjs.Physics3DRuntimeBehavior.Physics3DHook> =
|
|
[];
|
|
|
|
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, sharedData) {
|
|
this._registeredBehaviors = new Set<Physics3DRuntimeBehavior>();
|
|
this.gravityX = sharedData.gravityX;
|
|
this.gravityY = sharedData.gravityY;
|
|
this.gravityZ = sharedData.gravityZ;
|
|
this.worldScale = sharedData.worldScale;
|
|
this.worldInvScale = 1 / this.worldScale;
|
|
|
|
// Initialize Jolt
|
|
const settings = new Jolt.JoltSettings();
|
|
gdjs.Physics3DSharedData.setupCollisionFiltering(settings);
|
|
this.jolt = new Jolt.JoltInterface(settings);
|
|
Jolt.destroy(settings);
|
|
this.physicsSystem = this.jolt.GetPhysicsSystem();
|
|
this.physicsSystem.SetGravity(
|
|
this.getVec3(this.gravityX, this.gravityY, this.gravityZ)
|
|
);
|
|
this.bodyInterface = this.physicsSystem.GetBodyInterface();
|
|
|
|
this.contactListener = new Jolt.ContactListenerJS();
|
|
this.physicsSystem.SetContactListener(this.contactListener);
|
|
this.contactListener.OnContactAdded = (
|
|
bodyPtrA: number,
|
|
bodyPtrB: number,
|
|
manifoldPtr: number,
|
|
settingsPtr: number
|
|
): void => {
|
|
const bodyA = Jolt.wrapPointer(bodyPtrA, Jolt.Body);
|
|
const bodyB = Jolt.wrapPointer(bodyPtrB, Jolt.Body);
|
|
|
|
const behaviorA = bodyA.gdjsAssociatedBehavior;
|
|
const behaviorB = bodyB.gdjsAssociatedBehavior;
|
|
if (!behaviorA || !behaviorB) {
|
|
return;
|
|
}
|
|
|
|
behaviorA.onContactBegin(behaviorB);
|
|
behaviorB.onContactBegin(behaviorA);
|
|
};
|
|
this.contactListener.OnContactRemoved = (
|
|
subShapePairPtr: number
|
|
): void => {
|
|
const subShapePair = Jolt.wrapPointer(
|
|
subShapePairPtr,
|
|
Jolt.SubShapeIDPair
|
|
);
|
|
|
|
// This is ok because bodies are not deleted during the Physics step.
|
|
const bodyLockInterface = this.physicsSystem.GetBodyLockInterface();
|
|
const bodyA = bodyLockInterface.TryGetBody(subShapePair.GetBody1ID());
|
|
const bodyB = bodyLockInterface.TryGetBody(subShapePair.GetBody2ID());
|
|
|
|
const behaviorA = bodyA.gdjsAssociatedBehavior;
|
|
const behaviorB = bodyB.gdjsAssociatedBehavior;
|
|
if (!behaviorA || !behaviorB) {
|
|
return;
|
|
}
|
|
|
|
behaviorA.onContactEnd(behaviorB);
|
|
behaviorB.onContactEnd(behaviorA);
|
|
};
|
|
this.contactListener.OnContactPersisted = (
|
|
bodyPtrA: number,
|
|
bodyPtrB: number,
|
|
manifoldPtr: number,
|
|
settingsPtr: number
|
|
): void => {
|
|
// TODO we could rely on this event.
|
|
};
|
|
this.contactListener.OnContactValidate = (
|
|
bodyPtrA: number,
|
|
bodyPtrB: number,
|
|
inBaseOffset: number,
|
|
inCollisionResult: number
|
|
): number => {
|
|
return Jolt.ValidateResult_AcceptAllContactsForThisBodyPair;
|
|
};
|
|
}
|
|
|
|
getVec3(x: float, y: float, z: float): Jolt.Vec3 {
|
|
const tempVec3 = this._tempVec3;
|
|
tempVec3.Set(x, y, z);
|
|
return tempVec3;
|
|
}
|
|
|
|
getRVec3(x: float, y: float, z: float): Jolt.RVec3 {
|
|
const tempRVec3 = this._tempRVec3;
|
|
tempRVec3.Set(x, y, z);
|
|
return tempRVec3;
|
|
}
|
|
|
|
getQuat(x: float, y: float, z: float, w: float): Jolt.Quat {
|
|
const tempQuat = this._tempQuat;
|
|
tempQuat.Set(x, y, z, w);
|
|
return tempQuat;
|
|
}
|
|
|
|
static getSharedData(
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
behaviorName: string
|
|
): gdjs.Physics3DSharedData {
|
|
if (!runtimeScene.physics3DSharedData) {
|
|
const initialData =
|
|
runtimeScene.getInitialSharedDataForBehavior(behaviorName);
|
|
runtimeScene.physics3DSharedData = new gdjs.Physics3DSharedData(
|
|
runtimeScene,
|
|
initialData
|
|
);
|
|
}
|
|
return runtimeScene.physics3DSharedData;
|
|
}
|
|
|
|
// There are 4 bits for static layers and 4 bits for dynamic layers.
|
|
static readonly staticLayersMask = 0x0f;
|
|
static readonly dynamicLayersMask = 0xf0;
|
|
static readonly allLayersMask = 0xff;
|
|
static readonly staticBroadPhaseLayerIndex = 1;
|
|
static readonly dynamicBroadPhaseLayerIndex = 1;
|
|
|
|
private static setupCollisionFiltering(settings: Jolt.JoltSettings): void {
|
|
const objectFilter = new Jolt.ObjectLayerPairFilterMask();
|
|
const staticBroadPhaseLayer = new Jolt.BroadPhaseLayer(
|
|
gdjs.Physics3DSharedData.staticBroadPhaseLayerIndex
|
|
);
|
|
const dynamicBroadPhaseLayer = new Jolt.BroadPhaseLayer(
|
|
gdjs.Physics3DSharedData.dynamicBroadPhaseLayerIndex
|
|
);
|
|
const broadPhaseLayerInterfaceMask =
|
|
new Jolt.BroadPhaseLayerInterfaceMask(2);
|
|
broadPhaseLayerInterfaceMask.ConfigureLayer(
|
|
staticBroadPhaseLayer,
|
|
gdjs.Physics3DSharedData.staticLayersMask,
|
|
0
|
|
);
|
|
broadPhaseLayerInterfaceMask.ConfigureLayer(
|
|
dynamicBroadPhaseLayer,
|
|
gdjs.Physics3DSharedData.dynamicLayersMask,
|
|
0
|
|
);
|
|
// BroadPhaseLayer have been copied into bpInterface
|
|
Jolt.destroy(staticBroadPhaseLayer);
|
|
Jolt.destroy(dynamicBroadPhaseLayer);
|
|
|
|
settings.mObjectLayerPairFilter = objectFilter;
|
|
settings.mBroadPhaseLayerInterface = broadPhaseLayerInterfaceMask;
|
|
settings.mObjectVsBroadPhaseLayerFilter =
|
|
new Jolt.ObjectVsBroadPhaseLayerFilterMask(
|
|
broadPhaseLayerInterfaceMask
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add a physics object to the list of existing object.
|
|
*/
|
|
addToBehaviorsList(physicsBehavior: gdjs.Physics3DRuntimeBehavior): void {
|
|
this._registeredBehaviors.add(physicsBehavior);
|
|
}
|
|
|
|
/**
|
|
* Remove a physics object to the list of existing object.
|
|
*/
|
|
removeFromBehaviorsList(
|
|
physicsBehavior: gdjs.Physics3DRuntimeBehavior
|
|
): void {
|
|
this._registeredBehaviors.delete(physicsBehavior);
|
|
}
|
|
|
|
step(deltaTime: float): void {
|
|
for (const physicsBehavior of this._registeredBehaviors) {
|
|
physicsBehavior._contactsStartedThisFrame.length = 0;
|
|
physicsBehavior._contactsEndedThisFrame.length = 0;
|
|
}
|
|
for (const physicsBehavior of this._registeredBehaviors) {
|
|
physicsBehavior.updateBodyFromObject();
|
|
}
|
|
for (const physics3DHook of this._physics3DHooks) {
|
|
physics3DHook.doBeforePhysicsStep(deltaTime);
|
|
}
|
|
|
|
const numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1;
|
|
this.jolt.Step(deltaTime, numSteps);
|
|
this.stepped = true;
|
|
|
|
// It's important that updateBodyFromObject and updateObjectFromBody are
|
|
// called at the same time because other behavior may move the object in
|
|
// their doStepPreEvents.
|
|
for (const physicsBehavior of this._registeredBehaviors) {
|
|
physicsBehavior.updateObjectFromBody();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A hook must typically be registered by a behavior that requires this one
|
|
* in its onCreate function.
|
|
* The hook must stay forever to avoid side effects like a hooks order
|
|
* change. To handle deactivated behavior, the hook can check that its
|
|
* behavior is activated before doing anything.
|
|
*/
|
|
registerHook(hook: gdjs.Physics3DRuntimeBehavior.Physics3DHook) {
|
|
this._physics3DHooks.push(hook);
|
|
}
|
|
}
|
|
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
|
|
const physics3DSharedData = runtimeScene.physics3DSharedData;
|
|
if (physics3DSharedData) {
|
|
Jolt.destroy(physics3DSharedData.contactListener);
|
|
Jolt.destroy(physics3DSharedData._tempVec3);
|
|
Jolt.destroy(physics3DSharedData._tempRVec3);
|
|
Jolt.destroy(physics3DSharedData._tempQuat);
|
|
Jolt.destroy(physics3DSharedData.jolt);
|
|
runtimeScene.physics3DSharedData = null;
|
|
}
|
|
});
|
|
|
|
export class Physics3DRuntimeBehavior extends gdjs.RuntimeBehavior {
|
|
bodyUpdater: gdjs.Physics3DRuntimeBehavior.BodyUpdater;
|
|
collisionChecker: gdjs.Physics3DRuntimeBehavior.CollisionChecker;
|
|
owner3D: gdjs.RuntimeObject3D;
|
|
|
|
bodyType: string;
|
|
bullet: boolean;
|
|
fixedRotation: boolean;
|
|
private shape: string;
|
|
private shapeOrientation: string;
|
|
private shapeDimensionA: float;
|
|
private shapeDimensionB: float;
|
|
private shapeDimensionC: float;
|
|
private shapeOffsetX: float;
|
|
private shapeOffsetY: float;
|
|
shapeOffsetZ: float;
|
|
private massCenterOffsetX: float;
|
|
private massCenterOffsetY: float;
|
|
private massCenterOffsetZ: float;
|
|
private density: float;
|
|
massOverride: float;
|
|
friction: float;
|
|
restitution: float;
|
|
linearDamping: float;
|
|
angularDamping: float;
|
|
gravityScale: float;
|
|
private layers: integer;
|
|
private masks: integer;
|
|
shapeScale: number = 1;
|
|
|
|
/**
|
|
* Array containing the beginning of contacts reported by onContactBegin. Each contact
|
|
* should be unique to avoid recording glitches where the object loses and regain
|
|
* contact between two frames. The array is updated each time the method
|
|
* onContactBegin is called by the listener, which is only called when stepping
|
|
* the world i.e. in the first preEvent called by a physics behavior. This array is
|
|
* cleared just before stepping the world.
|
|
*/
|
|
_contactsStartedThisFrame: Array<Physics3DRuntimeBehavior> = [];
|
|
|
|
/**
|
|
* Array containing the end of contacts reported by onContactEnd. The array is updated
|
|
* each time the method onContactEnd is called by the listener, which can be called at
|
|
* any time. This array is cleared just before stepping the world.
|
|
*/
|
|
_contactsEndedThisFrame: Array<Physics3DRuntimeBehavior> = [];
|
|
|
|
/**
|
|
* Array containing the exact current contacts with the objects. It is updated
|
|
* each time the methods onContactBegin and onContactEnd are called by the contact
|
|
* listener.
|
|
*/
|
|
_currentContacts: Array<Physics3DRuntimeBehavior> = [];
|
|
|
|
private _destroyedDuringFrameLogic: boolean = false;
|
|
_body: Jolt.Body | null = null;
|
|
/**
|
|
* When set to `true` the body will be recreated before the next physics step.
|
|
*/
|
|
private _needToRecreateBody: boolean = false;
|
|
/**
|
|
* When set to `true` the shape will be recreated before the next physics step.
|
|
*/
|
|
_needToRecreateShape: boolean = false;
|
|
|
|
_shapeHalfWidth: float = 0;
|
|
_shapeHalfHeight: float = 0;
|
|
/**
|
|
* Used by {@link gdjs.PhysicsCharacter3DRuntimeBehavior} to convert coordinates.
|
|
*/
|
|
_shapeHalfDepth: float = 0;
|
|
|
|
/**
|
|
* sharedData is a reference to the shared data of the scene, that registers
|
|
* every physics behavior that is created so that collisions can be cleared
|
|
* before stepping the world.
|
|
*/
|
|
_sharedData: Physics3DSharedData;
|
|
|
|
_objectOldX: float = 0;
|
|
_objectOldY: float = 0;
|
|
_objectOldZ: float = 0;
|
|
_objectOldRotationX: float = 0;
|
|
_objectOldRotationY: float = 0;
|
|
_objectOldRotationZ: float = 0;
|
|
_objectOldWidth: float = 0;
|
|
_objectOldHeight: float = 0;
|
|
_objectOldDepth: float = 0;
|
|
|
|
constructor(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer,
|
|
behaviorData,
|
|
owner: gdjs.RuntimeObject3D
|
|
) {
|
|
super(instanceContainer, behaviorData, owner);
|
|
this.bodyUpdater = new gdjs.Physics3DRuntimeBehavior.DefaultBodyUpdater(
|
|
this
|
|
);
|
|
this.collisionChecker =
|
|
new gdjs.Physics3DRuntimeBehavior.DefaultCollisionChecker(this);
|
|
this.owner3D = owner;
|
|
this.bodyType = behaviorData.bodyType;
|
|
this.bullet = behaviorData.bullet;
|
|
this.fixedRotation = behaviorData.fixedRotation;
|
|
this.shape = behaviorData.shape;
|
|
this.shapeOrientation =
|
|
behaviorData.shape === 'Box' ? 'Z' : behaviorData.shapeOrientation;
|
|
this.shapeDimensionA = behaviorData.shapeDimensionA;
|
|
this.shapeDimensionB = behaviorData.shapeDimensionB;
|
|
this.shapeDimensionC = behaviorData.shapeDimensionC;
|
|
this.shapeOffsetX = behaviorData.shapeOffsetX || 0;
|
|
this.shapeOffsetY = behaviorData.shapeOffsetY || 0;
|
|
this.shapeOffsetZ = behaviorData.shapeOffsetZ || 0;
|
|
this.massCenterOffsetX = behaviorData.massCenterOffsetX || 0;
|
|
this.massCenterOffsetY = behaviorData.massCenterOffsetY || 0;
|
|
this.massCenterOffsetZ = behaviorData.massCenterOffsetZ || 0;
|
|
this.density = behaviorData.density;
|
|
this.massOverride = behaviorData.massOverride || 0;
|
|
this.friction = behaviorData.friction;
|
|
this.restitution = behaviorData.restitution;
|
|
this.linearDamping = Math.max(0, behaviorData.linearDamping);
|
|
this.angularDamping = Math.max(0, behaviorData.angularDamping);
|
|
this.gravityScale = behaviorData.gravityScale;
|
|
this.layers = behaviorData.layers;
|
|
this.masks = behaviorData.masks;
|
|
this._sharedData = Physics3DSharedData.getSharedData(
|
|
instanceContainer.getScene(),
|
|
behaviorData.name
|
|
);
|
|
this._sharedData.addToBehaviorsList(this);
|
|
}
|
|
|
|
private getVec3(x: float, y: float, z: float): Jolt.Vec3 {
|
|
const tempVec3 = this._sharedData._tempVec3;
|
|
tempVec3.Set(x, y, z);
|
|
return tempVec3;
|
|
}
|
|
|
|
private getRVec3(x: float, y: float, z: float): Jolt.RVec3 {
|
|
const tempRVec3 = this._sharedData._tempRVec3;
|
|
tempRVec3.Set(x, y, z);
|
|
return tempRVec3;
|
|
}
|
|
|
|
private getQuat(x: float, y: float, z: float, w: float): Jolt.Quat {
|
|
const tempQuat = this._sharedData._tempQuat;
|
|
tempQuat.Set(x, y, z, w);
|
|
return tempQuat;
|
|
}
|
|
|
|
override updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
|
if (oldBehaviorData.bullet !== newBehaviorData.bullet) {
|
|
this.setBullet(newBehaviorData.bullet);
|
|
}
|
|
if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) {
|
|
this.setFixedRotation(newBehaviorData.fixedRotation);
|
|
}
|
|
if (oldBehaviorData.shapeDimensionA !== newBehaviorData.shapeDimensionA) {
|
|
this.shapeDimensionA = newBehaviorData.shapeDimensionA;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
if (oldBehaviorData.shapeDimensionB !== newBehaviorData.shapeDimensionB) {
|
|
this.shapeDimensionB = newBehaviorData.shapeDimensionB;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
if (oldBehaviorData.density !== newBehaviorData.density) {
|
|
this.setDensity(newBehaviorData.density);
|
|
}
|
|
if (oldBehaviorData.friction !== newBehaviorData.friction) {
|
|
this.setFriction(newBehaviorData.friction);
|
|
}
|
|
if (oldBehaviorData.restitution !== newBehaviorData.restitution) {
|
|
this.setRestitution(newBehaviorData.restitution);
|
|
}
|
|
if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) {
|
|
this.setLinearDamping(newBehaviorData.linearDamping);
|
|
}
|
|
if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) {
|
|
this.setAngularDamping(newBehaviorData.angularDamping);
|
|
}
|
|
if (oldBehaviorData.gravityScale !== newBehaviorData.gravityScale) {
|
|
this.setGravityScale(newBehaviorData.gravityScale);
|
|
}
|
|
|
|
// TODO: make these properties updatable.
|
|
if (oldBehaviorData.layers !== newBehaviorData.layers) {
|
|
return false;
|
|
}
|
|
if (oldBehaviorData.masks !== newBehaviorData.masks) {
|
|
return false;
|
|
}
|
|
if (oldBehaviorData.vertices !== newBehaviorData.vertices) {
|
|
return false;
|
|
}
|
|
if (oldBehaviorData.bodyType !== newBehaviorData.bodyType) {
|
|
return false;
|
|
}
|
|
if (oldBehaviorData.shape !== newBehaviorData.shape) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override getNetworkSyncData(
|
|
options: GetNetworkSyncDataOptions
|
|
): Physics3DNetworkSyncData {
|
|
let bodyProps;
|
|
if (this._body) {
|
|
const position = this._body.GetPosition();
|
|
const rotation = this._body.GetRotation();
|
|
const linearVelocity = this._body.GetLinearVelocity();
|
|
const angularVelocity = this._body.GetAngularVelocity();
|
|
bodyProps = {
|
|
px: position.GetX(),
|
|
py: position.GetY(),
|
|
pz: position.GetZ(),
|
|
rx: rotation.GetX(),
|
|
ry: rotation.GetY(),
|
|
rz: rotation.GetZ(),
|
|
rw: rotation.GetW(),
|
|
lvx: linearVelocity.GetX(),
|
|
lvy: linearVelocity.GetY(),
|
|
lvz: linearVelocity.GetZ(),
|
|
avx: angularVelocity.GetX(),
|
|
avy: angularVelocity.GetY(),
|
|
avz: angularVelocity.GetZ(),
|
|
aw: this._body.IsActive(),
|
|
};
|
|
} else {
|
|
bodyProps = {
|
|
px: undefined,
|
|
py: undefined,
|
|
pz: undefined,
|
|
rx: undefined,
|
|
ry: undefined,
|
|
rz: undefined,
|
|
rw: undefined,
|
|
lvx: undefined,
|
|
lvy: undefined,
|
|
lvz: undefined,
|
|
avx: undefined,
|
|
avy: undefined,
|
|
avz: undefined,
|
|
aw: undefined,
|
|
};
|
|
}
|
|
return {
|
|
...super.getNetworkSyncData(options),
|
|
props: {
|
|
...bodyProps,
|
|
layers: this.layers,
|
|
masks: this.masks,
|
|
},
|
|
};
|
|
}
|
|
|
|
override updateFromNetworkSyncData(
|
|
networkSyncData: Physics3DNetworkSyncData,
|
|
options: UpdateFromNetworkSyncDataOptions
|
|
) {
|
|
super.updateFromNetworkSyncData(networkSyncData, options);
|
|
|
|
const behaviorSpecificProps = networkSyncData.props;
|
|
|
|
if (behaviorSpecificProps.layers !== undefined) {
|
|
this.layers = behaviorSpecificProps.layers;
|
|
}
|
|
if (behaviorSpecificProps.masks !== undefined) {
|
|
this.masks = behaviorSpecificProps.masks;
|
|
}
|
|
|
|
this._needToRecreateShape = true;
|
|
this._needToRecreateBody = true;
|
|
this.updateBodyFromObject();
|
|
|
|
if (!this._body) return;
|
|
|
|
if (
|
|
behaviorSpecificProps.px !== undefined &&
|
|
behaviorSpecificProps.py !== undefined &&
|
|
behaviorSpecificProps.pz !== undefined
|
|
) {
|
|
this._sharedData.bodyInterface.SetPosition(
|
|
this._body.GetID(),
|
|
this.getRVec3(
|
|
behaviorSpecificProps.px,
|
|
behaviorSpecificProps.py,
|
|
behaviorSpecificProps.pz
|
|
),
|
|
Jolt.EActivation_DontActivate
|
|
);
|
|
}
|
|
if (
|
|
behaviorSpecificProps.rx !== undefined &&
|
|
behaviorSpecificProps.ry !== undefined &&
|
|
behaviorSpecificProps.rz !== undefined &&
|
|
behaviorSpecificProps.rw !== undefined
|
|
) {
|
|
this._sharedData.bodyInterface.SetRotation(
|
|
this._body.GetID(),
|
|
this.getQuat(
|
|
behaviorSpecificProps.rx,
|
|
behaviorSpecificProps.ry,
|
|
behaviorSpecificProps.rz,
|
|
behaviorSpecificProps.rw
|
|
),
|
|
Jolt.EActivation_DontActivate
|
|
);
|
|
}
|
|
if (
|
|
behaviorSpecificProps.lvx !== undefined &&
|
|
behaviorSpecificProps.lvy !== undefined &&
|
|
behaviorSpecificProps.lvz !== undefined
|
|
) {
|
|
this._sharedData.bodyInterface.SetLinearVelocity(
|
|
this._body.GetID(),
|
|
this.getVec3(
|
|
behaviorSpecificProps.lvx,
|
|
behaviorSpecificProps.lvy,
|
|
behaviorSpecificProps.lvz
|
|
)
|
|
);
|
|
}
|
|
if (
|
|
behaviorSpecificProps.avx !== undefined &&
|
|
behaviorSpecificProps.avy !== undefined &&
|
|
behaviorSpecificProps.avz !== undefined
|
|
) {
|
|
this._sharedData.bodyInterface.SetAngularVelocity(
|
|
this._body.GetID(),
|
|
this.getVec3(
|
|
behaviorSpecificProps.avx,
|
|
behaviorSpecificProps.avy,
|
|
behaviorSpecificProps.avz
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
override onDeActivate() {
|
|
this._sharedData.removeFromBehaviorsList(this);
|
|
this._destroyBody();
|
|
}
|
|
|
|
override onActivate() {
|
|
this._sharedData.addToBehaviorsList(this);
|
|
}
|
|
|
|
override onDestroy() {
|
|
this._destroyedDuringFrameLogic = true;
|
|
this.onDeActivate();
|
|
}
|
|
|
|
_destroyBody() {
|
|
this.bodyUpdater.destroyBody();
|
|
this._contactsEndedThisFrame.length = 0;
|
|
this._contactsStartedThisFrame.length = 0;
|
|
this._currentContacts.length = 0;
|
|
}
|
|
|
|
resetToDefaultBodyUpdater() {
|
|
this.bodyUpdater = new gdjs.Physics3DRuntimeBehavior.DefaultBodyUpdater(
|
|
this
|
|
);
|
|
}
|
|
|
|
resetToDefaultCollisionChecker() {
|
|
this.collisionChecker =
|
|
new gdjs.Physics3DRuntimeBehavior.DefaultCollisionChecker(this);
|
|
}
|
|
|
|
createShape(): Jolt.Shape {
|
|
if (
|
|
this.massCenterOffsetX === 0 &&
|
|
this.massCenterOffsetY === 0 &&
|
|
this.massCenterOffsetZ === 0
|
|
) {
|
|
return this.createShapeWithoutMassCenterOffset();
|
|
}
|
|
const rotatedShapeSettings =
|
|
this._createNewShapeSettingsWithoutMassCenterOffset();
|
|
const shapeScale = this.shapeScale * this._sharedData.worldInvScale;
|
|
const offsetCenterShapeSettings =
|
|
new Jolt.OffsetCenterOfMassShapeSettings(
|
|
this.getVec3(
|
|
this.massCenterOffsetX * shapeScale,
|
|
this.massCenterOffsetY * shapeScale,
|
|
this.massCenterOffsetZ * shapeScale
|
|
),
|
|
rotatedShapeSettings
|
|
);
|
|
const shape = offsetCenterShapeSettings.Create().Get();
|
|
Jolt.destroy(offsetCenterShapeSettings);
|
|
return shape;
|
|
}
|
|
|
|
createShapeWithoutMassCenterOffset(): Jolt.Shape {
|
|
const rotatedShapeSettings =
|
|
this._createNewShapeSettingsWithoutMassCenterOffset();
|
|
const shape = rotatedShapeSettings.Create().Get();
|
|
Jolt.destroy(rotatedShapeSettings);
|
|
return shape;
|
|
}
|
|
|
|
private _createNewShapeSettingsWithoutMassCenterOffset(): Jolt.RotatedTranslatedShapeSettings {
|
|
let width = this.owner3D.getWidth() * this._sharedData.worldInvScale;
|
|
let height = this.owner3D.getHeight() * this._sharedData.worldInvScale;
|
|
let depth = this.owner3D.getDepth() * this._sharedData.worldInvScale;
|
|
if (this.shapeOrientation === 'X') {
|
|
const swap = depth;
|
|
depth = width;
|
|
width = swap;
|
|
} else if (this.shapeOrientation === 'Y') {
|
|
const swap = depth;
|
|
depth = height;
|
|
height = swap;
|
|
}
|
|
|
|
const shapeScale = this.shapeScale * this._sharedData.worldInvScale;
|
|
|
|
const shapeDimensionA = this.shapeDimensionA * shapeScale;
|
|
const shapeDimensionB = this.shapeDimensionB * shapeScale;
|
|
const shapeDimensionC = this.shapeDimensionC * shapeScale;
|
|
|
|
const onePixel = this._sharedData.worldInvScale;
|
|
|
|
let shapeSettings: Jolt.ConvexShapeSettings;
|
|
/** This is fine only because no other Quat is used locally. */
|
|
let quat: Jolt.Quat;
|
|
if (this.shape === 'Box') {
|
|
const boxWidth =
|
|
shapeDimensionA > 0 ? shapeDimensionA : width > 0 ? width : onePixel;
|
|
const boxHeight =
|
|
shapeDimensionB > 0
|
|
? shapeDimensionB
|
|
: height > 0
|
|
? height
|
|
: onePixel;
|
|
const boxDepth =
|
|
shapeDimensionC > 0 ? shapeDimensionC : depth > 0 ? depth : onePixel;
|
|
// The convex radius should not eat up the whole volume.
|
|
const convexRadius = Math.min(
|
|
onePixel,
|
|
Math.min(boxWidth, boxHeight, boxDepth) / 4
|
|
);
|
|
shapeSettings = new Jolt.BoxShapeSettings(
|
|
this.getVec3(boxWidth / 2, boxHeight / 2, boxDepth / 2),
|
|
convexRadius
|
|
);
|
|
quat = this.getQuat(0, 0, 0, 1);
|
|
this._shapeHalfWidth = boxWidth / 2;
|
|
this._shapeHalfHeight = boxHeight / 2;
|
|
this._shapeHalfDepth = boxDepth / 2;
|
|
} else if (this.shape === 'Capsule') {
|
|
const radius =
|
|
shapeDimensionA > 0
|
|
? shapeDimensionA
|
|
: width > 0
|
|
? Math.sqrt(width * height) / 2
|
|
: onePixel;
|
|
const capsuleDepth =
|
|
shapeDimensionB > 0 ? shapeDimensionB : depth > 0 ? depth : onePixel;
|
|
shapeSettings = new Jolt.CapsuleShapeSettings(
|
|
Math.max(0, capsuleDepth / 2 - radius),
|
|
radius
|
|
);
|
|
quat = this._getShapeOrientationQuat();
|
|
this._shapeHalfWidth =
|
|
this.shapeOrientation === 'X' ? capsuleDepth / 2 : radius;
|
|
this._shapeHalfHeight =
|
|
this.shapeOrientation === 'Y' ? capsuleDepth / 2 : radius;
|
|
this._shapeHalfDepth =
|
|
this.shapeOrientation === 'Z' ? capsuleDepth / 2 : radius;
|
|
} else if (this.shape === 'Cylinder') {
|
|
const radius =
|
|
shapeDimensionA > 0
|
|
? shapeDimensionA
|
|
: width > 0
|
|
? Math.sqrt(width * height) / 2
|
|
: onePixel;
|
|
const cylinderDepth =
|
|
shapeDimensionB > 0 ? shapeDimensionB : depth > 0 ? depth : onePixel;
|
|
// The convex radius should not eat up the whole volume.
|
|
const convexRadius = Math.min(
|
|
onePixel,
|
|
Math.min(cylinderDepth, radius) / 4
|
|
);
|
|
shapeSettings = new Jolt.CylinderShapeSettings(
|
|
cylinderDepth / 2,
|
|
radius,
|
|
convexRadius
|
|
);
|
|
quat = this._getShapeOrientationQuat();
|
|
this._shapeHalfWidth =
|
|
this.shapeOrientation === 'X' ? cylinderDepth / 2 : radius;
|
|
this._shapeHalfHeight =
|
|
this.shapeOrientation === 'Y' ? cylinderDepth / 2 : radius;
|
|
this._shapeHalfDepth =
|
|
this.shapeOrientation === 'Z' ? cylinderDepth / 2 : radius;
|
|
} else {
|
|
// Create a 'Sphere' by default.
|
|
const radius =
|
|
shapeDimensionA > 0
|
|
? shapeDimensionA
|
|
: width > 0
|
|
? Math.pow(width * height * depth, 1 / 3) / 2
|
|
: onePixel;
|
|
shapeSettings = new Jolt.SphereShapeSettings(radius);
|
|
quat = this.getQuat(0, 0, 0, 1);
|
|
this._shapeHalfWidth = radius;
|
|
this._shapeHalfHeight = radius;
|
|
this._shapeHalfDepth = radius;
|
|
}
|
|
shapeSettings.mDensity = this.density;
|
|
return new Jolt.RotatedTranslatedShapeSettings(
|
|
this.getVec3(
|
|
this.shapeOffsetX * shapeScale,
|
|
this.shapeOffsetY * shapeScale,
|
|
this.shapeOffsetZ * shapeScale
|
|
),
|
|
quat,
|
|
shapeSettings
|
|
);
|
|
}
|
|
|
|
private _getShapeOrientationQuat(): Jolt.Quat {
|
|
if (this.shapeOrientation === 'X') {
|
|
// Top on X axis.
|
|
return this.getQuat(0, 0, Math.sqrt(2) / 2, -Math.sqrt(2) / 2);
|
|
} else if (this.shapeOrientation === 'Y') {
|
|
// Top on Y axis.
|
|
return this.getQuat(0, 0, 0, 1);
|
|
} else {
|
|
// Top on Z axis.
|
|
return this.getQuat(Math.sqrt(2) / 2, 0, 0, Math.sqrt(2) / 2);
|
|
}
|
|
}
|
|
|
|
private _recreateShape(): void {
|
|
this.bodyUpdater.recreateShape();
|
|
|
|
this._objectOldWidth = this.owner3D.getWidth();
|
|
this._objectOldHeight = this.owner3D.getHeight();
|
|
this._objectOldDepth = this.owner3D.getDepth();
|
|
}
|
|
|
|
getShapeScale(): float {
|
|
return this.shapeScale;
|
|
}
|
|
|
|
setShapeScale(shapeScale: float): void {
|
|
if (shapeScale !== this.shapeScale && shapeScale > 0) {
|
|
this.shapeScale = shapeScale;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
}
|
|
|
|
getBody(): Jolt.Body {
|
|
if (this._body === null) {
|
|
this._createBody();
|
|
}
|
|
return this._body!;
|
|
}
|
|
|
|
_createBody(): boolean {
|
|
this._needToRecreateBody = false;
|
|
this._needToRecreateShape = false;
|
|
|
|
if (!this.activated() || this._destroyedDuringFrameLogic) return false;
|
|
|
|
this._body = this.bodyUpdater.createAndAddBody();
|
|
if (!this._body) {
|
|
// It can only happen when the character behavior is destroyed.
|
|
return false;
|
|
}
|
|
this._body.gdjsAssociatedBehavior = this;
|
|
|
|
this._objectOldWidth = this.owner3D.getWidth();
|
|
this._objectOldHeight = this.owner3D.getHeight();
|
|
this._objectOldDepth = this.owner3D.getDepth();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @returns The body layer id according to the behavior configuration.
|
|
*/
|
|
getBodyLayer(): number {
|
|
return Jolt.ObjectLayerPairFilterMask.prototype.sGetObjectLayer(
|
|
this.getLayersAccordingToBodyType(),
|
|
this.getMasksAccordingToBodyType()
|
|
);
|
|
}
|
|
|
|
private getLayersAccordingToBodyType(): integer {
|
|
// Make sure objects don't register in the wrong layer group.
|
|
return this.isStatic()
|
|
? this.layers & gdjs.Physics3DSharedData.staticLayersMask
|
|
: this.layers & gdjs.Physics3DSharedData.dynamicLayersMask;
|
|
}
|
|
|
|
private getMasksAccordingToBodyType(): integer {
|
|
// Static objects accept all collisions as it's the mask of dynamic objects that matters.
|
|
return this.isStatic()
|
|
? gdjs.Physics3DSharedData.allLayersMask
|
|
: this.masks;
|
|
}
|
|
|
|
override doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
|
// Step the world if not done this frame yet.
|
|
// Don't step at the first frame to allow events to handle overlapping objects.
|
|
if (
|
|
!this._sharedData.stepped &&
|
|
!instanceContainer.getScene().getTimeManager().isFirstFrame()
|
|
) {
|
|
this._sharedData.step(
|
|
instanceContainer.getScene().getTimeManager().getElapsedTime() /
|
|
1000.0
|
|
);
|
|
}
|
|
}
|
|
|
|
override doStepPostEvents(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer
|
|
) {
|
|
// Reset world step to update next frame
|
|
this._sharedData.stepped = false;
|
|
}
|
|
|
|
onObjectHotReloaded() {
|
|
this.updateBodyFromObject();
|
|
}
|
|
|
|
recreateBody(previousBodyData?: {
|
|
linearVelocityX: float;
|
|
linearVelocityY: float;
|
|
linearVelocityZ: float;
|
|
angularVelocityX: float;
|
|
angularVelocityY: float;
|
|
angularVelocityZ: float;
|
|
}) {
|
|
const bodyInterface = this._sharedData.bodyInterface;
|
|
const linearVelocityX = previousBodyData
|
|
? previousBodyData.linearVelocityX
|
|
: this._body
|
|
? this._body.GetLinearVelocity().GetX()
|
|
: 0;
|
|
const linearVelocityY = previousBodyData
|
|
? previousBodyData.linearVelocityY
|
|
: this._body
|
|
? this._body.GetLinearVelocity().GetY()
|
|
: 0;
|
|
const linearVelocityZ = previousBodyData
|
|
? previousBodyData.linearVelocityZ
|
|
: this._body
|
|
? this._body.GetLinearVelocity().GetZ()
|
|
: 0;
|
|
const angularVelocityX = previousBodyData
|
|
? previousBodyData.angularVelocityX
|
|
: this._body
|
|
? this._body.GetAngularVelocity().GetX()
|
|
: 0;
|
|
const angularVelocityY = previousBodyData
|
|
? previousBodyData.angularVelocityY
|
|
: this._body
|
|
? this._body.GetAngularVelocity().GetY()
|
|
: 0;
|
|
const angularVelocityZ = previousBodyData
|
|
? previousBodyData.angularVelocityZ
|
|
: this._body
|
|
? this._body.GetAngularVelocity().GetZ()
|
|
: 0;
|
|
|
|
if (this._body) {
|
|
this.bodyUpdater.destroyBody();
|
|
this._contactsEndedThisFrame.length = 0;
|
|
this._contactsStartedThisFrame.length = 0;
|
|
this._currentContacts.length = 0;
|
|
}
|
|
|
|
this._createBody();
|
|
if (!this._body) {
|
|
return;
|
|
}
|
|
|
|
const bodyID = this._body.GetID();
|
|
bodyInterface.SetLinearVelocity(
|
|
bodyID,
|
|
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
|
);
|
|
bodyInterface.SetAngularVelocity(
|
|
bodyID,
|
|
this.getVec3(angularVelocityX, angularVelocityY, angularVelocityZ)
|
|
);
|
|
}
|
|
|
|
updateObjectFromBody() {
|
|
this.bodyUpdater.updateObjectFromBody();
|
|
|
|
// Update cached transform.
|
|
this._objectOldX = this.owner3D.getX();
|
|
this._objectOldY = this.owner3D.getY();
|
|
this._objectOldZ = this.owner3D.getZ();
|
|
this._objectOldRotationX = this.owner3D.getRotationX();
|
|
this._objectOldRotationY = this.owner3D.getRotationY();
|
|
this._objectOldRotationZ = this.owner3D.getAngle();
|
|
}
|
|
|
|
updateBodyFromObject() {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
|
|
if (this._needToRecreateBody) {
|
|
this.recreateBody();
|
|
}
|
|
|
|
// The object size has changed, recreate the shape.
|
|
// The width has changed and there is no custom dimension A (box: width, circle: radius, edge: length) or
|
|
// The height has changed, the shape is not an edge (edges doesn't have height),
|
|
// it isn't a box with custom height or a circle with custom radius
|
|
if (
|
|
this._needToRecreateShape ||
|
|
(!this.hasCustomShapeDimension() &&
|
|
(this._objectOldWidth !== this.owner3D.getWidth() ||
|
|
this._objectOldHeight !== this.owner3D.getHeight() ||
|
|
this._objectOldDepth !== this.owner3D.getDepth()))
|
|
) {
|
|
this._needToRecreateShape = false;
|
|
this._recreateShape();
|
|
}
|
|
|
|
this.bodyUpdater.updateBodyFromObject();
|
|
}
|
|
|
|
hasCustomShapeDimension() {
|
|
return (
|
|
this.shapeDimensionA > 0 ||
|
|
this.shapeDimensionB > 0 ||
|
|
this.shapeDimensionC > 0
|
|
);
|
|
}
|
|
|
|
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
|
result.Set(
|
|
this.owner3D.getCenterXInScene() * this._sharedData.worldInvScale,
|
|
this.owner3D.getCenterYInScene() * this._sharedData.worldInvScale,
|
|
this.owner3D.getCenterZInScene() * this._sharedData.worldInvScale
|
|
);
|
|
return result;
|
|
}
|
|
|
|
_getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
|
|
const threeObject = this.owner3D.get3DRendererObject();
|
|
result.Set(
|
|
threeObject.quaternion.x,
|
|
threeObject.quaternion.y,
|
|
threeObject.quaternion.z,
|
|
threeObject.quaternion.w
|
|
);
|
|
return result;
|
|
}
|
|
|
|
_moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
|
|
this.owner3D.setCenterXInScene(
|
|
physicsPosition.GetX() * this._sharedData.worldScale
|
|
);
|
|
this.owner3D.setCenterYInScene(
|
|
physicsPosition.GetY() * this._sharedData.worldScale
|
|
);
|
|
this.owner3D.setCenterZInScene(
|
|
physicsPosition.GetZ() * this._sharedData.worldScale
|
|
);
|
|
}
|
|
|
|
_moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
|
|
const threeObject = this.owner3D.get3DRendererObject();
|
|
threeObject.quaternion.x = physicsRotation.GetX();
|
|
threeObject.quaternion.y = physicsRotation.GetY();
|
|
threeObject.quaternion.z = physicsRotation.GetZ();
|
|
threeObject.quaternion.w = physicsRotation.GetW();
|
|
// TODO Avoid this instantiation
|
|
const euler = new THREE.Euler(0, 0, 0, 'ZYX');
|
|
euler.setFromQuaternion(threeObject.quaternion);
|
|
this.owner3D.setRotationX(gdjs.toDegrees(euler.x));
|
|
this.owner3D.setRotationY(gdjs.toDegrees(euler.y));
|
|
this.owner3D.setAngle(gdjs.toDegrees(euler.z));
|
|
}
|
|
|
|
getWorldScale(): float {
|
|
return this._sharedData.worldScale;
|
|
}
|
|
|
|
getGravityX(): float {
|
|
return this._sharedData.gravityX;
|
|
}
|
|
|
|
getGravityY(): float {
|
|
return this._sharedData.gravityY;
|
|
}
|
|
|
|
getGravityZ(): float {
|
|
return this._sharedData.gravityZ;
|
|
}
|
|
|
|
setGravityX(gravityX: float): void {
|
|
if (this._sharedData.gravityX === gravityX) {
|
|
return;
|
|
}
|
|
|
|
this._sharedData.gravityX = gravityX;
|
|
this._sharedData.physicsSystem.SetGravity(
|
|
this.getVec3(
|
|
this._sharedData.gravityX,
|
|
this._sharedData.gravityY,
|
|
this._sharedData.gravityZ
|
|
)
|
|
);
|
|
}
|
|
|
|
setGravityY(gravityY: float): void {
|
|
if (this._sharedData.gravityX === gravityY) {
|
|
return;
|
|
}
|
|
|
|
this._sharedData.gravityX = gravityY;
|
|
this._sharedData.physicsSystem.SetGravity(
|
|
this.getVec3(
|
|
this._sharedData.gravityX,
|
|
this._sharedData.gravityY,
|
|
this._sharedData.gravityZ
|
|
)
|
|
);
|
|
}
|
|
|
|
setGravityZ(gravityZ: float): void {
|
|
if (this._sharedData.gravityX === gravityZ) {
|
|
return;
|
|
}
|
|
|
|
this._sharedData.gravityZ = gravityZ;
|
|
this._sharedData.physicsSystem.SetGravity(
|
|
this.getVec3(
|
|
this._sharedData.gravityX,
|
|
this._sharedData.gravityY,
|
|
this._sharedData.gravityZ
|
|
)
|
|
);
|
|
}
|
|
|
|
isDynamic(): boolean {
|
|
return this.bodyType === 'Dynamic';
|
|
}
|
|
|
|
isStatic(): boolean {
|
|
return this.bodyType === 'Static';
|
|
}
|
|
|
|
isKinematic(): boolean {
|
|
return this.bodyType === 'Kinematic';
|
|
}
|
|
|
|
isBullet(): boolean {
|
|
return this.bullet;
|
|
}
|
|
|
|
setBullet(enable: boolean): void {
|
|
if (this.bullet === enable) {
|
|
return;
|
|
}
|
|
this.bullet = enable;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetMotionQuality(
|
|
body.GetID(),
|
|
this.bullet
|
|
? Jolt.EMotionQuality_LinearCast
|
|
: Jolt.EMotionQuality_Discrete
|
|
);
|
|
}
|
|
|
|
hasFixedRotation(): boolean {
|
|
return this.fixedRotation;
|
|
}
|
|
|
|
setFixedRotation(enable: boolean): void {
|
|
if (this.fixedRotation === enable) {
|
|
return;
|
|
}
|
|
this.fixedRotation = enable;
|
|
this._needToRecreateBody = true;
|
|
}
|
|
|
|
getDensity() {
|
|
return this.density;
|
|
}
|
|
|
|
setDensity(density: float): void {
|
|
// Non-negative values only
|
|
if (density < 0) {
|
|
density = 0;
|
|
}
|
|
if (this.density === density) {
|
|
return;
|
|
}
|
|
this.density = density;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
|
|
getMassOverride(): float {
|
|
return this.massOverride;
|
|
}
|
|
|
|
setMassOverride(mass: float): void {
|
|
if (this.massOverride === mass) {
|
|
return;
|
|
}
|
|
this.massOverride = mass;
|
|
this._needToRecreateBody = true;
|
|
}
|
|
|
|
getShapeOffsetX(): float {
|
|
return this.shapeOffsetX;
|
|
}
|
|
|
|
setShapeOffsetX(shapeOffsetX: float): void {
|
|
this.shapeOffsetX = shapeOffsetX;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
|
|
getShapeOffsetY(): float {
|
|
return this.shapeOffsetY;
|
|
}
|
|
|
|
setShapeOffsetY(shapeOffsetY: float): void {
|
|
this.shapeOffsetY = shapeOffsetY;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
|
|
getShapeOffsetZ(): float {
|
|
return this.shapeOffsetZ;
|
|
}
|
|
|
|
setShapeOffsetZ(shapeOffsetZ: float): void {
|
|
this.shapeOffsetZ = shapeOffsetZ;
|
|
this._needToRecreateShape = true;
|
|
}
|
|
|
|
getFriction(): float {
|
|
return this.friction;
|
|
}
|
|
|
|
setFriction(friction: float): void {
|
|
// Non-negative values only
|
|
if (friction < 0) {
|
|
friction = 0;
|
|
}
|
|
if (this.friction === friction) {
|
|
return;
|
|
}
|
|
this.friction = friction;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetFriction(body.GetID(), friction);
|
|
}
|
|
|
|
getRestitution(): float {
|
|
return this.restitution;
|
|
}
|
|
|
|
setRestitution(restitution: float): void {
|
|
// Non-negative values only
|
|
if (restitution < 0) {
|
|
restitution = 0;
|
|
}
|
|
if (this.restitution === restitution) {
|
|
return;
|
|
}
|
|
this.restitution = restitution;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetRestitution(body.GetID(), restitution);
|
|
}
|
|
|
|
getLinearDamping(): float {
|
|
return this.linearDamping;
|
|
}
|
|
|
|
setLinearDamping(linearDamping: float): void {
|
|
// Non-negative values only
|
|
if (linearDamping < 0) {
|
|
linearDamping = 0;
|
|
}
|
|
if (this.linearDamping === linearDamping) {
|
|
return;
|
|
}
|
|
this.linearDamping = linearDamping;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
body.GetMotionProperties().SetLinearDamping(linearDamping);
|
|
}
|
|
|
|
getAngularDamping(): float {
|
|
return this.angularDamping;
|
|
}
|
|
|
|
setAngularDamping(angularDamping: float): void {
|
|
// Non-negative values only
|
|
if (angularDamping < 0) {
|
|
angularDamping = 0;
|
|
}
|
|
if (this.angularDamping === angularDamping) {
|
|
return;
|
|
}
|
|
this.angularDamping = angularDamping;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
body.GetMotionProperties().SetAngularDamping(angularDamping);
|
|
}
|
|
|
|
getGravityScale(): float {
|
|
return this.gravityScale;
|
|
}
|
|
|
|
setGravityScale(gravityScale: float): void {
|
|
if (this.gravityScale === gravityScale) {
|
|
return;
|
|
}
|
|
this.gravityScale = gravityScale;
|
|
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
body.GetMotionProperties().SetGravityFactor(gravityScale);
|
|
}
|
|
|
|
layerEnabled(layer: integer): boolean {
|
|
// Layer must be an integer
|
|
layer = Math.floor(layer);
|
|
if (layer < 1 || layer > 8) {
|
|
return false;
|
|
}
|
|
return !!(this.layers & (1 << (layer - 1)));
|
|
}
|
|
|
|
enableLayer(layer: integer, enable: boolean): void {
|
|
// Layer must be an integer
|
|
layer = Math.floor(layer);
|
|
if (layer < 1 || layer > 8) {
|
|
return;
|
|
}
|
|
|
|
if (enable) {
|
|
this.layers |= 1 << (layer - 1);
|
|
} else {
|
|
this.layers &= ~(1 << (layer - 1));
|
|
}
|
|
|
|
this._needToRecreateBody = true;
|
|
}
|
|
|
|
maskEnabled(mask: integer): boolean {
|
|
// Mask must be an integer
|
|
mask = Math.floor(mask);
|
|
if (mask < 1 || mask > 16) {
|
|
return false;
|
|
}
|
|
return !!(this.masks & (1 << (mask - 1)));
|
|
}
|
|
|
|
enableMask(mask: integer, enable: boolean): void {
|
|
// Mask must be an integer
|
|
mask = Math.floor(mask);
|
|
if (mask < 1 || mask > 16) {
|
|
return;
|
|
}
|
|
|
|
if (enable) {
|
|
this.masks |= 1 << (mask - 1);
|
|
} else {
|
|
this.masks &= ~(1 << (mask - 1));
|
|
}
|
|
|
|
this._needToRecreateBody = true;
|
|
}
|
|
|
|
getLinearVelocityX(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return body.GetLinearVelocity().GetX() * this._sharedData.worldScale;
|
|
}
|
|
|
|
setLinearVelocityX(linearVelocityX: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetLinearVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
linearVelocityX * this._sharedData.worldInvScale,
|
|
body.GetLinearVelocity().GetY(),
|
|
body.GetLinearVelocity().GetZ()
|
|
)
|
|
);
|
|
}
|
|
|
|
getLinearVelocityY(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return body.GetLinearVelocity().GetY() * this._sharedData.worldScale;
|
|
}
|
|
|
|
setLinearVelocityY(linearVelocityY: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetLinearVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
body.GetLinearVelocity().GetX(),
|
|
linearVelocityY * this._sharedData.worldInvScale,
|
|
body.GetLinearVelocity().GetZ()
|
|
)
|
|
);
|
|
}
|
|
|
|
getLinearVelocityZ(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return body.GetLinearVelocity().GetZ() * this._sharedData.worldScale;
|
|
}
|
|
|
|
setLinearVelocityZ(linearVelocityZ: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetLinearVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
body.GetLinearVelocity().GetX(),
|
|
body.GetLinearVelocity().GetY(),
|
|
linearVelocityZ * this._sharedData.worldInvScale
|
|
)
|
|
);
|
|
}
|
|
|
|
getLinearVelocityLength(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return body.GetLinearVelocity().Length() * this._sharedData.worldScale;
|
|
}
|
|
|
|
getAngularVelocityX(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return gdjs.toDegrees(body.GetAngularVelocity().GetX());
|
|
}
|
|
|
|
setAngularVelocityX(angularVelocityX: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetAngularVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
gdjs.toRad(angularVelocityX),
|
|
body.GetAngularVelocity().GetY(),
|
|
body.GetAngularVelocity().GetZ()
|
|
)
|
|
);
|
|
}
|
|
|
|
getAngularVelocityY(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return gdjs.toDegrees(body.GetAngularVelocity().GetY());
|
|
}
|
|
|
|
setAngularVelocityY(angularVelocityY: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetAngularVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
body.GetAngularVelocity().GetX(),
|
|
gdjs.toRad(angularVelocityY),
|
|
body.GetAngularVelocity().GetZ()
|
|
)
|
|
);
|
|
}
|
|
|
|
getAngularVelocityZ(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return gdjs.toDegrees(body.GetAngularVelocity().GetZ());
|
|
}
|
|
|
|
setAngularVelocityZ(angularVelocityZ: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.SetAngularVelocity(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
body.GetAngularVelocity().GetX(),
|
|
body.GetAngularVelocity().GetY(),
|
|
gdjs.toRad(angularVelocityZ)
|
|
)
|
|
);
|
|
}
|
|
|
|
applyForce(
|
|
forceX: float,
|
|
forceY: float,
|
|
forceZ: float,
|
|
positionX: float,
|
|
positionY: float,
|
|
positionZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddForce(
|
|
body.GetID(),
|
|
this.getVec3(forceX, forceY, forceZ),
|
|
this.getRVec3(
|
|
positionX * this._sharedData.worldInvScale,
|
|
positionY * this._sharedData.worldInvScale,
|
|
positionZ * this._sharedData.worldInvScale
|
|
),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
|
|
applyForceAtCenter(forceX: float, forceY: float, forceZ: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddForce(
|
|
body.GetID(),
|
|
this.getVec3(forceX, forceY, forceZ),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
|
|
applyForceTowardPosition(
|
|
length: float,
|
|
towardX: float,
|
|
towardY: float,
|
|
towardZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
const deltaX = towardX - this.owner3D.getX();
|
|
const deltaY = towardY - this.owner3D.getY();
|
|
const deltaZ = towardZ - this.owner3D.getZ();
|
|
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
|
if (distanceSq === 0) {
|
|
return;
|
|
}
|
|
const ratio = length / Math.sqrt(distanceSq);
|
|
|
|
this._sharedData.bodyInterface.AddForce(
|
|
body.GetID(),
|
|
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
|
|
applyImpulse(
|
|
impulseX: float,
|
|
impulseY: float,
|
|
impulseZ: float,
|
|
positionX: float,
|
|
positionY: float,
|
|
positionZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddImpulse(
|
|
body.GetID(),
|
|
this.getVec3(impulseX, impulseY, impulseZ),
|
|
this.getRVec3(
|
|
positionX * this._sharedData.worldInvScale,
|
|
positionY * this._sharedData.worldInvScale,
|
|
positionZ * this._sharedData.worldInvScale
|
|
)
|
|
);
|
|
}
|
|
|
|
applyImpulseAtCenter(
|
|
impulseX: float,
|
|
impulseY: float,
|
|
impulseZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddImpulse(
|
|
body.GetID(),
|
|
this.getVec3(impulseX, impulseY, impulseZ)
|
|
);
|
|
}
|
|
|
|
applyImpulseTowardPosition(
|
|
length: float,
|
|
towardX: float,
|
|
towardY: float,
|
|
towardZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
const deltaX = towardX - this.owner3D.getX();
|
|
const deltaY = towardY - this.owner3D.getY();
|
|
const deltaZ = towardZ - this.owner3D.getZ();
|
|
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
|
if (distanceSq === 0) {
|
|
return;
|
|
}
|
|
const ratio = length / Math.sqrt(distanceSq);
|
|
|
|
this._sharedData.bodyInterface.AddImpulse(
|
|
body.GetID(),
|
|
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio)
|
|
);
|
|
}
|
|
|
|
applyTorque(torqueX: float, torqueY: float, torqueZ: float): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddTorque(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
gdjs.toRad(torqueX),
|
|
gdjs.toRad(torqueY),
|
|
gdjs.toRad(torqueZ)
|
|
),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
|
|
applyAngularImpulse(
|
|
angularImpulseX: float,
|
|
angularImpulseY: float,
|
|
angularImpulseZ: float
|
|
): void {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return;
|
|
}
|
|
const body = this._body!;
|
|
|
|
this._sharedData.bodyInterface.AddAngularImpulse(
|
|
body.GetID(),
|
|
this.getVec3(
|
|
gdjs.toRad(angularImpulseX),
|
|
gdjs.toRad(angularImpulseY),
|
|
gdjs.toRad(angularImpulseZ)
|
|
)
|
|
);
|
|
}
|
|
|
|
getMass(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return 1 / body.GetMotionProperties().GetInverseMass();
|
|
}
|
|
|
|
/**
|
|
* @returns The inertia for a rotation around X axis of the object at its
|
|
* default rotation (0°; 0°; 0°).
|
|
*/
|
|
getInertiaAroundX(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return 1 / body.GetMotionProperties().GetInverseInertiaDiagonal().GetX();
|
|
}
|
|
|
|
/**
|
|
* @returns The inertia for a rotation around Y axis of the object at its
|
|
* default rotation (0°; 0°; 0°).
|
|
*/
|
|
getInertiaAroundY(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return 1 / body.GetMotionProperties().GetInverseInertiaDiagonal().GetY();
|
|
}
|
|
|
|
/**
|
|
* @returns The inertia for a rotation around Z axis of the object at its
|
|
* default rotation (0°; 0°; 0°).
|
|
*/
|
|
getInertiaAroundZ(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return 1 / body.GetMotionProperties().GetInverseInertiaDiagonal().GetZ();
|
|
}
|
|
|
|
getMassCenterX(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return (
|
|
body.GetCenterOfMassPosition().GetX() * this._sharedData.worldScale
|
|
);
|
|
}
|
|
|
|
getMassCenterY(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return (
|
|
body.GetCenterOfMassPosition().GetY() * this._sharedData.worldScale
|
|
);
|
|
}
|
|
|
|
getMassCenterZ(): float {
|
|
if (this._body === null) {
|
|
if (!this._createBody()) return 0;
|
|
}
|
|
const body = this._body!;
|
|
|
|
return (
|
|
body.GetCenterOfMassPosition().GetZ() * this._sharedData.worldScale
|
|
);
|
|
}
|
|
|
|
onContactBegin(otherBehavior: Physics3DRuntimeBehavior): void {
|
|
this._currentContacts.push(otherBehavior);
|
|
|
|
// There might be contacts that end during the frame and
|
|
// start again right away. It is considered a glitch
|
|
// and should not be detected.
|
|
let i = this._contactsEndedThisFrame.indexOf(otherBehavior);
|
|
if (i !== -1) {
|
|
this._contactsEndedThisFrame.splice(i, 1);
|
|
} else {
|
|
this._contactsStartedThisFrame.push(otherBehavior);
|
|
}
|
|
}
|
|
|
|
onContactEnd(otherBehavior: Physics3DRuntimeBehavior): void {
|
|
this._contactsEndedThisFrame.push(otherBehavior);
|
|
|
|
const index = this._currentContacts.indexOf(otherBehavior);
|
|
if (index !== -1) {
|
|
this._currentContacts.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
canCollideAgainst(otherBehavior: gdjs.Physics3DRuntimeBehavior): boolean {
|
|
return (
|
|
(this.getMasksAccordingToBodyType() &
|
|
otherBehavior.getLayersAccordingToBodyType()) !==
|
|
0
|
|
);
|
|
}
|
|
|
|
static areObjectsColliding(
|
|
object1: gdjs.RuntimeObject,
|
|
object2: gdjs.RuntimeObject,
|
|
behaviorName: string
|
|
): boolean {
|
|
const behavior1 = object1.getBehavior(
|
|
behaviorName
|
|
) as Physics3DRuntimeBehavior | null;
|
|
if (!behavior1) return false;
|
|
return behavior1.collisionChecker.isColliding(object2);
|
|
}
|
|
|
|
static hasCollisionStartedBetween(
|
|
object1: gdjs.RuntimeObject,
|
|
object2: gdjs.RuntimeObject,
|
|
behaviorName: string
|
|
): boolean {
|
|
const behavior1 = object1.getBehavior(
|
|
behaviorName
|
|
) as Physics3DRuntimeBehavior | null;
|
|
if (!behavior1) return false;
|
|
return behavior1.collisionChecker.hasCollisionStartedWith(object2);
|
|
}
|
|
|
|
static hasCollisionStoppedBetween(
|
|
object1: gdjs.RuntimeObject,
|
|
object2: gdjs.RuntimeObject,
|
|
behaviorName: string
|
|
): boolean {
|
|
const behavior1 = object1.getBehavior(
|
|
behaviorName
|
|
) as Physics3DRuntimeBehavior | null;
|
|
if (!behavior1) return false;
|
|
return behavior1.collisionChecker.hasCollisionStoppedWith(object2);
|
|
}
|
|
}
|
|
|
|
gdjs.registerBehavior(
|
|
'Physics3D::Physics3DBehavior',
|
|
gdjs.Physics3DRuntimeBehavior
|
|
);
|
|
|
|
export namespace Physics3DRuntimeBehavior {
|
|
/**
|
|
* Allow extensions relying on the 3D physics to customize its
|
|
* behavior a bit.
|
|
*/
|
|
export interface Physics3DHook {
|
|
/**
|
|
* Called before the physics engine step.
|
|
*/
|
|
doBeforePhysicsStep(timeDelta: float): void;
|
|
}
|
|
|
|
export interface BodyUpdater {
|
|
createAndAddBody(): Jolt.Body | null;
|
|
updateObjectFromBody(): void;
|
|
updateBodyFromObject(): void;
|
|
recreateShape(): void;
|
|
destroyBody(): void;
|
|
}
|
|
|
|
export class DefaultBodyUpdater
|
|
implements gdjs.Physics3DRuntimeBehavior.BodyUpdater
|
|
{
|
|
behavior: gdjs.Physics3DRuntimeBehavior;
|
|
|
|
constructor(behavior: gdjs.Physics3DRuntimeBehavior) {
|
|
this.behavior = behavior;
|
|
}
|
|
|
|
createAndAddBody(): Jolt.Body | null {
|
|
const { behavior } = this;
|
|
const { _sharedData } = behavior;
|
|
|
|
const shape = behavior.createShape();
|
|
const bodyCreationSettings = new Jolt.BodyCreationSettings(
|
|
shape,
|
|
behavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
|
behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
|
behavior.bodyType === 'Static'
|
|
? Jolt.EMotionType_Static
|
|
: behavior.bodyType === 'Kinematic'
|
|
? Jolt.EMotionType_Kinematic
|
|
: Jolt.EMotionType_Dynamic,
|
|
behavior.getBodyLayer()
|
|
);
|
|
bodyCreationSettings.mMotionQuality = behavior.bullet
|
|
? Jolt.EMotionQuality_LinearCast
|
|
: Jolt.EMotionQuality_Discrete;
|
|
bodyCreationSettings.mAllowedDOFs = behavior.fixedRotation
|
|
? Jolt.EAllowedDOFs_TranslationX |
|
|
Jolt.EAllowedDOFs_TranslationY |
|
|
Jolt.EAllowedDOFs_TranslationZ
|
|
: Jolt.EAllowedDOFs_All;
|
|
bodyCreationSettings.mFriction = behavior.friction;
|
|
bodyCreationSettings.mRestitution = behavior.restitution;
|
|
bodyCreationSettings.mLinearDamping = behavior.linearDamping;
|
|
bodyCreationSettings.mAngularDamping = behavior.angularDamping;
|
|
bodyCreationSettings.mGravityFactor = behavior.gravityScale;
|
|
if (behavior.massOverride > 0) {
|
|
bodyCreationSettings.mOverrideMassProperties =
|
|
Jolt.EOverrideMassProperties_CalculateInertia;
|
|
bodyCreationSettings.mMassPropertiesOverride.mMass =
|
|
behavior.massOverride;
|
|
}
|
|
|
|
const bodyInterface = _sharedData.bodyInterface;
|
|
const body = bodyInterface.CreateBody(bodyCreationSettings);
|
|
Jolt.destroy(bodyCreationSettings);
|
|
|
|
bodyInterface.AddBody(body.GetID(), Jolt.EActivation_Activate);
|
|
return body;
|
|
}
|
|
|
|
updateObjectFromBody() {
|
|
const { behavior } = this;
|
|
const { _body } = behavior;
|
|
// Copy transform from body to the GD object.
|
|
// The body is null when the behavior was either deactivated or the object deleted.
|
|
// It would be useless to try to recreate it as updateBodyFromObject already does it.
|
|
// If the body is null, we just don't do anything
|
|
// (but still run the physics simulation - this is independent).
|
|
if (_body !== null && _body.IsActive()) {
|
|
behavior._moveObjectToPhysicsPosition(_body.GetPosition());
|
|
behavior._moveObjectToPhysicsRotation(_body.GetRotation());
|
|
}
|
|
}
|
|
|
|
updateBodyFromObject() {
|
|
const { behavior } = this;
|
|
const { owner3D, _sharedData } = behavior;
|
|
if (behavior._body === null) {
|
|
if (!behavior._createBody()) return;
|
|
}
|
|
const body = behavior._body!;
|
|
|
|
if (
|
|
this.behavior._objectOldX !== owner3D.getX() ||
|
|
this.behavior._objectOldY !== owner3D.getY() ||
|
|
this.behavior._objectOldZ !== owner3D.getZ() ||
|
|
this.behavior._objectOldRotationX !== owner3D.getRotationX() ||
|
|
this.behavior._objectOldRotationY !== owner3D.getRotationY() ||
|
|
this.behavior._objectOldRotationZ !== owner3D.getAngle()
|
|
) {
|
|
_sharedData.bodyInterface.SetPositionAndRotationWhenChanged(
|
|
body.GetID(),
|
|
this.behavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
|
this.behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
}
|
|
|
|
recreateShape() {
|
|
const { behavior } = this;
|
|
const { _sharedData } = behavior;
|
|
if (behavior._body === null) {
|
|
if (!behavior._createBody()) return;
|
|
}
|
|
const body = behavior._body!;
|
|
|
|
const bodyInterface = _sharedData.bodyInterface;
|
|
bodyInterface.SetShape(
|
|
body.GetID(),
|
|
behavior.createShape(),
|
|
true,
|
|
Jolt.EActivation_Activate
|
|
);
|
|
}
|
|
|
|
destroyBody() {
|
|
const { behavior } = this;
|
|
const { _sharedData } = behavior;
|
|
if (behavior._body !== null) {
|
|
_sharedData.bodyInterface.RemoveBody(behavior._body.GetID());
|
|
_sharedData.bodyInterface.DestroyBody(behavior._body.GetID());
|
|
behavior._body = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export interface CollisionChecker {
|
|
isColliding(object: gdjs.RuntimeObject): boolean;
|
|
hasCollisionStartedWith(object: gdjs.RuntimeObject): boolean;
|
|
hasCollisionStoppedWith(object: gdjs.RuntimeObject): boolean;
|
|
}
|
|
|
|
/**
|
|
* The default collision checker uses the contacts found while
|
|
* stepping the physics simulation. For characters, another one is used
|
|
* as characters are simulated before the rest of the physics simulation.
|
|
*/
|
|
export class DefaultCollisionChecker implements CollisionChecker {
|
|
behavior: gdjs.Physics3DRuntimeBehavior;
|
|
|
|
constructor(behavior: gdjs.Physics3DRuntimeBehavior) {
|
|
this.behavior = behavior;
|
|
}
|
|
|
|
isColliding(object: gdjs.RuntimeObject): boolean {
|
|
if (
|
|
this.behavior._currentContacts.some(
|
|
(behavior) => behavior.owner === object
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
return this.behavior._contactsStartedThisFrame.some(
|
|
(behavior) => behavior.owner === object
|
|
);
|
|
}
|
|
|
|
hasCollisionStartedWith(object: gdjs.RuntimeObject): boolean {
|
|
return this.behavior._contactsStartedThisFrame.some(
|
|
(behavior) => behavior.owner === object
|
|
);
|
|
}
|
|
|
|
hasCollisionStoppedWith(object: gdjs.RuntimeObject): boolean {
|
|
return this.behavior._contactsEndedThisFrame.some(
|
|
(behavior) => behavior.owner === object
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|