Add collision conditions.

This commit is contained in:
Davy Hélard
2024-11-06 17:39:59 +01:00
parent 9742310d1d
commit bf3c4ba2e8
3 changed files with 307 additions and 12 deletions

View File

@@ -591,6 +591,67 @@ module.exports = {
.getCodeExtraInformation()
.setFunctionName('applyImpulseAtCenter');
// Collision
extension
.addCondition(
'Collision',
_('Collision'),
_('Check if two objects collide.'),
_('_PARAM0_ is colliding with _PARAM2_'),
'',
'res/physics32.png',
'res/physics32.png'
)
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DTools.js')
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js')
.setFunctionName('gdjs.physics3d.objectsCollide');
extension
.addCondition(
'CollisionStarted',
_('Collision started'),
_('Check if two objects just started colliding during this frame.'),
_('_PARAM0_ started colliding with _PARAM2_'),
_('Collision'),
'res/physics32.png',
'res/physics32.png'
)
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DTools.js')
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js')
.setFunctionName('gdjs.physics3d.haveObjectsStartedColliding');
extension
.addCondition(
'CollisionStopped',
_('Collision stopped'),
_('Check if two objects just stopped colliding at this frame.'),
_('_PARAM0_ stopped colliding with _PARAM2_'),
_('Collision'),
'res/physics32.png',
'res/physics32.png'
)
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addParameter('objectList', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DTools.js')
.addIncludeFile('Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js')
.setFunctionName('gdjs.physics3d.haveObjectsStoppedColliding');
return extension;
},

View File

@@ -1,5 +1,11 @@
/// <reference path="./jolt-physics.d.ts" />
namespace Jolt {
export interface Body {
gdjsAssociatedBehavior: gdjs.Physics3DRuntimeBehavior | null;
}
}
namespace gdjs {
gdjs.registerAsynchronouslyLoadingLibraryPromise(
new Promise((resolve) => {
@@ -92,6 +98,9 @@ namespace gdjs {
jolt: Jolt.JoltInterface;
physicsSystem: Jolt.PhysicsSystem;
bodyInterface: Jolt.BodyInterface;
/** Contact listener to keep track of current collisions */
contactListener: Jolt.ContactListenerJS;
behaviorsByBodyID = new Map<number, gdjs.Physics3DRuntimeBehavior>();
stepped: boolean = false;
/**
@@ -119,6 +128,71 @@ namespace gdjs {
new Jolt.Vec3(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);
// Get associated behaviors
const behaviorA = bodyA.gdjsAssociatedBehavior;
const behaviorB = bodyB.gdjsAssociatedBehavior;
if (!behaviorA || !behaviorB) {
return;
}
behaviorA.onContactBegin(behaviorB);
behaviorB.onContactBegin(behaviorA);
this.behaviorsByBodyID.set(
bodyA.GetID().GetIndexAndSequenceNumber(),
behaviorA
);
this.behaviorsByBodyID.set(
bodyB.GetID().GetIndexAndSequenceNumber(),
behaviorB
);
};
this.contactListener.OnContactRemoved = (
subShapePairPtr: number
): void => {
const subShapePair = Jolt.wrapPointer(
subShapePairPtr,
Jolt.SubShapeIDPair
);
const behaviorA = this.behaviorsByBodyID.get(
subShapePair.GetBody1ID().GetIndexAndSequenceNumber()
);
const behaviorB = this.behaviorsByBodyID.get(
subShapePair.GetBody2ID().GetIndexAndSequenceNumber()
);
if (!behaviorA || !behaviorB) {
return;
}
behaviorA.onContactEnd(behaviorB);
behaviorB.onContactEnd(behaviorA);
};
this.contactListener.OnContactPersisted = (
inBody1: number,
inBody2: number,
inManifold: number,
ioSettings: number
): void => {};
this.contactListener.OnContactValidate = (
inBody1: number,
inBody2: number,
inBaseOffset: number,
inCollisionResult: number
): number => {
return Jolt.ValidateResult_AcceptAllContactsForThisBodyPair;
};
}
// (string)jointId -> (b2Joint)b2Joint
@@ -160,10 +234,10 @@ namespace gdjs {
*/
resetStartedAndEndedCollisions(): void {
for (const physicsBehavior of this._registeredBehaviors) {
// TODO
// physicsBehavior.contactsStartedThisFrame.length = 0;
// physicsBehavior.contactsEndedThisFrame.length = 0;
physicsBehavior.contactsStartedThisFrame.length = 0;
physicsBehavior.contactsEndedThisFrame.length = 0;
}
this.behaviorsByBodyID.clear();
}
/**
@@ -216,6 +290,30 @@ namespace gdjs {
gravityScale: float;
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> = [];
destroyedDuringFrameLogic: boolean;
_body: Jolt.Body | null = null;
@@ -308,19 +406,17 @@ namespace gdjs {
this._sharedData.bodyInterface.DestroyBody(this._body.GetID());
this._body = null;
}
// TODO
// this.contactsEndedThisFrame.length = 0;
// this.contactsStartedThisFrame.length = 0;
// this.currentContacts.length = 0;
this.contactsEndedThisFrame.length = 0;
this.contactsStartedThisFrame.length = 0;
this.currentContacts.length = 0;
}
onActivate() {
this._sharedData.addToBehaviorsList(this);
// TODO
// this.contactsEndedThisFrame.length = 0;
// this.contactsStartedThisFrame.length = 0;
// this.currentContacts.length = 0;
this.contactsEndedThisFrame.length = 0;
this.contactsStartedThisFrame.length = 0;
this.currentContacts.length = 0;
this.updateBodyFromObject();
}
@@ -490,10 +586,14 @@ namespace gdjs {
bodyCreationSettings.mLinearDamping = this.linearDamping;
bodyCreationSettings.mAngularDamping = this.angularDamping;
bodyCreationSettings.mGravityFactor = this.gravityScale;
// TODO Collision between 2 non-dynamic body should be checked during the
// collision condition to improve efficiency.
bodyCreationSettings.mCollideKinematicVsNonDynamic = true;
const bodyInterface = this._sharedData.bodyInterface;
this._body = bodyInterface.CreateBody(bodyCreationSettings);
bodyInterface.AddBody(this._body.GetID(), Jolt.EActivation_Activate);
this._body.gdjsAssociatedBehavior = this;
this._objectOldWidth = this.owner3D.getWidth();
this._objectOldHeight = this.owner3D.getHeight();
@@ -706,6 +806,92 @@ namespace gdjs {
this.getVec3(impulseX, impulseY, impulseZ)
);
}
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);
}
}
static areObjectsColliding(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
): boolean {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics3DRuntimeBehavior | null;
if (!behavior1) return false;
if (
behavior1.currentContacts.some((behavior) => behavior.owner === object2)
) {
return true;
}
// If a contact has started at this frame and ended right away, it
// won't appear in current contacts but the condition should return
// true anyway.
if (
behavior1.contactsStartedThisFrame.some(
(behavior) => behavior.owner === object2
)
) {
return true;
}
// No contact found
return false;
}
static hasCollisionStartedBetween(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
): boolean {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics3DRuntimeBehavior | null;
if (!behavior1) return false;
return behavior1.contactsStartedThisFrame.some(
(behavior) => behavior.owner === object2
);
}
static hasCollisionStoppedBetween(
object1: gdjs.RuntimeObject,
object2: gdjs.RuntimeObject,
behaviorName: string
): boolean {
// Test if the second object is in the list of contacts of the first one
const behavior1 = object1.getBehavior(
behaviorName
) as Physics3DRuntimeBehavior | null;
if (!behavior1) return false;
return behavior1.contactsEndedThisFrame.some(
(behavior) => behavior.owner === object2
);
}
}
gdjs.registerBehavior(

View File

@@ -1,3 +1,51 @@
namespace gdjs {
export namespace physics3d {}
export namespace physics3d {
export const objectsCollide = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.areObjectsColliding,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStartedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.hasCollisionStartedBetween,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
export const haveObjectsStoppedColliding = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName: string,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
behaviorName2: string,
inverted: boolean
) {
return gdjs.evtTools.object.twoListsTest(
gdjs.Physics3DRuntimeBehavior.hasCollisionStoppedBetween,
objectsLists1,
objectsLists2,
inverted,
behaviorName
);
};
}
}