Skip to content

3D physics vehicle #7479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
May 14, 2025
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
ce7b46b
WIP 3D vehicle
D8H Mar 17, 2025
5e484e6
Upgrade to Jolt 0.34.0
D8H Mar 18, 2025
1c0b019
Fix car forward direction.
D8H Mar 18, 2025
865c625
Allow to offset the shape and the center of mass.
D8H Mar 20, 2025
c6d20d1
Fix wheel settings.
D8H Mar 20, 2025
e7dda7c
Try to find playable settings.
D8H Mar 21, 2025
6e2c704
Remove useless code.
D8H Mar 21, 2025
730ac11
Add a steering speed.
D8H Mar 21, 2025
948aa74
Add more gravity.
D8H Mar 21, 2025
ddd9a07
Use the default gear ratios.
D8H Apr 1, 2025
5084a6f
Add stick controls.
D8H Apr 2, 2025
c1f9794
Optimize memory usage.
D8H Apr 2, 2025
31c3292
Handle size changes.
D8H Apr 2, 2025
ba9a69d
Fix some memory leaks.
D8H Apr 3, 2025
447c06d
Update to Jolt.js 0.35.0
D8H Apr 3, 2025
2a1fd29
Fix warnings about characters not handling mass center offset.
D8H Apr 4, 2025
4987195
Fix a crash when a car is destroyed.
D8H Apr 4, 2025
885cd09
Add some properties.
D8H Apr 4, 2025
f607761
Add properties for gears and wheels.
D8H Apr 7, 2025
ea84ff1
Add properties for 4 wheels drive.
D8H Apr 8, 2025
6f9afdd
Add an icon.
D8H Apr 8, 2025
18b3494
Add brake properties.
D8H Apr 8, 2025
f452544
Use a cylinder cast.
D8H Apr 9, 2025
c193cda
Fix character memory.
D8H Apr 9, 2025
60731a4
Add override annotations.
D8H Apr 9, 2025
7045f8d
Add instructions for dashboard and turbo.
D8H Apr 9, 2025
42589aa
Move the mass override to the Physics behavior
D8H Apr 10, 2025
eeb1256
Add todo
D8H Apr 11, 2025
5a409c0
Rename the behavior from Vehicle to Car
D8H Apr 13, 2025
f0f04b6
Use template property values for steering
D8H Apr 13, 2025
0d2269e
Remove a todo.
D8H Apr 13, 2025
df6f58a
Remove logs.
D8H Apr 13, 2025
465761f
Fix some memory leaks.
D8H Apr 14, 2025
82f5473
Fix another memory leak.
D8H Apr 14, 2025
fabe151
Fix a memory crash.
D8H Apr 14, 2025
ba78e5c
Add a "is on the floor" condition.
D8H Apr 15, 2025
4904a5d
Add some comments.
D8H Apr 15, 2025
282eb96
Add some properties in multiplayer data.
D8H Apr 15, 2025
eabc3fe
Format
D8H Apr 15, 2025
8a8fb21
Review changes
D8H May 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,048 changes: 1,046 additions & 2 deletions Extensions/Physics3DBehavior/JsExtension.js

Large diffs are not rendered by default.

119 changes: 100 additions & 19 deletions Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts
Original file line number Diff line number Diff line change
@@ -305,15 +305,22 @@ namespace gdjs {
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;
private shapeScale: number = 1;
shapeScale: number = 1;

/**
* Array containing the beginning of contacts reported by onContactBegin. Each contact
@@ -348,7 +355,10 @@ namespace gdjs {
/**
* When set to `true` the shape will be recreated before the next physics step.
*/
private _needToRecreateShape: boolean = false;
_needToRecreateShape: boolean = false;

_shapeHalfWidth: float = 0;
_shapeHalfHeight: float = 0;
/**
* Used by {@link gdjs.PhysicsCharacter3DRuntimeBehavior} to convert coordinates.
*/
@@ -392,7 +402,14 @@ namespace gdjs {
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);
@@ -634,6 +651,39 @@ namespace gdjs {
}

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;
@@ -679,6 +729,8 @@ namespace gdjs {
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 =
@@ -694,8 +746,12 @@ namespace gdjs {
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' ? radius : capsuleDepth / 2;
this.shapeOrientation === 'Z' ? capsuleDepth / 2 : radius;
} else if (this.shape === 'Cylinder') {
const radius =
shapeDimensionA > 0
@@ -716,8 +772,12 @@ namespace gdjs {
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' ? radius : cylinderDepth / 2;
this.shapeOrientation === 'Z' ? cylinderDepth / 2 : radius;
} else {
// Create a 'Sphere' by default.
const radius =
@@ -728,17 +788,20 @@ namespace gdjs {
: 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;
const rotatedShapeSettings = new Jolt.RotatedTranslatedShapeSettings(
this.getVec3(0, 0, 0),
return new Jolt.RotatedTranslatedShapeSettings(
this.getVec3(
this.shapeOffsetX * shapeScale,
this.shapeOffsetY * shapeScale,
this.shapeOffsetZ * shapeScale
),
quat,
shapeSettings
);
const rotatedShape = rotatedShapeSettings.Create().Get();
Jolt.destroy(rotatedShapeSettings);
return rotatedShape;
}

private _getShapeOrientationQuat(): Jolt.Quat {
@@ -933,7 +996,7 @@ namespace gdjs {
);
}

getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
result.Set(
this.owner3D.getCenterXInScene() * this._sharedData.worldInvScale,
this.owner3D.getCenterYInScene() * this._sharedData.worldInvScale,
@@ -942,7 +1005,7 @@ namespace gdjs {
return result;
}

getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
_getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
const threeObject = this.owner3D.get3DRendererObject();
result.Set(
threeObject.quaternion.x,
@@ -953,7 +1016,7 @@ namespace gdjs {
return result;
}

moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
_moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
this.owner3D.setCenterXInScene(
physicsPosition.GetX() * this._sharedData.worldScale
);
@@ -965,7 +1028,7 @@ namespace gdjs {
);
}

moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
_moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
const threeObject = this.owner3D.get3DRendererObject();
threeObject.quaternion.x = physicsRotation.GetX();
threeObject.quaternion.y = physicsRotation.GetY();
@@ -1103,6 +1166,18 @@ namespace gdjs {
this._needToRecreateShape = true;
}

getMassOverride(): float {
return this.massOverride;
}

setMassOverride(mass: float): void {
if (this.massOverride === mass) {
return;
}
this.massOverride = mass;
this._needToRecreateBody = true;
}

getFriction(): float {
return this.friction;
}
@@ -1784,8 +1859,8 @@ namespace gdjs {
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._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
behavior.bodyType === 'Static'
? Jolt.EMotionType_Static
: behavior.bodyType === 'Kinematic'
@@ -1806,6 +1881,12 @@ namespace gdjs {
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);
@@ -1824,8 +1905,8 @@ namespace gdjs {
// 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());
behavior._moveObjectToPhysicsPosition(_body.GetPosition());
behavior._moveObjectToPhysicsRotation(_body.GetRotation());
}
}

@@ -1847,8 +1928,8 @@ namespace gdjs {
) {
_sharedData.bodyInterface.SetPositionAndRotationWhenChanged(
body.GetID(),
this.behavior.getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
this.behavior.getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
this.behavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
this.behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
Jolt.EActivation_Activate
);
}
Loading