feat: vertice transformation and collider aabb updates

This commit is contained in:
cha0s 2024-07-25 10:45:23 -05:00
parent b9985a573d
commit 557c8285ba
5 changed files with 178 additions and 26 deletions

View File

@ -1,5 +1,5 @@
import Component from '@/ecs/component.js';
import {distance, intersects} from '@/util/math.js';
import {distance, intersects, transform} from '@/util/math.js';
import vector2d from './helpers/vector-2d';
@ -7,6 +7,8 @@ export default class Collider extends Component {
instanceFromSchema() {
const {ecs} = this;
return class ColliderInstance extends super.instanceFromSchema() {
$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
$$aabbs = [];
collidingWith = {};
get aabb() {
const {Position: {x: px, y: py}} = ecs.get(this.entity);
@ -76,34 +78,36 @@ export default class Collider extends Component {
}
return false;
}
updateAabbs() {
this.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
this.$$aabbs = [];
const {bodies} = this;
const {Direction: {direction = 0} = {}} = ecs.get(this.entity);
for (const body of bodies) {
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
for (const point of transform(body.points, {rotation: direction})) {
const {x, y} = point;
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,
});
}
}
}
}
async load(instance) {
instance.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity};
instance.$$aabbs = [];
const {bodies} = instance;
for (const body of bodies) {
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
for (const point of body.points) {
const x = point.x;
const y = point.y;
if (x < x0) x0 = x;
if (x < instance.$$aabb.x0) instance.$$aabb.x0 = x;
if (x > x1) x1 = x;
if (x > instance.$$aabb.x1) instance.$$aabb.x1 = x;
if (y < y0) y0 = y;
if (y < instance.$$aabb.y0) instance.$$aabb.y0 = y;
if (y > y1) y1 = y;
if (y > instance.$$aabb.y1) instance.$$aabb.y1 = y;
}
instance.$$aabbs.push({
x0: x0 > x1 ? x1 : x0,
x1: x0 > x1 ? x0 : x1,
y0: y0 > y1 ? y1 : y0,
y1: y0 > y1 ? y0 : y1,
});
}
instance.updateAabbs();
// heavy handed...
if ('undefined' !== typeof window) {
return;

View File

@ -40,7 +40,16 @@ export default class Colliders extends System {
tick() {
const collisions = new Map();
for (const entity of this.ecs.changed(['Direction'])) {
if (!entity.Collider) {
continue;
}
entity.Collider.updateAabbs();
}
for (const entity of this.ecs.changed(['Position'])) {
if (!entity.Collider) {
continue;
}
this.updateHash(entity);
}
for (const entity of this.ecs.changed(['Position'])) {

View File

@ -390,6 +390,9 @@ function Ui({disconnected}) {
if (!update) {
continue;
}
if (update.Direction && entity.Collider) {
entity.Collider.updateAabbs();
}
if (update.Sound?.play) {
for (const sound of update.Sound.play) {
(new Audio(sound)).play();

View File

@ -316,3 +316,52 @@ export function removeCollinear([...vertices]) {
}
return trimmed;
}
export function transform(
vertices,
{
rotation = 0,
scale = 1,
translation = {x: 0, y: 0},
origin = {x: 0, y: 0},
},
) {
// nop
if (0 === rotation && 1 === scale && 0 === translation.x && 0 === translation.y) {
return vertices;
}
const transformed = [];
// scale
for (const vertice of vertices) {
if (1 === scale) {
transformed.push({x: vertice.x, y: vertice.y});
continue;
}
transformed.push({
x: origin.x + (vertice.x - origin.x) * scale,
y: origin.y + (vertice.y - origin.y) * scale,
});
}
// rotation
rotation = rotation % TAU;
if (0 !== rotation) {
for (const vertice of transformed) {
let a = rotation + Math.atan2(
vertice.y - origin.y,
vertice.x - origin.x,
);
a = (a >= 0 || a < TAU) ? a : (a % TAU + TAU) % TAU;
const d = distance(vertice, origin);
vertice.x = origin.x + d * Math.cos(a);
vertice.y = origin.y + d * Math.sin(a);
}
}
// translation
if (0 !== translation.x || 0 !== translation.y) {
for (const vertice of transformed) {
vertice.x += translation.x;
vertice.y += translation.y;
}
}
return transformed;
}

87
app/util/math.test.js Normal file
View File

@ -0,0 +1,87 @@
import {expect, test} from 'vitest';
import * as MathUtil from './math.js';
test('transforms vertices', async () => {
const expectCloseTo = (l, r) => {
expect(l.length)
.to.equal(r.length);
for (let i = 0; i < l.length; ++i) {
expect(l[i].x)
.to.be.closeTo(r[i].x, 0.0001);
expect(l[i].y)
.to.be.closeTo(r[i].y, 0.0001);
}
}
const vertices = [
{x: -1, y: -1},
{x: 1, y: -1},
{x: 1, y: 1},
{x: -1, y: 1},
];
expect(MathUtil.transform(vertices, {}))
.to.deep.equal(vertices);
expectCloseTo(
MathUtil.transform(vertices, {scale: 2}),
[
{x: -2, y: -2},
{x: 2, y: -2},
{x: 2, y: 2},
{x: -2, y: 2},
],
);
expectCloseTo(
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI}),
[
{x: 0, y: -Math.sqrt(2)},
{x: Math.sqrt(2), y: 0},
{x: 0, y: Math.sqrt(2)},
{x: -Math.sqrt(2), y: 0},
],
);
expectCloseTo(
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, scale: 2}),
[
{x: 0, y: -Math.sqrt(2) * 2},
{x: Math.sqrt(2) * 2, y: 0},
{x: 0, y: Math.sqrt(2) * 2},
{x: -Math.sqrt(2) * 2, y: 0},
],
);
expectCloseTo(
MathUtil.transform(vertices, {translation: {x: 10, y: 10}}),
[
{x: 9, y: 9},
{x: 11, y: 9},
{x: 11, y: 11},
{x: 9, y: 11},
],
);
expectCloseTo(
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, translation: {x: 10, y: 10}}),
[
{x: 10, y: 10 - Math.sqrt(2)},
{x: 10 + Math.sqrt(2), y: 10},
{x: 10, y: 10 + Math.sqrt(2)},
{x: 10 - Math.sqrt(2), y: 10},
],
);
expectCloseTo(
MathUtil.transform(vertices, {scale: 2, translation: {x: 10, y: 10}}),
[
{x: 8, y: 8},
{x: 12, y: 8},
{x: 12, y: 12},
{x: 8, y: 12},
],
);
expectCloseTo(
MathUtil.transform(vertices, {rotation: MathUtil.QUARTER_PI, scale: 2, translation: {x: 10, y: 10}}),
[
{x: 10, y: 10 - Math.sqrt(2) * 2},
{x: 10 + Math.sqrt(2) * 2, y: 10},
{x: 10, y: 10 + Math.sqrt(2) * 2},
{x: 10 - Math.sqrt(2) * 2, y: 10},
],
);
});