feat: collider
This commit is contained in:
parent
463d9b5858
commit
cca1445043
75
app/ecs-components/collider.js
Normal file
75
app/ecs-components/collider.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import Component from '@/ecs/component.js';
|
||||||
|
import {intersects} from '@/util/math.js';
|
||||||
|
|
||||||
|
import vector2d from './helpers/vector-2d';
|
||||||
|
|
||||||
|
export default class Collider extends Component {
|
||||||
|
instanceFromSchema() {
|
||||||
|
const {ecs} = this;
|
||||||
|
return class ColliderInstance extends super.instanceFromSchema() {
|
||||||
|
isCollidingWith(other) {
|
||||||
|
const {aabb, aabbs} = this;
|
||||||
|
const {aabb: otherAabb, aabbs: otherAabbs} = other;
|
||||||
|
if (!intersects(aabb, otherAabb)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const aabb of aabbs) {
|
||||||
|
for (const otherAabb of otherAabbs) {
|
||||||
|
if (intersects(aabb, otherAabb)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
isWithin(query) {
|
||||||
|
const {aabb, aabbs} = this;
|
||||||
|
if (!intersects(aabb, query)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const aabb of aabbs) {
|
||||||
|
if (intersects(aabb, query)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
recalculateAabbs() {
|
||||||
|
const {Position: {x: px, y: py}} = ecs.get(this.entity);
|
||||||
|
this.aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
|
||||||
|
this.aabbs = [];
|
||||||
|
const {bodies} = this;
|
||||||
|
for (const points of bodies) {
|
||||||
|
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
||||||
|
for (const point of points) {
|
||||||
|
const x = point.x + px;
|
||||||
|
const y = point.y + py;
|
||||||
|
if (x < x0) x0 = x;
|
||||||
|
if (x < this.aabb.x0) this.aabb.x0 = x;
|
||||||
|
if (x > x1) x1 = x;
|
||||||
|
if (x > this.aabb.x1) this.aabb.x1 = x;
|
||||||
|
if (y < y0) y0 = y;
|
||||||
|
if (y < this.aabb.y0) this.aabb.y0 = y;
|
||||||
|
if (y > y1) y1 = y;
|
||||||
|
if (y > this.aabb.y1) this.aabb.y1 = y;
|
||||||
|
}
|
||||||
|
this.aabbs.push({
|
||||||
|
x0: x0 > x1 ? x1 : x0,
|
||||||
|
x1: x0 > x1 ? x0 : x1,
|
||||||
|
y0: y0 > y1 ? y1 : y0,
|
||||||
|
y1: y0 > y1 ? y0 : y1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static properties = {
|
||||||
|
bodies: {
|
||||||
|
type: 'array',
|
||||||
|
subtype: {
|
||||||
|
type: 'array',
|
||||||
|
subtype: vector2d('int16'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
90
app/ecs-systems/colliders.js
Normal file
90
app/ecs-systems/colliders.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import {System} from '@/ecs/index.js';
|
||||||
|
import SpatialHash from '@/util/spatial-hash.js';
|
||||||
|
|
||||||
|
export default class Colliders extends System {
|
||||||
|
|
||||||
|
hash;
|
||||||
|
|
||||||
|
deindex(entities) {
|
||||||
|
super.deindex(entities);
|
||||||
|
for (const id of entities) {
|
||||||
|
this.hash.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get priority() {
|
||||||
|
return {
|
||||||
|
after: 'IntegratePhysics',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
reindex(entities) {
|
||||||
|
for (const id of entities) {
|
||||||
|
if (1 === id) {
|
||||||
|
this.hash = new SpatialHash(this.ecs.get(1).AreaSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.reindex(entities);
|
||||||
|
for (const id of entities) {
|
||||||
|
this.updateHash(this.ecs.get(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHash(entity) {
|
||||||
|
if (!entity.Collider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entity.Collider.recalculateAabbs();
|
||||||
|
this.hash.update(entity.Collider.aabb, entity.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
const seen = {};
|
||||||
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
|
if (seen[entity.id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen[entity.id] = true;
|
||||||
|
if (!entity.Collider) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.updateHash(entity);
|
||||||
|
for (const other of this.within(entity.Collider.aabb)) {
|
||||||
|
if (seen[other.id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen[other.id] = true;
|
||||||
|
if (!other.Collider) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entity.Collider.isCollidingWith(other.Collider)) {
|
||||||
|
console.log('collide', entity, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
within(query) {
|
||||||
|
const {x0, x1, y0, y1} = query;
|
||||||
|
const [cx0, cy0] = this.hash.chunkIndex(x0, y0);
|
||||||
|
const [cx1, cy1] = this.hash.chunkIndex(x1, y1);
|
||||||
|
const seen = {};
|
||||||
|
const within = new Set();
|
||||||
|
for (let cy = cy0; cy <= cy1; ++cy) {
|
||||||
|
for (let cx = cx0; cx <= cx1; ++cx) {
|
||||||
|
for (const id of this.hash.chunks[cx][cy]) {
|
||||||
|
if (seen[id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen[id] = true;
|
||||||
|
const entity = this.ecs.get(id);
|
||||||
|
if (entity.Collider.isWithin(query)) {
|
||||||
|
within.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return within;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -167,6 +167,7 @@ export default class Engine {
|
||||||
'PlantGrowth',
|
'PlantGrowth',
|
||||||
'FollowCamera',
|
'FollowCamera',
|
||||||
'VisibleAabbs',
|
'VisibleAabbs',
|
||||||
|
'Collliders',
|
||||||
'ControlDirection',
|
'ControlDirection',
|
||||||
'SpriteDirection',
|
'SpriteDirection',
|
||||||
'RunAnimations',
|
'RunAnimations',
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
export function distance({x: lx, y: ly}, {x: rx, y: ry}) {
|
||||||
|
const xd = lx - rx;
|
||||||
|
const yd = ly - ry;
|
||||||
|
return Math.sqrt(xd * xd + yd * yd);
|
||||||
|
}
|
||||||
|
|
||||||
export function intersects(l, r) {
|
export function intersects(l, r) {
|
||||||
if (l.x0 > r.x1) return false;
|
if (l.x0 > r.x1) return false;
|
||||||
if (l.y0 > r.y1) return false;
|
if (l.y0 > r.y1) return false;
|
||||||
|
|
|
@ -9,6 +9,16 @@ if (projected?.length > 0) {
|
||||||
const [, direction] = Sprite.animation.split(':')
|
const [, direction] = Sprite.animation.split(':')
|
||||||
|
|
||||||
const plant = {
|
const plant = {
|
||||||
|
Collider: {
|
||||||
|
bodies: [
|
||||||
|
[
|
||||||
|
{x: -8, y: -8},
|
||||||
|
{x: 8, y: -8},
|
||||||
|
{x: -8, y: 8},
|
||||||
|
{x: 8, y: 8},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
Interactive: {
|
Interactive: {
|
||||||
interactScript: '/assets/tomato-plant/interact.js',
|
interactScript: '/assets/tomato-plant/interact.js',
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user