mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-08-25 12:23:58 -04:00
deb802cfec
* For now, this is limited to instances inside custom objects. This will be made available in the future for all instances in scenes if this works well.
1162 lines
37 KiB
TypeScript
1162 lines
37 KiB
TypeScript
/// <reference path="./jolt-physics.d.ts" />
|
|
|
|
namespace gdjs {
|
|
interface PhysicsCar3DNetworkSyncDataType {
|
|
lek: boolean;
|
|
rik: boolean;
|
|
upk: boolean;
|
|
dok: boolean;
|
|
hbk: boolean;
|
|
asf: float;
|
|
ssf: float;
|
|
etm: float;
|
|
esm: float;
|
|
ei: float;
|
|
es: float;
|
|
}
|
|
|
|
/**
|
|
* @category Behaviors > Physics 3D
|
|
*/
|
|
export interface PhysicsCar3DNetworkSyncData extends BehaviorNetworkSyncData {
|
|
props: PhysicsCar3DNetworkSyncDataType;
|
|
}
|
|
|
|
type Physics3D = {
|
|
behavior: gdjs.Physics3DRuntimeBehavior;
|
|
};
|
|
|
|
/**
|
|
* @category Behaviors > Physics 3D
|
|
*/
|
|
export class PhysicsCar3DRuntimeBehavior
|
|
extends gdjs.RuntimeBehavior
|
|
implements gdjs.Physics3DRuntimeBehavior.Physics3DHook
|
|
{
|
|
owner3D: gdjs.RuntimeObject3D;
|
|
private _physics3DBehaviorName: string;
|
|
private _physics3D: Physics3D | null = null;
|
|
private _isHookedToPhysicsStep = false;
|
|
_vehicleController: Jolt.WheeledVehicleController | null = null;
|
|
_stepListener: Jolt.VehicleConstraintStepListener | null = null;
|
|
_vehicleCollisionTester: Jolt.VehicleCollisionTesterCastCylinder | null =
|
|
null;
|
|
/**
|
|
* 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: gdjs.Physics3DSharedData;
|
|
private _destroyedDuringFrameLogic: boolean = false;
|
|
|
|
// Readonly configuration
|
|
|
|
_steerAngleMax;
|
|
private _beginningSteerSpeed: float;
|
|
private _endSteerSpeed: float;
|
|
_gearRatios: Array<float>;
|
|
_reverseGearRatios: Array<float>;
|
|
private _backWheelOffsetX: float;
|
|
private _frontWheelOffsetX: float;
|
|
private _wheelOffsetY: float;
|
|
private _wheelOffsetZ: float;
|
|
private _wheelRadius: float;
|
|
private _wheelWidth: float;
|
|
private _brakeTorqueMax: float;
|
|
private _handBrakeTorqueMax: float;
|
|
_hasBackWheelDrive: boolean;
|
|
_hasFrontWheelDrive: boolean;
|
|
_pitchRollAngleMax: float;
|
|
|
|
// Mutable configuration
|
|
|
|
_engineTorqueMax: float;
|
|
/** in RPM (rotation per minute) */
|
|
_engineSpeedMax: float;
|
|
_engineInertia: float;
|
|
|
|
// State
|
|
|
|
private _currentSteerRatio: float = 0;
|
|
/** Used to make sure the car is stopped before going backward. */
|
|
_previousForward = 1;
|
|
|
|
private _hasPressedForwardKey: boolean = false;
|
|
private _hasPressedBackwardKey: boolean = false;
|
|
private _hasPressedRightKey: boolean = false;
|
|
private _hasPressedLeftKey: boolean = false;
|
|
private _hasPressedHandBrakeKey: boolean = false;
|
|
private _acceleratorStickForce: float = 0;
|
|
private _steeringStickForce: float = 0;
|
|
|
|
// This is useful for extensions that need to know
|
|
// which keys were pressed and doesn't know the mapping
|
|
// done by the scene events.
|
|
private _wasLeftKeyPressed: boolean = false;
|
|
private _wasRightKeyPressed: boolean = false;
|
|
private _wasForwardKeyPressed: boolean = false;
|
|
private _wasBackwardKeyPressed: boolean = false;
|
|
private _wasHandBrakeKeyPressed: boolean = false;
|
|
private _previousAcceleratorStickForce: float = 0;
|
|
private _previousSteeringStickForce: float = 0;
|
|
|
|
// This is useful when the object is synchronized by an external source
|
|
// like in a multiplayer game, and we want to be able to predict the
|
|
// movement of the object, even if the inputs are not updated every frame.
|
|
private _clearInputsBetweenFrames: boolean = true;
|
|
|
|
constructor(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer,
|
|
behaviorData,
|
|
owner: gdjs.RuntimeObject3D
|
|
) {
|
|
super(instanceContainer, behaviorData, owner);
|
|
this.owner3D = owner;
|
|
this._physics3DBehaviorName = behaviorData.physics3D;
|
|
this._sharedData = gdjs.Physics3DSharedData.getSharedData(
|
|
instanceContainer.getScene(),
|
|
behaviorData.Physics3D
|
|
);
|
|
this._steerAngleMax = behaviorData.steerAngleMax;
|
|
this._beginningSteerSpeed = behaviorData.beginningSteerSpeed;
|
|
this._endSteerSpeed = behaviorData.endSteerSpeed;
|
|
this._engineTorqueMax = behaviorData.engineTorqueMax;
|
|
this._engineSpeedMax = behaviorData.engineSpeedMax;
|
|
this._engineInertia = behaviorData.engineInertia;
|
|
this._reverseGearRatios = [behaviorData.reverseGearRatio1];
|
|
this._gearRatios = [
|
|
behaviorData.gearRatio1,
|
|
behaviorData.gearRatio2,
|
|
behaviorData.gearRatio3,
|
|
behaviorData.gearRatio4,
|
|
behaviorData.gearRatio5,
|
|
behaviorData.gearRatio6,
|
|
];
|
|
this._backWheelOffsetX = behaviorData.backWheelOffsetX;
|
|
this._frontWheelOffsetX = behaviorData.frontWheelOffsetX;
|
|
this._wheelOffsetY = behaviorData.wheelOffsetY;
|
|
this._wheelOffsetZ = behaviorData.wheelOffsetZ;
|
|
this._wheelRadius = behaviorData.wheelRadius;
|
|
this._wheelWidth = behaviorData.wheelWidth;
|
|
this._brakeTorqueMax = behaviorData.brakeTorqueMax;
|
|
this._handBrakeTorqueMax = behaviorData.handBrakeTorqueMax;
|
|
this._hasBackWheelDrive = behaviorData.hasBackWheelDrive;
|
|
this._hasFrontWheelDrive = behaviorData.hasFrontWheelDrive;
|
|
this._pitchRollAngleMax = behaviorData.pitchRollAngleMax;
|
|
}
|
|
|
|
private getVec3(x: float, y: float, z: float): Jolt.Vec3 {
|
|
const tempVec3 = this._sharedData._tempVec3;
|
|
tempVec3.Set(x, y, z);
|
|
return tempVec3;
|
|
}
|
|
|
|
getPhysics3D(): Physics3D | null {
|
|
if (this._destroyedDuringFrameLogic) {
|
|
return null;
|
|
}
|
|
if (this._physics3D) {
|
|
return this._physics3D;
|
|
}
|
|
const behavior = this.owner.getBehavior(
|
|
this._physics3DBehaviorName
|
|
) as gdjs.Physics3DRuntimeBehavior;
|
|
if (!behavior.activated()) {
|
|
return null;
|
|
}
|
|
if (behavior._shape === 'Mesh') {
|
|
behavior._shape = 'Box';
|
|
}
|
|
const sharedData = behavior._sharedData;
|
|
|
|
this._physics3D = {
|
|
behavior,
|
|
};
|
|
if (!this._isHookedToPhysicsStep) {
|
|
sharedData.registerHook(this);
|
|
this._isHookedToPhysicsStep = true;
|
|
}
|
|
|
|
// Destroy the body before switching the bodyUpdater,
|
|
// to ensure the body of the previous bodyUpdater is not left alive.
|
|
// (would be a memory leak and would create a phantom body in the physics world)
|
|
// But transfer the linear and angular velocity to the new body,
|
|
// so the body doesn't stop when it is recreated.
|
|
let previousBodyData = {
|
|
linearVelocityX: 0,
|
|
linearVelocityY: 0,
|
|
linearVelocityZ: 0,
|
|
angularVelocityX: 0,
|
|
angularVelocityY: 0,
|
|
angularVelocityZ: 0,
|
|
};
|
|
if (behavior._body) {
|
|
const linearVelocity = behavior._body.GetLinearVelocity();
|
|
previousBodyData.linearVelocityX = linearVelocity.GetX();
|
|
previousBodyData.linearVelocityY = linearVelocity.GetY();
|
|
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
|
|
const angularVelocity = behavior._body.GetAngularVelocity();
|
|
previousBodyData.angularVelocityX = angularVelocity.GetX();
|
|
previousBodyData.angularVelocityY = angularVelocity.GetY();
|
|
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
|
|
behavior.bodyUpdater.destroyBody();
|
|
}
|
|
|
|
behavior.bodyUpdater =
|
|
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
|
|
this,
|
|
behavior.bodyUpdater
|
|
);
|
|
behavior.recreateBody(previousBodyData);
|
|
|
|
return this._physics3D;
|
|
}
|
|
|
|
override applyBehaviorOverriding(behaviorData): boolean {
|
|
if (behaviorData.steerAngleMax !== undefined) {
|
|
this.setSteerAngleMax(behaviorData.steerAngleMax);
|
|
}
|
|
if (behaviorData.beginningSteerSpeed !== undefined) {
|
|
this.setBeginningSteerSpeed(behaviorData.beginningSteerSpeed);
|
|
}
|
|
if (behaviorData.endSteerSpeed !== undefined) {
|
|
this.setEndSteerSpeed(behaviorData.endSteerSpeed);
|
|
}
|
|
if (behaviorData.engineTorqueMax !== undefined) {
|
|
this.setEngineTorqueMax(behaviorData.engineTorqueMax);
|
|
}
|
|
if (behaviorData.engineSpeedMax !== undefined) {
|
|
this.setEngineSpeedMax(behaviorData.engineSpeedMax);
|
|
}
|
|
if (behaviorData.engineInertia !== undefined) {
|
|
this.setEngineInertia(behaviorData.engineInertia);
|
|
}
|
|
if (behaviorData.reverseGearRatio1 !== undefined) {
|
|
this.setGearRatio(-1, behaviorData.reverseGearRatio1);
|
|
}
|
|
if (behaviorData.gearRatio1 !== undefined) {
|
|
this.setGearRatio(1, behaviorData.gearRatio1);
|
|
}
|
|
if (behaviorData.gearRatio2 !== undefined) {
|
|
this.setGearRatio(2, behaviorData.gearRatio2);
|
|
}
|
|
if (behaviorData.gearRatio3 !== undefined) {
|
|
this.setGearRatio(3, behaviorData.gearRatio3);
|
|
}
|
|
if (behaviorData.gearRatio4 !== undefined) {
|
|
this.setGearRatio(4, behaviorData.gearRatio4);
|
|
}
|
|
if (behaviorData.gearRatio5 !== undefined) {
|
|
this.setGearRatio(5, behaviorData.gearRatio5);
|
|
}
|
|
if (behaviorData.gearRatio6 !== undefined) {
|
|
this.setGearRatio(6, behaviorData.gearRatio6);
|
|
}
|
|
if (behaviorData.backWheelOffsetX !== undefined) {
|
|
this.setBackWheelOffsetX(behaviorData.backWheelOffsetX);
|
|
}
|
|
if (behaviorData.frontWheelOffsetX !== undefined) {
|
|
this.setFrontWheelOffsetX(behaviorData.frontWheelOffsetX);
|
|
}
|
|
if (behaviorData.wheelOffsetY !== undefined) {
|
|
this.setWheelOffsetY(behaviorData.wheelOffsetY);
|
|
}
|
|
if (behaviorData.wheelOffsetZ !== undefined) {
|
|
this.setWheelOffsetZ(behaviorData.wheelOffsetZ);
|
|
}
|
|
if (behaviorData.wheelRadius !== undefined) {
|
|
this.setWheelRadius(behaviorData.wheelRadius);
|
|
}
|
|
if (behaviorData.wheelWidth !== undefined) {
|
|
this.setWheelWidth(behaviorData.wheelWidth);
|
|
}
|
|
if (behaviorData.brakeTorqueMax !== undefined) {
|
|
this.setBrakeTorqueMax(behaviorData.brakeTorqueMax);
|
|
}
|
|
if (behaviorData.handBrakeTorqueMax !== undefined) {
|
|
this.setHandBrakeTorqueMax(behaviorData.handBrakeTorqueMax);
|
|
}
|
|
if (behaviorData.hasBackWheelDrive !== undefined) {
|
|
this.setBackWheelDrive(behaviorData.hasBackWheelDrive);
|
|
}
|
|
if (behaviorData.hasFrontWheelDrive !== undefined) {
|
|
this.setFrontWheelDrive(behaviorData.hasFrontWheelDrive);
|
|
}
|
|
if (behaviorData.pitchRollAngleMax !== undefined) {
|
|
this.setPitchRollAngleMax(behaviorData.pitchRollAngleMax);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override getNetworkSyncData(
|
|
syncOptions: GetNetworkSyncDataOptions
|
|
): PhysicsCar3DNetworkSyncData {
|
|
// This method is called, so we are synchronizing this object.
|
|
// Let's clear the inputs between frames as we control it.
|
|
this._clearInputsBetweenFrames = true;
|
|
|
|
return {
|
|
...super.getNetworkSyncData(syncOptions),
|
|
props: {
|
|
lek: this._wasLeftKeyPressed,
|
|
rik: this._wasRightKeyPressed,
|
|
upk: this._wasForwardKeyPressed,
|
|
dok: this._wasBackwardKeyPressed,
|
|
hbk: this._wasHandBrakeKeyPressed,
|
|
asf: this._previousAcceleratorStickForce,
|
|
ssf: this._previousSteeringStickForce,
|
|
etm: this._engineTorqueMax,
|
|
esm: this._engineSpeedMax,
|
|
ei: this._engineInertia,
|
|
es: this.getEngineSpeed(),
|
|
},
|
|
};
|
|
}
|
|
|
|
override updateFromNetworkSyncData(
|
|
networkSyncData: PhysicsCar3DNetworkSyncData,
|
|
options: UpdateFromNetworkSyncDataOptions
|
|
) {
|
|
super.updateFromNetworkSyncData(networkSyncData, options);
|
|
|
|
const behaviorSpecificProps = networkSyncData.props;
|
|
this._hasPressedForwardKey = behaviorSpecificProps.upk;
|
|
this._hasPressedBackwardKey = behaviorSpecificProps.dok;
|
|
this._hasPressedLeftKey = behaviorSpecificProps.lek;
|
|
this._hasPressedRightKey = behaviorSpecificProps.rik;
|
|
this._hasPressedHandBrakeKey = behaviorSpecificProps.hbk;
|
|
this._acceleratorStickForce = behaviorSpecificProps.asf;
|
|
this._steeringStickForce = behaviorSpecificProps.ssf;
|
|
this._engineTorqueMax = behaviorSpecificProps.etm;
|
|
this._engineSpeedMax = behaviorSpecificProps.esm;
|
|
this._engineInertia = behaviorSpecificProps.ei;
|
|
if (this._vehicleController) {
|
|
this._vehicleController
|
|
.GetEngine()
|
|
.SetCurrentRPM(behaviorSpecificProps.es);
|
|
}
|
|
|
|
// When the object is synchronized from the network, the inputs must not be cleared,
|
|
// except if asked specifically.
|
|
this._clearInputsBetweenFrames = !!options.clearInputs;
|
|
}
|
|
|
|
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
|
const physics3D = this.getPhysics3D();
|
|
if (!physics3D) {
|
|
result.Set(0, 0, 0);
|
|
return result;
|
|
}
|
|
const { behavior } = physics3D;
|
|
// Same as for characters, the origin is at its feet:
|
|
// - the center is used for X and Y because Box3D origin is at the top-left corner
|
|
// - the origin is used for Z because, when the character is made smaller,
|
|
// it must stay on the ground and not fell from its old size.
|
|
result.Set(
|
|
this.owner3D.getCenterXInScene() * this._sharedData.worldInvScale,
|
|
this.owner3D.getCenterYInScene() * this._sharedData.worldInvScale,
|
|
this.owner3D.getZ() * this._sharedData.worldInvScale +
|
|
behavior._shapeHalfDepth
|
|
);
|
|
return result;
|
|
}
|
|
|
|
override onDeActivate() {
|
|
if (!this._physics3D) {
|
|
return;
|
|
}
|
|
this._destroyBody();
|
|
}
|
|
|
|
override onActivate() {
|
|
const behavior = this.owner.getBehavior(
|
|
this._physics3DBehaviorName
|
|
) as gdjs.Physics3DRuntimeBehavior;
|
|
if (!behavior) {
|
|
return;
|
|
}
|
|
behavior._destroyBody();
|
|
}
|
|
|
|
override onDestroy() {
|
|
this._destroyedDuringFrameLogic = true;
|
|
this._destroyBody();
|
|
}
|
|
|
|
_destroyBody() {
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
if (this._stepListener) {
|
|
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
|
|
Jolt.destroy(this._stepListener);
|
|
this._stepListener = null;
|
|
}
|
|
const constraint = this._vehicleController.GetConstraint();
|
|
this._sharedData.physicsSystem.RemoveConstraint(constraint);
|
|
// The controller is destroyed with the constraint.
|
|
this._vehicleController = null;
|
|
// VehicleConstraint.mVehicleCollisionTester is a RefConst (smart pointer).
|
|
// It is destroyed with the constraint.
|
|
this._vehicleCollisionTester = null;
|
|
if (this._physics3D) {
|
|
const { behavior } = this._physics3D;
|
|
behavior.resetToDefaultBodyUpdater();
|
|
this._physics3D = null;
|
|
}
|
|
}
|
|
|
|
override doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
|
// Trigger createAndAddBody()
|
|
this.getPhysics3D();
|
|
}
|
|
|
|
override doStepPostEvents(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer
|
|
) {
|
|
// Trigger createAndAddBody()
|
|
this.getPhysics3D();
|
|
}
|
|
|
|
doBeforePhysicsStep(timeDelta: float): void {
|
|
if (!this.activated()) {
|
|
return;
|
|
}
|
|
const physics3D = this.getPhysics3D();
|
|
if (!physics3D) {
|
|
return;
|
|
}
|
|
const { behavior } = physics3D;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
const carBody = behavior._body;
|
|
if (!carBody) {
|
|
return;
|
|
}
|
|
|
|
const steeringControl =
|
|
this._steeringStickForce ||
|
|
(this._hasPressedLeftKey ? -1 : 0) + (this._hasPressedRightKey ? 1 : 0);
|
|
if (steeringControl === 0) {
|
|
this._currentSteerRatio = 0;
|
|
} else {
|
|
const steerSpeed = gdjs.evtTools.common.lerp(
|
|
this._beginningSteerSpeed,
|
|
this._endSteerSpeed,
|
|
Math.abs(this._currentSteerRatio)
|
|
);
|
|
if (steeringControl < 0) {
|
|
// Avoid to much latency when changing of direction
|
|
this._currentSteerRatio = Math.min(0, this._currentSteerRatio);
|
|
this._currentSteerRatio +=
|
|
(steeringControl * steerSpeed * timeDelta) / this._steerAngleMax;
|
|
this._currentSteerRatio = Math.max(-1, this._currentSteerRatio);
|
|
}
|
|
if (steeringControl > 0) {
|
|
// Avoid to much latency when changing of direction
|
|
this._currentSteerRatio = Math.max(0, this._currentSteerRatio);
|
|
this._currentSteerRatio +=
|
|
(steeringControl * steerSpeed * timeDelta) / this._steerAngleMax;
|
|
this._currentSteerRatio = Math.min(1, this._currentSteerRatio);
|
|
}
|
|
}
|
|
|
|
let brake = 0;
|
|
const acceleratorControl =
|
|
this._acceleratorStickForce ||
|
|
(this._hasPressedBackwardKey ? -1 : 0) +
|
|
(this._hasPressedForwardKey ? 1 : 0);
|
|
let forward = acceleratorControl;
|
|
if (this._previousForward * forward < 0.0) {
|
|
const velocity = carBody
|
|
.GetRotation()
|
|
.InverseRotate(carBody.GetLinearVelocity())
|
|
.GetX();
|
|
if (
|
|
(forward > 0.0 && velocity < -0.1) ||
|
|
(forward < 0.0 && velocity > 0.1)
|
|
) {
|
|
// Brake while we've not stopped yet
|
|
forward = 0.0;
|
|
brake = 1.0;
|
|
} else {
|
|
// When we've come to a stop, accept the new direction
|
|
this._previousForward = forward;
|
|
}
|
|
}
|
|
|
|
let handBrake = 0;
|
|
if (this._hasPressedHandBrakeKey) {
|
|
forward = 0.0;
|
|
handBrake = 1.0;
|
|
}
|
|
|
|
this._vehicleController.SetDriverInput(
|
|
forward,
|
|
-this._currentSteerRatio,
|
|
brake,
|
|
handBrake
|
|
);
|
|
if (
|
|
forward !== 0.0 ||
|
|
this._currentSteerRatio !== 0.0 ||
|
|
brake !== 0.0 ||
|
|
handBrake !== 0.0
|
|
) {
|
|
this._sharedData.bodyInterface.ActivateBody(carBody.GetID());
|
|
}
|
|
|
|
this._wasForwardKeyPressed = this._hasPressedForwardKey;
|
|
this._wasBackwardKeyPressed = this._hasPressedBackwardKey;
|
|
this._wasRightKeyPressed = this._hasPressedRightKey;
|
|
this._wasLeftKeyPressed = this._hasPressedLeftKey;
|
|
this._wasHandBrakeKeyPressed = this._hasPressedHandBrakeKey;
|
|
this._previousAcceleratorStickForce = this._acceleratorStickForce;
|
|
this._previousSteeringStickForce = this._steeringStickForce;
|
|
|
|
if (this._clearInputsBetweenFrames) {
|
|
this._hasPressedForwardKey = false;
|
|
this._hasPressedBackwardKey = false;
|
|
this._hasPressedRightKey = false;
|
|
this._hasPressedLeftKey = false;
|
|
this._hasPressedHandBrakeKey = false;
|
|
this._acceleratorStickForce = 0;
|
|
this._steeringStickForce = 0;
|
|
}
|
|
}
|
|
|
|
override onObjectHotReloaded() {}
|
|
|
|
simulateForwardKey(): void {
|
|
this._hasPressedForwardKey = true;
|
|
}
|
|
|
|
wasForwardKeyPressed(): boolean {
|
|
return this._wasForwardKeyPressed;
|
|
}
|
|
|
|
simulateBackwardKey(): void {
|
|
this._hasPressedBackwardKey = true;
|
|
}
|
|
|
|
wasBackwardKeyPressed(): boolean {
|
|
return this._wasBackwardKeyPressed;
|
|
}
|
|
|
|
simulateRightKey(): void {
|
|
this._hasPressedRightKey = true;
|
|
}
|
|
|
|
wasRightKeyPressed(): boolean {
|
|
return this._wasRightKeyPressed;
|
|
}
|
|
|
|
simulateLeftKey(): void {
|
|
this._hasPressedLeftKey = true;
|
|
}
|
|
|
|
wasLeftKeyPressed(): boolean {
|
|
return this._wasLeftKeyPressed;
|
|
}
|
|
|
|
simulateHandBrakeKey(): void {
|
|
this._hasPressedHandBrakeKey = true;
|
|
}
|
|
|
|
wasHandBrakeKeyPressed(): boolean {
|
|
return this._wasHandBrakeKeyPressed;
|
|
}
|
|
|
|
simulateAcceleratorStick(stickForce: float): void {
|
|
this._acceleratorStickForce = gdjs.evtTools.common.clamp(
|
|
-1,
|
|
1,
|
|
stickForce
|
|
);
|
|
}
|
|
|
|
simulateSteeringStick(stickForce: float): void {
|
|
this._steeringStickForce = gdjs.evtTools.common.clamp(-1, 1, stickForce);
|
|
}
|
|
|
|
getAcceleratorStickForce(): float {
|
|
return this._acceleratorStickForce;
|
|
}
|
|
|
|
getSteeringStickForce(): float {
|
|
return this._steeringStickForce;
|
|
}
|
|
|
|
getPreviousAcceleratorStickForce(): float {
|
|
return this._previousAcceleratorStickForce;
|
|
}
|
|
|
|
getPreviousSteeringStickForce(): float {
|
|
return this._previousSteeringStickForce;
|
|
}
|
|
|
|
getSteerAngle(): float {
|
|
return this._currentSteerRatio * this._steerAngleMax;
|
|
}
|
|
|
|
/**
|
|
* @returns The engine current speed in RPM (rotation per minute)
|
|
*/
|
|
getEngineSpeed(): float {
|
|
return this._vehicleController
|
|
? this._vehicleController.GetEngine().GetCurrentRPM()
|
|
: 0;
|
|
}
|
|
|
|
getCurrentGear(): float {
|
|
return this._vehicleController
|
|
? this._vehicleController.GetTransmission().GetCurrentGear()
|
|
: 0;
|
|
}
|
|
|
|
getSteerAngleMax(): float {
|
|
return this._steerAngleMax;
|
|
}
|
|
|
|
setSteerAngleMax(steerAngleMax: float) {
|
|
this._steerAngleMax = steerAngleMax;
|
|
}
|
|
|
|
getBeginningSteerSpeed(): float {
|
|
return this._beginningSteerSpeed;
|
|
}
|
|
|
|
setBeginningSteerSpeed(beginningSteerSpeed: float): void {
|
|
this._beginningSteerSpeed = beginningSteerSpeed;
|
|
}
|
|
|
|
getEndSteerSpeed(): float {
|
|
return this._endSteerSpeed;
|
|
}
|
|
|
|
setEndSteerSpeed(endSteerSpeed: float): void {
|
|
this._endSteerSpeed = endSteerSpeed;
|
|
}
|
|
|
|
invalidateShape(): void {
|
|
const physics3D = this.getPhysics3D();
|
|
if (!physics3D) {
|
|
return;
|
|
}
|
|
const { behavior } = physics3D;
|
|
behavior._needToRecreateShape = true;
|
|
}
|
|
|
|
getEngineTorqueMax(): float {
|
|
return this._engineTorqueMax;
|
|
}
|
|
|
|
setEngineTorqueMax(engineTorqueMax: float): void {
|
|
this._engineTorqueMax = engineTorqueMax;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
this._vehicleController.GetEngine().mMaxTorque = engineTorqueMax;
|
|
}
|
|
|
|
/**
|
|
* @returns The engine maximum speed in RPM (rotation per minute)
|
|
*/
|
|
getEngineSpeedMax(): float {
|
|
return this._engineSpeedMax;
|
|
}
|
|
|
|
setEngineSpeedMax(engineSpeedMax: float): void {
|
|
this._engineSpeedMax = engineSpeedMax;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
this._vehicleController.GetEngine().mMaxRPM = engineSpeedMax;
|
|
}
|
|
|
|
getEngineInertia(): float {
|
|
return this._engineInertia;
|
|
}
|
|
|
|
setEngineInertia(engineInertia: float): void {
|
|
this._engineInertia = engineInertia;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
this._vehicleController.GetEngine().mInertia = engineInertia;
|
|
}
|
|
|
|
getGearRatio(gearNumber: integer): float {
|
|
if (gearNumber === 0) {
|
|
return 0;
|
|
}
|
|
if (gearNumber < 0) {
|
|
return this._reverseGearRatios[-gearNumber - 1] || 0;
|
|
}
|
|
return this._gearRatios[gearNumber - 1] || 0;
|
|
}
|
|
|
|
setGearRatio(gearNumber: integer, ratio: float) {
|
|
if (gearNumber > 0) {
|
|
this._gearRatios[gearNumber - 1] = ratio;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
this._updateGearRatios();
|
|
} else if (gearNumber < 0) {
|
|
this._reverseGearRatios[-gearNumber - 1] = ratio;
|
|
this._updateReverseGearRatios();
|
|
}
|
|
}
|
|
|
|
_updateGearRatios() {
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
const gearRatios = this._vehicleController.GetTransmission().mGearRatios;
|
|
gearRatios.clear();
|
|
let previousGearRatio = Number.POSITIVE_INFINITY;
|
|
for (let index = 0; index < this._gearRatios.length; index++) {
|
|
const gearRatio = this._gearRatios[index];
|
|
if (0 < gearRatio && gearRatio < previousGearRatio) {
|
|
gearRatios.push_back(gearRatio);
|
|
previousGearRatio = gearRatio;
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateReverseGearRatios() {
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
const reverseGearRatios =
|
|
this._vehicleController.GetTransmission().mReverseGearRatios;
|
|
reverseGearRatios.clear();
|
|
let previousGearRatio = Number.NEGATIVE_INFINITY;
|
|
for (let index = 0; index < this._reverseGearRatios.length; index++) {
|
|
const gearRatio = this._reverseGearRatios[index];
|
|
if (previousGearRatio < gearRatio && gearRatio < 0) {
|
|
reverseGearRatios.push_back(gearRatio);
|
|
previousGearRatio = gearRatio;
|
|
}
|
|
}
|
|
}
|
|
|
|
getBackWheelOffsetX(): float {
|
|
return this._backWheelOffsetX;
|
|
}
|
|
|
|
setBackWheelOffsetX(backWheelOffsetX: float): void {
|
|
this._backWheelOffsetX = backWheelOffsetX;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getFrontWheelOffsetX(): float {
|
|
return this._frontWheelOffsetX;
|
|
}
|
|
|
|
setFrontWheelOffsetX(frontWheelOffsetX: float): void {
|
|
this._frontWheelOffsetX = frontWheelOffsetX;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getWheelOffsetY(): float {
|
|
return this._wheelOffsetY;
|
|
}
|
|
|
|
setWheelOffsetY(wheelOffsetY: float): void {
|
|
this._wheelOffsetY = wheelOffsetY;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getWheelOffsetZ(): float {
|
|
return this._wheelOffsetZ;
|
|
}
|
|
|
|
setWheelOffsetZ(wheelOffsetZ: float): void {
|
|
this._wheelOffsetZ = wheelOffsetZ;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getWheelRadius(): float {
|
|
return this._wheelRadius;
|
|
}
|
|
|
|
setWheelRadius(wheelRadius: float): void {
|
|
this._wheelRadius = wheelRadius;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getWheelWidth(): float {
|
|
return this._wheelWidth;
|
|
}
|
|
|
|
setWheelWidth(wheelWidth: float): void {
|
|
this._wheelWidth = wheelWidth;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getBrakeTorqueMax(): float {
|
|
return this._brakeTorqueMax;
|
|
}
|
|
|
|
setBrakeTorqueMax(brakeTorqueMax: float): void {
|
|
this._brakeTorqueMax = brakeTorqueMax;
|
|
this._updateWheels();
|
|
}
|
|
|
|
getHandBrakeTorqueMax(): float {
|
|
return this._handBrakeTorqueMax;
|
|
}
|
|
|
|
setHandBrakeTorqueMax(handBrakeTorqueMax: float): void {
|
|
this._handBrakeTorqueMax = handBrakeTorqueMax;
|
|
this._updateWheels();
|
|
}
|
|
|
|
hasBackWheelDrive(): boolean {
|
|
return this._hasBackWheelDrive;
|
|
}
|
|
|
|
setBackWheelDrive(hasBackWheelDrive: boolean): void {
|
|
this._hasBackWheelDrive = hasBackWheelDrive;
|
|
this.invalidateShape();
|
|
}
|
|
|
|
hasFrontWheelDrive(): boolean {
|
|
return this._hasFrontWheelDrive;
|
|
}
|
|
|
|
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
|
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
|
this.invalidateShape();
|
|
}
|
|
|
|
getPitchRollAngleMax(): float {
|
|
return this._pitchRollAngleMax;
|
|
}
|
|
|
|
setPitchRollAngleMax(pitchRollAngleMax: float): void {
|
|
this._pitchRollAngleMax = pitchRollAngleMax;
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
this._vehicleController
|
|
.GetConstraint()
|
|
.SetMaxPitchRollAngle(pitchRollAngleMax);
|
|
}
|
|
|
|
isOnFloor() {
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
for (let index = 0; index < 4; index++) {
|
|
const wheel = Jolt.castObject(
|
|
this._vehicleController.GetConstraint().GetWheel(index),
|
|
Jolt.WheelWV
|
|
);
|
|
if (wheel.HasContact()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_updateWheels() {
|
|
if (!this._vehicleController) {
|
|
return;
|
|
}
|
|
const physics3D = this.getPhysics3D();
|
|
if (!physics3D) {
|
|
return;
|
|
}
|
|
const { behavior } = physics3D;
|
|
|
|
// Retrieved the dimensions set by `createShape`.
|
|
const halfVehicleWidth = behavior._shapeHalfWidth;
|
|
const halfVehicleHeight = behavior._shapeHalfHeight;
|
|
const halfVehicleDepth = behavior._shapeHalfDepth;
|
|
|
|
const shapeScale =
|
|
behavior.shapeScale * behavior._sharedData.worldInvScale;
|
|
|
|
const wheelRadius = this._wheelRadius * shapeScale;
|
|
const wheelWidth = this._wheelWidth * shapeScale;
|
|
const backWheelOffsetX =
|
|
halfVehicleWidth - wheelRadius + this._backWheelOffsetX * shapeScale;
|
|
const frontWheelOffsetX =
|
|
halfVehicleWidth - wheelRadius + this._frontWheelOffsetX * shapeScale;
|
|
const wheelOffsetY =
|
|
halfVehicleHeight - wheelWidth / 2 + this._wheelOffsetY * shapeScale;
|
|
// Put the wheels center at the bottom of the car physics shape.
|
|
const wheelOffsetZ =
|
|
halfVehicleDepth +
|
|
(-behavior.shapeOffsetZ + this._wheelOffsetZ) * shapeScale;
|
|
const suspensionMinLength = wheelRadius;
|
|
const suspensionMaxLength = 1.5 * suspensionMinLength;
|
|
|
|
const constraint = this._vehicleController.GetConstraint();
|
|
const fl = constraint.GetWheel(0).GetSettings();
|
|
fl.mPosition = this.getVec3(
|
|
frontWheelOffsetX,
|
|
-wheelOffsetY,
|
|
-wheelOffsetZ
|
|
);
|
|
const fr = constraint.GetWheel(1).GetSettings();
|
|
fr.mPosition = this.getVec3(
|
|
frontWheelOffsetX,
|
|
wheelOffsetY,
|
|
-wheelOffsetZ
|
|
);
|
|
const bl = constraint.GetWheel(2).GetSettings();
|
|
bl.mPosition = this.getVec3(
|
|
-backWheelOffsetX,
|
|
-wheelOffsetY,
|
|
-wheelOffsetZ
|
|
);
|
|
const br = constraint.GetWheel(3).GetSettings();
|
|
br.mPosition = this.getVec3(
|
|
-backWheelOffsetX,
|
|
wheelOffsetY,
|
|
-wheelOffsetZ
|
|
);
|
|
for (let index = 0; index < 4; index++) {
|
|
const wheel = Jolt.castObject(
|
|
constraint.GetWheel(index),
|
|
Jolt.WheelWV
|
|
).GetSettings();
|
|
wheel.mRadius = wheelRadius;
|
|
wheel.mWidth = wheelWidth;
|
|
wheel.mSuspensionMinLength = suspensionMinLength;
|
|
wheel.mSuspensionMaxLength = suspensionMaxLength;
|
|
wheel.mMaxBrakeTorque = this._brakeTorqueMax;
|
|
if (index >= 2) {
|
|
wheel.mMaxHandBrakeTorque = this._handBrakeTorqueMax;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
gdjs.registerBehavior(
|
|
'Physics3D::PhysicsCar3D',
|
|
gdjs.PhysicsCar3DRuntimeBehavior
|
|
);
|
|
|
|
/** @category Behaviors > Physics 3D */
|
|
export namespace PhysicsCar3DRuntimeBehavior {
|
|
export class VehicleBodyUpdater
|
|
implements gdjs.Physics3DRuntimeBehavior.BodyUpdater
|
|
{
|
|
carBehavior: gdjs.PhysicsCar3DRuntimeBehavior;
|
|
physicsBodyUpdater: gdjs.Physics3DRuntimeBehavior.BodyUpdater;
|
|
|
|
constructor(
|
|
carBehavior: gdjs.PhysicsCar3DRuntimeBehavior,
|
|
physicsBodyUpdater: gdjs.Physics3DRuntimeBehavior.BodyUpdater
|
|
) {
|
|
this.carBehavior = carBehavior;
|
|
this.physicsBodyUpdater = physicsBodyUpdater;
|
|
}
|
|
|
|
private getVec3(x: float, y: float, z: float): Jolt.Vec3 {
|
|
const tempVec3 = this.carBehavior._sharedData._tempVec3;
|
|
tempVec3.Set(x, y, z);
|
|
return tempVec3;
|
|
}
|
|
|
|
createAndAddBody(): Jolt.Body | null {
|
|
const physics3D = this.carBehavior.getPhysics3D();
|
|
if (!physics3D) {
|
|
return null;
|
|
}
|
|
const { behavior } = physics3D;
|
|
const { _sharedData } = this.carBehavior;
|
|
|
|
const carShape = behavior.createShape();
|
|
|
|
// Create car body
|
|
const carBodySettings = new Jolt.BodyCreationSettings(
|
|
carShape,
|
|
this.carBehavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
|
behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
|
Jolt.EMotionType_Dynamic,
|
|
behavior.getBodyLayer()
|
|
);
|
|
carBodySettings.mFriction = behavior.friction;
|
|
carBodySettings.mRestitution = behavior.restitution;
|
|
carBodySettings.mLinearDamping = behavior.linearDamping;
|
|
carBodySettings.mAngularDamping = behavior.angularDamping;
|
|
carBodySettings.mGravityFactor = behavior.gravityScale;
|
|
if (behavior.massOverride > 0) {
|
|
carBodySettings.mOverrideMassProperties =
|
|
Jolt.EOverrideMassProperties_CalculateInertia;
|
|
carBodySettings.mMassPropertiesOverride.mMass = behavior.massOverride;
|
|
}
|
|
const carBody = _sharedData.bodyInterface.CreateBody(carBodySettings);
|
|
Jolt.destroy(carBodySettings);
|
|
_sharedData.bodyInterface.AddBody(
|
|
carBody.GetID(),
|
|
Jolt.EActivation_Activate
|
|
);
|
|
|
|
// Create vehicle constraint
|
|
const vehicle = new Jolt.VehicleConstraintSettings();
|
|
vehicle.mUp = this.getVec3(0, 0, 1);
|
|
vehicle.mForward = this.getVec3(1, 0, 0);
|
|
vehicle.mMaxPitchRollAngle = gdjs.toRad(
|
|
this.carBehavior._pitchRollAngleMax
|
|
);
|
|
|
|
const FL_WHEEL = 0;
|
|
const FR_WHEEL = 1;
|
|
const BL_WHEEL = 2;
|
|
const BR_WHEEL = 3;
|
|
{
|
|
const setupWheel = (wheelS: Jolt.WheelSettingsWV) => {
|
|
wheelS.mWheelUp = this.getVec3(0, 0, 1);
|
|
wheelS.mWheelForward = this.getVec3(1, 0, 0);
|
|
wheelS.mSuspensionDirection = this.getVec3(0, 0, -1);
|
|
wheelS.mSteeringAxis = this.getVec3(0, 0, 1);
|
|
};
|
|
|
|
// vehicle.mWheels is a Array of Ref (smart pointer).
|
|
// They are destroyed automatically.
|
|
vehicle.mWheels.clear();
|
|
|
|
const fl = new Jolt.WheelSettingsWV();
|
|
fl.mMaxSteerAngle = gdjs.toRad(this.carBehavior._steerAngleMax);
|
|
// Front wheel doesn't have hand brake
|
|
fl.mMaxHandBrakeTorque = 0.0;
|
|
setupWheel(fl);
|
|
vehicle.mWheels.push_back(fl);
|
|
|
|
const fr = new Jolt.WheelSettingsWV();
|
|
fr.mMaxSteerAngle = gdjs.toRad(this.carBehavior._steerAngleMax);
|
|
fr.mMaxHandBrakeTorque = 0.0;
|
|
setupWheel(fr);
|
|
vehicle.mWheels.push_back(fr);
|
|
|
|
const bl = new Jolt.WheelSettingsWV();
|
|
bl.mMaxSteerAngle = 0.0;
|
|
setupWheel(bl);
|
|
vehicle.mWheels.push_back(bl);
|
|
|
|
const br = new Jolt.WheelSettingsWV();
|
|
br.mMaxSteerAngle = 0.0;
|
|
setupWheel(br);
|
|
vehicle.mWheels.push_back(br);
|
|
}
|
|
|
|
const controllerSettings = new Jolt.WheeledVehicleControllerSettings();
|
|
controllerSettings.mEngine.mMaxTorque =
|
|
this.carBehavior._engineTorqueMax;
|
|
controllerSettings.mEngine.mMaxRPM = this.carBehavior._engineSpeedMax;
|
|
controllerSettings.mEngine.mInertia = this.carBehavior._engineInertia;
|
|
// vehicle.mController is a Ref (smart pointer).
|
|
// It is destroyed with the vehicle.
|
|
vehicle.mController = controllerSettings;
|
|
|
|
const fourWheelDrive =
|
|
this.carBehavior._hasBackWheelDrive &&
|
|
this.carBehavior._hasFrontWheelDrive;
|
|
controllerSettings.mDifferentials.clear();
|
|
|
|
// Front differential
|
|
if (this.carBehavior._hasFrontWheelDrive) {
|
|
const frontWheelDrive = new Jolt.VehicleDifferentialSettings();
|
|
frontWheelDrive.mLeftWheel = FL_WHEEL;
|
|
frontWheelDrive.mRightWheel = FR_WHEEL;
|
|
if (fourWheelDrive) {
|
|
// Split engine torque when 4 wheels drive
|
|
frontWheelDrive.mEngineTorqueRatio = 0.5;
|
|
}
|
|
controllerSettings.mDifferentials.push_back(frontWheelDrive);
|
|
}
|
|
|
|
// Rear differential
|
|
if (this.carBehavior._hasBackWheelDrive) {
|
|
const rearWheelDrive = new Jolt.VehicleDifferentialSettings();
|
|
rearWheelDrive.mLeftWheel = BL_WHEEL;
|
|
rearWheelDrive.mRightWheel = BR_WHEEL;
|
|
if (fourWheelDrive) {
|
|
// Split engine torque when 4 wheels drive
|
|
rearWheelDrive.mEngineTorqueRatio = 0.5;
|
|
}
|
|
controllerSettings.mDifferentials.push_back(rearWheelDrive);
|
|
}
|
|
|
|
// Anti-roll bars
|
|
vehicle.mAntiRollBars.clear();
|
|
const frontRollBar = new Jolt.VehicleAntiRollBar();
|
|
frontRollBar.mLeftWheel = FL_WHEEL;
|
|
frontRollBar.mRightWheel = FR_WHEEL;
|
|
const rearRollBar = new Jolt.VehicleAntiRollBar();
|
|
rearRollBar.mLeftWheel = BL_WHEEL;
|
|
rearRollBar.mRightWheel = BR_WHEEL;
|
|
vehicle.mAntiRollBars.push_back(frontRollBar);
|
|
vehicle.mAntiRollBars.push_back(rearRollBar);
|
|
|
|
const constraint = new Jolt.VehicleConstraint(carBody, vehicle);
|
|
Jolt.destroy(vehicle);
|
|
|
|
// VehicleConstraint.mVehicleCollisionTester is a RefConst (smart pointer)
|
|
// previously created ones are destroyed automatically.
|
|
const vehicleCollisionTester =
|
|
new Jolt.VehicleCollisionTesterCastCylinder(
|
|
behavior.getBodyLayer(),
|
|
0.05
|
|
);
|
|
constraint.SetVehicleCollisionTester(vehicleCollisionTester);
|
|
this.carBehavior._vehicleCollisionTester = vehicleCollisionTester;
|
|
|
|
if (this.carBehavior._vehicleController) {
|
|
const constraint =
|
|
this.carBehavior._vehicleController.GetConstraint();
|
|
this.carBehavior._sharedData.physicsSystem.RemoveConstraint(
|
|
constraint
|
|
);
|
|
}
|
|
_sharedData.physicsSystem.AddConstraint(constraint);
|
|
this.carBehavior._vehicleController = Jolt.castObject(
|
|
constraint.GetController(),
|
|
Jolt.WheeledVehicleController
|
|
);
|
|
if (this.carBehavior._stepListener) {
|
|
_sharedData.physicsSystem.RemoveStepListener(
|
|
this.carBehavior._stepListener
|
|
);
|
|
}
|
|
this.carBehavior._stepListener = new Jolt.VehicleConstraintStepListener(
|
|
constraint
|
|
);
|
|
_sharedData.physicsSystem.AddStepListener(
|
|
this.carBehavior._stepListener
|
|
);
|
|
this.carBehavior._updateWheels();
|
|
this.carBehavior._updateGearRatios();
|
|
this.carBehavior._updateReverseGearRatios();
|
|
return carBody;
|
|
}
|
|
|
|
updateObjectFromBody() {
|
|
this.physicsBodyUpdater.updateObjectFromBody();
|
|
}
|
|
|
|
updateBodyFromObject() {
|
|
this.physicsBodyUpdater.updateBodyFromObject();
|
|
}
|
|
|
|
recreateShape() {
|
|
this.physicsBodyUpdater.recreateShape();
|
|
this.carBehavior._updateWheels();
|
|
}
|
|
|
|
destroyBody() {
|
|
this.carBehavior._destroyBody();
|
|
this.physicsBodyUpdater.destroyBody();
|
|
}
|
|
}
|
|
}
|
|
}
|