feat: physics

This commit is contained in:
cha0s 2019-03-22 11:25:20 -05:00
parent 31efc515c3
commit d64ec2d10c
6 changed files with 338 additions and 0 deletions

24
packages/physics/index.js Normal file
View File

@ -0,0 +1,24 @@
import {PolygonShape} from './polygon';
export {PolygonShape};
import {RectangleShape} from './rectangle';
export {RectangleShape};
import {ShapeList} from './list';
export {ShapeList};
export function shapeFromJSON(json) {
let shape;
switch (json.type) {
case 'list':
shape = new ShapeList();
break;
case 'polygon':
shape = new PolygonShape();
break;
case 'rectangle':
shape = new RectangleShape();
break;
}
return shape.fromJSON(json);
}

110
packages/physics/list.js Normal file
View File

@ -0,0 +1,110 @@
import {Vector} from '@avocado/math';
import {Shape} from './shape';
import {shapeFromJSON} from './index';
export class ShapeList extends Shape {
constructor() {
super();
this._shapes = [];
this.on('shapesChanged', () => {
this._shapes.forEach((shape) => {
shape.off('aabbChanged');
shape.on('aabbChanged', () => {
this.emit('aabbChanged');
});
});
});
this.on('originChanged', (oldOrigin) => {
this._shapes.forEach((shape) => {
shape.emit('parentOriginChanged', oldOrigin, this.origin);
});
});
this.on('rotationChanged', (oldRotation) => {
this._shapes.forEach((shape) => {
shape.emit('parentRotationChanged', oldRotation, this.rotation);
});
});
this.on('scaleChanged', (oldScale) => {
this._shapes.forEach((shape) => {
shape.emit('parentScaleChanged', oldScale, this.scale);
});
});
}
*[Symbol.iterator]() {
for (const shape of this_shapes) {
yield shape;
}
}
get aabb() {
if (0 === this._shapes.length) {
return [0, 0, 0, 0];
}
const min = [Infinity, Infinity];
const max = [-Infinity, -Infinity];
for (const shape of this._shapes) {
const aabb = shape.aabb;
min[0] = aabb[0] < min[0] ? aabb[0] : min[0];
min[1] = aabb[1] < min[1] ? aabb[1] : min[1];
max[0] = aabb[0] > max[0] ? aabb[0] : max[0];
max[1] = aabb[1] > max[1] ? aabb[1] : max[1];
}
return Rectangle.translated(
[min[0], min[1], max[0] - min[0], max[1] - min[1]],
this.position
);
}
fromJSON(json) {
super.fromJSON(json);
if (json.shapes) {
const shapes = [];
json.shapes.forEach((shape) => {
shapes.push(shapeFromJSON(shape));
});
this._shapes = shapes;
this.emit('shapesChanged');
}
return this;
}
addShape(shape) {
this._shapes.push(shape);
this.emit('shapesChanged');
}
intersects(list) {
if (0 === this._shapes.length) {
return false;
}
if (!Rectangle.intersects(this.aabb, list.aabb)) {
return false;
}
// TODO: Quadtrees?
for (const shape of this._shapes) {
for (const otherShape of list._shapes) {
if (Rectangle.intersects(
Rectangle.translated(shape.aabb, this.position),
Rectangle.translated(otherShape.aabb, otherShape.position)
)) {
return true;
}
}
}
return false;
}
toJSON() {
return {
...super.toJSON(),
type: 'polygon',
shapes: this._shapes.map((shape) => {
return shape.toJSON();
}),
}
}
}

View File

@ -0,0 +1,7 @@
{
"name": "@avocado/physics",
"version": "1.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT"
}

View File

