feat: physics
This commit is contained in:
parent
31efc515c3
commit
d64ec2d10c
24
packages/physics/index.js
Normal file
24
packages/physics/index.js
Normal 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
110
packages/physics/list.js
Normal 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();
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
7
packages/physics/package.json
Normal file
7
packages/physics/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@avocado/physics",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "cha0s",
|
||||
"license": "MIT"
|
||||
}
|
76
packages/physics/polygon.js
Normal file
76
packages/physics/polygon.js
Normal 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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
58
packages/physics/rectangle.js
Normal file
58
packages/physics/rectangle.js
Normal 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
63
packages/physics/shape.js
Normal 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');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user