avocado-old/packages/physics/dummy/world.js

190 lines
5.4 KiB
JavaScript
Raw Normal View History

2019-03-23 18:26:35 -05:00
import * as I from 'immutable';
2019-03-24 00:46:31 -05:00
import {arrayUnique} from '@avocado/core';
2019-03-23 18:26:35 -05:00
import {Rectangle, QuadTree, Vector} from '@avocado/math';
2019-03-24 00:46:31 -05:00
import {Body} from './body';
2019-03-24 01:16:24 -05:00
import {AbstractWorld} from '../abstract/world';
2019-03-23 18:26:35 -05:00
2019-03-24 01:16:24 -05:00
export class World extends AbstractWorld {
2019-03-23 18:26:35 -05:00
constructor() {
2019-03-24 01:16:24 -05:00
super();
2019-03-23 18:26:35 -05:00
this.bodies = [];
this.quadTree = new QuadTree();
2019-03-24 00:43:07 -05:00
this.quadTreeNodes = new Map();
2019-03-23 18:26:35 -05:00
}
addBody(body) {
this.bodies.push(body);
// Add to quad tree.
this.addQuadTreeNodes(body);
body.on('positionChanged', () => {
this.removeQuadTreeNodes(body);
this.addQuadTreeNodes(body);
});
}
addQuadTreeNodes(body) {
// 4 points.
const aabb = body.aabb;
2019-03-23 18:26:35 -05:00
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]);
2019-03-23 20:04:59 -05:00
const points = [
2019-03-23 18:26:35 -05:00
upperLeft,
upperRight,
lowerLeft,
lowerRight,
2019-03-23 20:04:59 -05:00
];
const nodes = points.map((point) => [...point, body]);
2019-03-23 20:05:48 -05:00
this.quadTreeNodes = this.quadTreeNodes.set(body, nodes);
2019-03-23 18:26:35 -05:00
for (const node of nodes) {
this.quadTree.add(node);
}
}
2019-03-24 01:16:24 -05:00
createBody(shape) {
return new Body(shape);
2019-03-23 18:26:35 -05:00
}
removeBody(body) {
2019-03-24 01:16:24 -05:00
super.removeBody(body);
2019-03-23 18:26:35 -05:00
const index = this.bodies.indexOf(body);
if (-1 === index) {
return;
}
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.
2019-03-24 00:43:07 -05:00
for (const entity of this.entities.values()) {
2019-03-23 18:26:35 -05:00
const body = entity.body;
const translation = Vector.add(
Vector.scale(body.impulse, elapsed),
body.force,
);
body.position = Vector.add(body.position, translation);
2019-03-23 18:26:35 -05:00
}
// Contact checks.
const allContacts = new Map();
const checkedSoFar = new Map();
2019-03-24 00:43:07 -05:00
for (const entity of this.entities.values()) {
2019-03-23 18:26:35 -05:00
const body = entity.body;
// Find bodies in AABB.
const aabb = body.aabb;
2019-03-23 18:26:35 -05:00
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
const checkSet = new Set();
checkedSoFar.set(entity, checkSet);
2019-03-23 18:26:35 -05:00
for (const otherBody of otherBodies) {
2019-03-24 00:43:07 -05:00
const otherEntity = this.entities.get(otherBody);
if (otherEntity) {
// Only one check per pair.
const otherCheckSet = checkedSoFar.get(otherEntity);
if (otherCheckSet) {
if (otherCheckSet.has(entity)) {
continue;
}
}
// Self contacts.
2019-03-23 20:05:48 -05:00
let contacts = allContacts.get(entity);
if (!contacts) {
contacts = I.Set();
}
contacts = contacts.add(otherEntity);
allContacts.set(entity, contacts);
// Other contacts.
let otherContacts = allContacts.get(otherEntity);
if (!otherContacts) {
otherContacts = I.Set();
}
otherContacts = otherContacts.add(entity);
allContacts.set(otherEntity, otherContacts);
// Mark as checked.
2019-03-24 00:43:07 -05:00
checkSet.add(otherEntity);
2019-03-23 18:26:35 -05:00
}
}
2019-03-23 20:05:48 -05:00
if (!allContacts.get(entity)) {
allContacts.set(entity, I.Set());
}
2019-03-23 18:26:35 -05:00
}
// Report collisions.
2019-03-24 00:43:07 -05:00
for (const entity of this.entities.values()) {
2019-03-23 18:26:35 -05:00
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.
2019-03-24 00:43:07 -05:00
for (const entity of this.entities.values()) {
2019-03-23 18:26:35 -05:00
const body = entity.body;
// Contact? Undo impulse.
const contacts = body.contacts;
if (contacts.size > 0) {
const translation = Vector.add(
Vector.scale(body.impulse, elapsed),
body.force,
);
body.position = Vector.sub(body.position, translation);
// Check all contacts and see if impulse vector can be fixed.
for (const axe of [0, 1]) {
const anyContact = false;
const potentialTranslation = [0, 0];
potentialTranslation[axe] = translation[axe];
body.position = Vector.add(body.position, potentialTranslation);
for (const contact of contacts) {
const contactBody = contact.body;
if (Rectangle.isTouching(body.aabb, contactBody.aabb)) {
anyContact = true;
break;
}
}
if (anyContact) {
body.position = Vector.sub(body.position, potentialTranslation);
}
}
2019-03-23 18:26:35 -05:00
}
}
// Drop transforms.
2019-03-24 00:43:07 -05:00
for (const {body} of this.entities.values()) {
2019-03-23 18:26:35 -05:00
body.force = [0, 0];
body.impulse = [0, 0];
}
2019-03-24 01:16:24 -05:00
super.tick(elapsed);
2019-03-23 18:26:35 -05:00
}
}