@ -0,0 +1,76 @@
import {Rectangle, Vector, Vertice} from '@avocado/math';
import {Shape} from './shape';
export class PolygonShape extends Shape {
constructor() {
super();
this._translatedVertices = [];
this._vertices = [];
this.on([
'parentOriginChanged',
'parentRotationChanged',
'parentScaleChanged',
'originChanged',
'rotationChanged',
'scaleChanged',
'verticesChanged',
], () => {
const parentOrigin = this.parent ? this.parent.origin : [0, 0];
const origin = Vector.add(this.origin, parentOrigin);
const parentRotation = this.parent ? this.parent.rotation : 0;
const rotation = this.rotation + parentRotation;
const parentScale = this.parent ? this.parent.scale : 1;
const scale = this.scale * parentScale;
this._translatedVertices = this._vertices.map((vertice) => {
return Vertice.translate(vertice, origin, rotation, scale);
});
this.emit('aabbChanged');
});
}
get aabb() {
if (0 === this._vertices.length) {
return [0, 0, 0, 0];
}
const min = [Infinity, Infinity];
const max = [-Infinity, -Infinity];
for (const vertice of this._translatedVertices) {
min[0] = vertice[0] < min[0] ? vertice[0] : min[0];
min[1] = vertice[1] < min[1] ? vertice[1] : min[1];
max[0] = vertice[0] > max[0] ? vertice[0] : max[0];
max[1] = vertice[1] > max[1] ? vertice[1] : max[1];
}
return Rectangle.translated(
[min[0], min[1], max[0] - min[0], max[1] - min[1]],
this.position
);
}
fromJSON(json) {
super.fromJSON(json);
if (json.vertices) {
this.vertices = json.vertices;
}
return this;
}
get vertices() {
return this._vertices;
}
set vertices(vertices) {
this._vertices = [...vertices];
this.emit('verticesChanged');
}
toJSON() {
return {
...super.toJSON(),
type: 'polygon',
vertices: [...this._vertices]
}
}
}

View File

@ -0,0 +1,58 @@
import {compose} from '@avocado/core';
import {Vector} from '@avocado/math';
import {PolygonShape} from './polygon';
import { Rectangle } from '../math';
const decorate = compose(
Vector.Mixin('size', 'width', 'height', {
default: [0, 0],
track: true,
}),
);
export class RectangleShape extends decorate(PolygonShape) {
constructor() {
super();
this.on(['positionChanged', 'sizeChanged'], () => {
const halfSize = Vector.scale(this.size, 0.5);
const width = this.width - 0.001;
const height = this.height - 0.001;
const position = Vector.add(this.position, Vector.scale(halfSize, -1));
this.vertices = [
position,
Vector.add(position, [width, 0]),
Vector.add(position, [width, height]),
Vector.add(position, [0, height]),
];
});
}
// get aabb() {
// return Rectangle.compose([0, 0], this.size);
// }
fromJSON(json) {
super.fromJSON(json);
const keys = [
'size',
];
for (const key of keys) {
if (json[key]) {
this[key] = json[key];
}
}
return this;
}
toJSON() {
return {
...super.toJSON(),
type: 'rectangle',
position: this.position,
size: this.size,
}
}
}

63
packages/physics/shape.js Normal file
View File

@ -0,0 +1,63 @@
import {compose} from '@avocado/core';
import {Vector} from '@avocado/math';
import {EventEmitter, Property} from '@avocado/mixins';
const decorate = compose(
EventEmitter,
Property('parent'),
Property('rotation', {
default: 0,
track: true,
}),
Property('scale', {
default: 1,
track: true,
}),
Vector.Mixin('origin', 'originX', 'originY', {
default: [0, 0],
track: true,
}),
Vector.Mixin('position', 'x', 'y', {
default: [0, 0],
track: true,
}),
)
class ShapeBase {
get aabb() {
return [0, 0, 0, 0];
}
fromJSON(json) {
const keys = [
'origin',
'position',
'rotation',
'scale',
]
for (const key of keys) {
if (json[key]) {
this[key] = json[key];
}
}
return this;
}
}
export class Shape extends decorate(ShapeBase) {
constructor() {
super();
this.on([
'originChanged',
'rotationChanged',
'scaleChanged',
], () => {
console.log('change');
this.emit('aabbChanged');
});
}
}