mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Allow grid based object to optimise collision checks (#3245)
* Allow to get the hit boxes for a given area. * Also remove useless array and wrong sharing of vertices in Light object renderers Only show in developer changelog
This commit is contained in:
@@ -87,12 +87,16 @@ namespace gdjs {
|
||||
_manager: any;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
this._manager = LightObstaclesManager.getManager(runtimeScene);
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
// Make sure the obstacle is or is not in the obstacles manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removeObstacle(this);
|
||||
|
@@ -8,19 +8,24 @@ namespace gdjs {
|
||||
export class LightRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.LightRuntimeObject;
|
||||
_runtimeScene: gdjs.RuntimeScene;
|
||||
_manager: any;
|
||||
_manager: gdjs.LightObstaclesManager;
|
||||
_radius: number;
|
||||
_color: any;
|
||||
_color: [number, number, number];
|
||||
_texture: PIXI.Texture | null = null;
|
||||
_center: any;
|
||||
_defaultVertexBuffer: any;
|
||||
_vertexBuffer: any;
|
||||
_indexBuffer: any;
|
||||
_center: Float32Array;
|
||||
_defaultVertexBuffer: Float32Array;
|
||||
_vertexBuffer: Float32Array;
|
||||
_indexBuffer: Uint16Array;
|
||||
_light: PIXI.Mesh<PIXI.Shader> | null = null;
|
||||
_isPreview: boolean;
|
||||
_debugMode: any = null;
|
||||
_debugMode: boolean = false;
|
||||
_debugLight: PIXI.Container | null = null;
|
||||
_debugGraphics: PIXI.Graphics | null = null;
|
||||
|
||||
/**
|
||||
* A polygon updated when vertices of the light are computed
|
||||
* to be a polygon bounding the light and its obstacles.
|
||||
*/
|
||||
_lightBoundingPoly: gdjs.Polygon;
|
||||
|
||||
constructor(
|
||||
@@ -53,13 +58,8 @@ namespace gdjs {
|
||||
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
||||
this.updateMesh();
|
||||
this._isPreview = runtimeScene.getGame().isPreview();
|
||||
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
|
||||
|
||||
this._lightBoundingPoly = new gdjs.Polygon();
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._lightBoundingPoly.vertices.push(
|
||||
runtimeObject.getHitBoxes()[0].vertices[i]
|
||||
);
|
||||
}
|
||||
this.updateDebugMode();
|
||||
|
||||
// Objects will be added in lighting layer, this is just to maintain consistency.
|
||||
@@ -85,10 +85,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
static _computeClosestIntersectionPoint(
|
||||
lightObject,
|
||||
angle,
|
||||
polygons,
|
||||
boundingSquareHalfDiag
|
||||
lightObject: gdjs.LightRuntimeObject,
|
||||
angle: float,
|
||||
polygons: Array<gdjs.Polygon>,
|
||||
boundingSquareHalfDiag: float
|
||||
) {
|
||||
const centerX = lightObject.getX();
|
||||
const centerY = lightObject.getY();
|
||||
@@ -308,8 +308,8 @@ namespace gdjs {
|
||||
// and instead use a subarray. Otherwise, allocate new array buffers as
|
||||
// there would be memory wastage.
|
||||
let isSubArrayUsed = false;
|
||||
let vertexBufferSubArray = null;
|
||||
let indexBufferSubArray = null;
|
||||
let vertexBufferSubArray: Float32Array | null = null;
|
||||
let indexBufferSubArray: Uint16Array | null = null;
|
||||
if (this._vertexBuffer.length > 2 * verticesCount + 2) {
|
||||
if (this._vertexBuffer.length < 4 * verticesCount + 4) {
|
||||
isSubArrayUsed = true;
|
||||
@@ -368,7 +368,7 @@ namespace gdjs {
|
||||
* Computes the vertices of mesh using raycasting.
|
||||
* @returns the vertices of mesh.
|
||||
*/
|
||||
_computeLightVertices(): Array<any> {
|
||||
_computeLightVertices(): Array<FloatPoint> {
|
||||
const lightObstacles: gdjs.BehaviorRBushAABB<
|
||||
LightObstacleRuntimeBehavior
|
||||
>[] = [];
|
||||
@@ -379,38 +379,47 @@ namespace gdjs {
|
||||
lightObstacles
|
||||
);
|
||||
}
|
||||
const searchAreaLeft = this._object.getX() - this._radius;
|
||||
const searchAreaTop = this._object.getY() - this._radius;
|
||||
const searchAreaRight = this._object.getX() + this._radius;
|
||||
const searchAreaBottom = this._object.getY() + this._radius;
|
||||
|
||||
// Bail out early if there are no obstacles.
|
||||
if (lightObstacles.length === 0) {
|
||||
// @ts-ignore TODO the array should probably be pass as a parameter.
|
||||
return lightObstacles;
|
||||
}
|
||||
|
||||
// Synchronize light bounding polygon with the hitbox.
|
||||
const lightHitboxPoly = this._object.getHitBoxes()[0];
|
||||
// Note: we suppose the hitbox is always a single rectangle.
|
||||
const objectHitBox = this._object.getHitBoxes()[0];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 2; j++) {
|
||||
this._lightBoundingPoly.vertices[i][j] =
|
||||
lightHitboxPoly.vertices[i][j];
|
||||
}
|
||||
this._lightBoundingPoly.vertices[i][0] = objectHitBox.vertices[i][0];
|
||||
this._lightBoundingPoly.vertices[i][1] = objectHitBox.vertices[i][1];
|
||||
}
|
||||
const obstaclesCount = lightObstacles.length;
|
||||
const obstacleHitBoxes = new Array(obstaclesCount);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
obstacleHitBoxes[i] = lightObstacles[i].behavior.owner.getHitBoxes();
|
||||
}
|
||||
const obstaclePolygons: Array<any> = [];
|
||||
|
||||
// Create the list of polygons to compute the light vertices
|
||||
const obstaclePolygons: Array<gdjs.Polygon> = [];
|
||||
obstaclePolygons.push(this._lightBoundingPoly);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
const noOfHitBoxes = obstacleHitBoxes[i].length;
|
||||
for (let j = 0; j < noOfHitBoxes; j++) {
|
||||
obstaclePolygons.push(obstacleHitBoxes[i][j]);
|
||||
for (let i = 0; i < lightObstacles.length; i++) {
|
||||
const obstacleHitBoxes = lightObstacles[
|
||||
i
|
||||
].behavior.owner.getHitBoxesAround(
|
||||
searchAreaLeft,
|
||||
searchAreaTop,
|
||||
searchAreaRight,
|
||||
searchAreaBottom
|
||||
);
|
||||
for (const hitbox of obstacleHitBoxes) {
|
||||
obstaclePolygons.push(hitbox);
|
||||
}
|
||||
}
|
||||
|
||||
let maxX = this._object.x + this._radius;
|
||||
let minX = this._object.x - this._radius;
|
||||
let maxY = this._object.y + this._radius;
|
||||
let minY = this._object.y - this._radius;
|
||||
const flattenVertices: Array<any> = [];
|
||||
const flattenVertices: Array<FloatPoint> = [];
|
||||
for (let i = 1; i < obstaclePolygons.length; i++) {
|
||||
const vertices = obstaclePolygons[i].vertices;
|
||||
const verticesCount = vertices.length;
|
||||
@@ -452,6 +461,7 @@ namespace gdjs {
|
||||
(maxY - this._object.y) * (maxY - this._object.y)
|
||||
)
|
||||
);
|
||||
// Add this._object.hitBoxes vertices.
|
||||
for (let i = 0; i < 4; i++) {
|
||||
flattenVertices.push(obstaclePolygons[0].vertices[i]);
|
||||
}
|
||||
|
@@ -149,10 +149,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the light obstacles manager if objects with the behavior exist, null otherwise.
|
||||
* @returns gdjs.LightObstaclesManager if it exists, otherwise null.
|
||||
* Get the light obstacles manager.
|
||||
* @returns the light obstacles manager.
|
||||
*/
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager | null {
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager {
|
||||
return this._obstaclesManager;
|
||||
}
|
||||
|
||||
|
@@ -770,7 +770,12 @@ namespace gdjs {
|
||||
return context;
|
||||
}
|
||||
|
||||
for (const hitbox of platformObject.getHitBoxes()) {
|
||||
for (const hitbox of platformObject.getHitBoxesAround(
|
||||
context.ownerMinX,
|
||||
context.headMinY,
|
||||
context.ownerMaxX,
|
||||
context.floorMaxY
|
||||
)) {
|
||||
if (hitbox.vertices.length < 3) {
|
||||
continue;
|
||||
}
|
||||
|
@@ -1338,11 +1338,14 @@ namespace gdjs {
|
||||
|
||||
//Hit boxes and collision :
|
||||
/**
|
||||
* Get the hit boxes for the object.<br>
|
||||
* The default implementation returns a basic bouding box based the size (getWidth and
|
||||
* Get all the hit boxes for the object.
|
||||
*
|
||||
* For collision checks, {@link getHitBoxesAround} should be used instead.
|
||||
*
|
||||
* The default implementation returns a basic bounding box based the size (getWidth and
|
||||
* getHeight) and the center point of the object (getCenterX and getCenterY).
|
||||
*
|
||||
* You should probably redefine updateHitBoxes instead of this function.
|
||||
* You should probably redefine {@link updateHitBoxes} instead of this function.
|
||||
*
|
||||
* @return An array composed of polygon.
|
||||
*/
|
||||
@@ -1359,6 +1362,41 @@ namespace gdjs {
|
||||
return this.hitBoxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return at least all the hit boxes that overlap a given area.
|
||||
*
|
||||
* The hit boxes don't need to actually overlap the area,
|
||||
* (i.e: it's correct to return more hit boxes than those in the specified area)
|
||||
* but the ones that do must be returned.
|
||||
*
|
||||
* The default implementation returns the same as {@link getHitBoxes}.
|
||||
*
|
||||
* This method can be overridden by grid based objects (or other objects
|
||||
* that can quickly compute which hitboxes are touching a given area)
|
||||
* to optimize collision checks.
|
||||
*
|
||||
* When overriding this method, the following ones should be overridden too:
|
||||
* * {@link getHitBoxes}
|
||||
* * {@link getAABB}
|
||||
* * {@link updateHitBoxes}
|
||||
* * {@link updateAABB}
|
||||
*
|
||||
* @param left bound of the area in scene coordinates
|
||||
* @param top bound of the area in scene coordinates
|
||||
* @param right bound of the area in scene coordinates
|
||||
* @param bottom bound of the area in scene coordinates
|
||||
*
|
||||
* @return at least all the hit boxes that overlap a given area.
|
||||
*/
|
||||
getHitBoxesAround(
|
||||
left: float,
|
||||
top: float,
|
||||
right: float,
|
||||
bottom: float
|
||||
): Iterable<gdjs.Polygon> {
|
||||
return this.getHitBoxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the hit boxes for the object.
|
||||
*
|
||||
@@ -1758,14 +1796,32 @@ namespace gdjs {
|
||||
moveXArray.length = 0;
|
||||
moveYArray.length = 0;
|
||||
|
||||
// We can assume that the moving object is not grid based,
|
||||
// so there is no need for optimization:
|
||||
// getHitBoxes can be called directly.
|
||||
const hitBoxes = this.getHitBoxes();
|
||||
let aabb: AABB | null = null;
|
||||
|
||||
// Check if their is a collision with each object
|
||||
// Check if there is a collision with each object
|
||||
for (const otherObject of objects) {
|
||||
if (otherObject.id === this.id) {
|
||||
continue;
|
||||
}
|
||||
const otherHitBoxes = otherObject.getHitBoxes();
|
||||
let otherHitBoxesArray = otherObject.getHitBoxes();
|
||||
let otherHitBoxes: Iterable<gdjs.Polygon> = otherHitBoxesArray;
|
||||
if (otherHitBoxesArray.length > 4) {
|
||||
// The other object has a lot of hit boxes.
|
||||
// Try to reduce the amount of hitboxes to check.
|
||||
if (!aabb) {
|
||||
aabb = this.getAABB();
|
||||
}
|
||||
otherHitBoxes = otherObject.getHitBoxesAround(
|
||||
aabb.min[0],
|
||||
aabb.min[1],
|
||||
aabb.max[0],
|
||||
aabb.max[1]
|
||||
);
|
||||
}
|
||||
for (const hitBox of hitBoxes) {
|
||||
for (const otherHitBox of otherHitBoxes) {
|
||||
const result = gdjs.Polygon.collisionTest(
|
||||
@@ -1798,7 +1854,11 @@ namespace gdjs {
|
||||
moveXArray.length = 0;
|
||||
moveYArray.length = 0;
|
||||
|
||||
// We can assume that the moving object is not grid based
|
||||
// So there is no need for optimization
|
||||
// getHitBoxes can be called directly.
|
||||
const hitBoxes = this.getHitBoxes();
|
||||
let aabb: AABB | null = null;
|
||||
|
||||
for (const name in objectsLists.items) {
|
||||
if (objectsLists.items.hasOwnProperty(name)) {
|
||||
@@ -1809,7 +1869,21 @@ namespace gdjs {
|
||||
if (otherObject.id === this.id) {
|
||||
continue;
|
||||
}
|
||||
const otherHitBoxes = otherObject.getHitBoxes();
|
||||
let otherHitBoxesArray = otherObject.getHitBoxes();
|
||||
let otherHitBoxes: Iterable<gdjs.Polygon> = otherHitBoxesArray;
|
||||
if (otherHitBoxesArray.length > 4) {
|
||||
// The other object has a lot of hit boxes.
|
||||
// Try to reduce the amount of hitboxes to check.
|
||||
if (!aabb) {
|
||||
aabb = this.getAABB();
|
||||
}
|
||||
otherHitBoxes = otherObject.getHitBoxesAround(
|
||||
aabb.min[0],
|
||||
aabb.min[1],
|
||||
aabb.max[0],
|
||||
aabb.max[1]
|
||||
);
|
||||
}
|
||||
for (const hitBox of hitBoxes) {
|
||||
for (const otherHitBox of otherHitBoxes) {
|
||||
const result = gdjs.Polygon.collisionTest(
|
||||
@@ -2098,10 +2172,13 @@ namespace gdjs {
|
||||
)
|
||||
);
|
||||
|
||||
const diffX =
|
||||
obj1.getDrawableX() + o1centerX - (obj2.getDrawableX() + o2centerX);
|
||||
const diffY =
|
||||
obj1.getDrawableY() + o1centerY - (obj2.getDrawableY() + o2centerY);
|
||||
const o1AbsoluteCenterX = obj1.getDrawableX() + o1centerX;
|
||||
const o1AbsoluteCenterY = obj1.getDrawableY() + o1centerY;
|
||||
const o2AbsoluteCenterX = obj2.getDrawableX() + o2centerX;
|
||||
const o2AbsoluteCenterY = obj2.getDrawableY() + o2centerY;
|
||||
|
||||
const diffX = o1AbsoluteCenterX - o2AbsoluteCenterX;
|
||||
const diffY = o1AbsoluteCenterY - o2AbsoluteCenterY;
|
||||
if (
|
||||
Math.sqrt(diffX * diffX + diffY * diffY) >
|
||||
obj1BoundingRadius + obj2BoundingRadius
|
||||
@@ -2110,16 +2187,24 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// Do a real check if necessary.
|
||||
const hitBoxes1 = obj1.getHitBoxes();
|
||||
const hitBoxes2 = obj2.getHitBoxes();
|
||||
for (let k = 0, lenBoxes1 = hitBoxes1.length; k < lenBoxes1; ++k) {
|
||||
for (let l = 0, lenBoxes2 = hitBoxes2.length; l < lenBoxes2; ++l) {
|
||||
const hitBoxes1 = obj1.getHitBoxesAround(
|
||||
o2AbsoluteCenterX - obj2BoundingRadius,
|
||||
o2AbsoluteCenterY - obj2BoundingRadius,
|
||||
o2AbsoluteCenterX + obj2BoundingRadius,
|
||||
o2AbsoluteCenterY + obj2BoundingRadius
|
||||
);
|
||||
const hitBoxes2 = obj2.getHitBoxesAround(
|
||||
o1AbsoluteCenterX - obj1BoundingRadius,
|
||||
o1AbsoluteCenterY - obj1BoundingRadius,
|
||||
o1AbsoluteCenterX + obj1BoundingRadius,
|
||||
o1AbsoluteCenterY + obj1BoundingRadius
|
||||
);
|
||||
|
||||
for (const hitBox1 of hitBoxes1) {
|
||||
for (const hitBox2 of hitBoxes2) {
|
||||
if (
|
||||
gdjs.Polygon.collisionTest(
|
||||
hitBoxes1[k],
|
||||
hitBoxes2[l],
|
||||
ignoreTouchingEdges
|
||||
).collision
|
||||
gdjs.Polygon.collisionTest(hitBox1, hitBox2, ignoreTouchingEdges)
|
||||
.collision
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -2177,9 +2262,9 @@ namespace gdjs {
|
||||
|
||||
// Do a real check if necessary.
|
||||
let testSqDist = closest ? raySqBoundingRadius : 0;
|
||||
const hitBoxes = this.getHitBoxes();
|
||||
for (let i = 0; i < hitBoxes.length; i++) {
|
||||
const res = gdjs.Polygon.raycastTest(hitBoxes[i], x, y, endX, endY);
|
||||
const hitBoxes = this.getHitBoxesAround(x, y, endX, endY);
|
||||
for (const hitBox of hitBoxes) {
|
||||
const res = gdjs.Polygon.raycastTest(hitBox, x, y, endX, endY);
|
||||
if (res.collision) {
|
||||
if (closest && res.closeSqDist < testSqDist) {
|
||||
testSqDist = res.closeSqDist;
|
||||
@@ -2267,9 +2352,9 @@ namespace gdjs {
|
||||
* @return true if the point is inside the object collision hitboxes.
|
||||
*/
|
||||
isCollidingWithPoint(pointX: float, pointY: float): boolean {
|
||||
const hitBoxes = this.getHitBoxes();
|
||||
for (let i = 0; i < this.hitBoxes.length; ++i) {
|
||||
if (gdjs.Polygon.isPointInside(hitBoxes[i], pointX, pointY)) {
|
||||
const hitBoxes = this.getHitBoxesAround(pointX, pointY, pointX, pointY);
|
||||
for (const hitBox of hitBoxes) {
|
||||
if (gdjs.Polygon.isPointInside(hitBox, pointX, pointY)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user