avocado-old/packages/physics/dummy.js
2019-03-23 20:04:59 -05:00

187 lines
4.7 KiB
JavaScript

import * as I from 'immutable';
import {arrayUnique, compose} from '@avocado/core';
import {Rectangle, QuadTree, Vector} from '@avocado/math';
import {EventEmitter} from '@avocado/mixins';
const decorate = compose(
EventEmitter,
Vector.Mixin('position', 'x', 'y', {
default: [0, 0],
}),
);
class BodyBase {
constructor(entity) {
this.force = [0, 0];
this.impulse = [0, 0];
this.contacts = I.Set();
this.entity = entity;
this.shape = entity.shape;
}
applyForce(vector) {
this.force = Vector.add(this.force, vector);
}
applyImpulse(vector) {
this.impulse = Vector.add(this.impulse, vector);
}
}
export class Body extends decorate(BodyBase) {
constructor(entity) {
super(entity);
this.position = entity.position;
}
}
export class World {
constructor() {
this.bodies = [];
this.entities = [];
this.quadTree = new QuadTree();
this.quadTreeNodes = new WeakMap();
}
addBody(body) {
this.bodies.push(body);
if (body.entity) {
this.entities.push(body.entity);
}
// Add to quad tree.
this.addQuadTreeNodes(body);
body.on('positionChanged', () => {
this.removeQuadTreeNodes(body);
this.addQuadTreeNodes(body);
});
}
addQuadTreeNodes(body) {
// 4 points.
const aabb = Rectangle.translated(body.shape.aabb, body.position);
const width = aabb[2] - .0001;
const height = aabb[3] - .0001;
const upperLeft = Rectangle.position(aabb);
const upperRight = Vector.add(upperLeft, [width, 0]);
const lowerLeft = Vector.add(upperLeft, [0, height]);
const lowerRight = Vector.add(upperLeft, [width, height]);
const points = [
upperLeft,
upperRight,
lowerLeft,
lowerRight,
];
const nodes = points.map((point) => [...point, body]);
this.quadTreeNodes.set(body, nodes);
for (const node of nodes) {
this.quadTree.add(node);
}
}
createBodyForEntity(entity) {
return new Body(entity);
}
removeBody(body) {
const index = this.bodies.indexOf(body);
if (-1 === index) {
return;
}
if (body.entity) {
const entities = this.entities;
const entityIndex = entities.indexOf(body.entity);
if (-1 !== entityIndex) {
entities.splice(entityIndex, 1);
}
}
this.removeQuadTreeNodes(body);
this.bodies.splice(index, 1);
}
removeQuadTreeNodes(body) {
const nodes = this.quadTreeNodes.get(body);
if (!nodes) {
return;
}
for (const node of nodes) {
this.quadTree.remove(node);
}
}
tick(elapsed) {
// Apply.
for (const entity of this.entities) {
const body = entity.body;
const impulse = Vector.scale(body.impulse, elapsed);
body.position = Vector.add(body.position, impulse);
body.position = Vector.add(body.position, body.force);
}
// Contact checks.
const allContacts = new Map();
for (const entity of this.entities) {
const body = entity.body;
let thisContacts = I.Set();
// Find bodies in AABB.
const aabb = Rectangle.translated(body.shape.aabb, body.position);
let otherBodies = this.quadTree.search(aabb).map((node) => {
return node.data[2];
});
// Not self.
otherBodies = otherBodies.filter((otherBody) => {
return otherBody !== body;
});
// Uniques only.
otherBodies = arrayUnique(otherBodies);
// TODO: full collision check
for (const otherBody of otherBodies) {
if (otherBody.entity) {
thisContacts = thisContacts.add(otherBody.entity);
}
}
allContacts.set(entity, thisContacts);
}
// Report collisions.
for (const entity of this.entities) {
const oldContacts = entity.body.contacts;
const newContacts = allContacts.get(entity);
for (const contact of newContacts.values()) {
if (!oldContacts.has(contact)) {
entity.emit('contactStart', contact);
}
}
for (const contact of oldContacts.values()) {
if (!newContacts.has(contact)) {
entity.emit('contactEnd', contact);
}
}
entity.body.contacts = newContacts;
}
// Super rudimentary resolving.
for (const entity of this.entities) {
const body = entity.body;
// Contact? Undo impulse.
if (entity.body.contacts.size > 0) {
const impulse = Vector.scale(body.impulse, elapsed);
body.position = Vector.sub(body.position, impulse);
body.position = Vector.sub(body.position, body.force);
}
}
// Drop transforms.
for (const {body} of this.entities) {
body.force = [0, 0];
body.impulse = [0, 0];
}
// Propagate position updates.
for (const entity of this.entities) {
entity.position = entity.body.position;
}
}
}