@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
242 lines (173 loc) • 6.19 kB
JavaScript
import { QuadTreeNode } from "../../../../geom/2d/quad-tree/QuadTreeNode.js";
import Vector2 from "../../../../geom/Vector2.js";
import { resolveBoxOverlapUsingForce } from "../../../../graph/layout/box/resolveBoxOverlapUsingForce.js";
import { seededRandom } from "../../../../math/random/seededRandom.js";
export class ConnectedBoxLayouter {
/**
*
* @type {BoxLayoutSpec[]}
*/
boxes = [];
/**
*
* @type {ConnectionLayoutSpec[]}
*/
connections = [];
/**
*
* @type {QuadTreeNode<>}
* @private
*/
__spatial_index_connections = new QuadTreeNode();
/**
* @param {BoxLayoutSpec[]} boxes
* @param {ConnectionLayoutSpec[]} connections
*/
initialize(boxes, connections) {
this.boxes = boxes;
this.connections = connections;
this.__spatial_index_connections.clear();
const n = connections.length;
for (let i = 0; i < n; i++) {
const connectionLayoutSpec = connections[i];
}
}
__resolve_box_overlap(max_steps = 3) {
const forces = this.boxes.map(n => new Vector2());
const bound_array = this.boxes.map(b => b.bounds);
let step_count = 0;
let overlapMoves = -1;
while (step_count < max_steps && overlapMoves !== 0) {
step_count++;
overlapMoves = resolveBoxOverlapUsingForce(forces, bound_array);
}
}
/**
*
* @param {BoxLayoutSpec} box
* @returns {number}
* @private
*/
__compute_box_cost(box) {
let result = 0;
// fitness is based on how long distances between connection points are
const connections = box.connections;
const n = connections.length;
for (let i = 0; i < n; i++) {
const connection = connections[i];
const source = connection.source;
const _x0 = source.point.x + source.box.bounds.x0;
const _y0 = source.point.y + source.box.bounds.y0;
const target = connection.target;
const _x1 = target.point.x + target.box.bounds.x0;
const _y1 = target.point.y + target.box.bounds.y0;
const dx = _x1 - _x0;
const dy = _y1 - _y0;
result += dx * dx + dy * dy;
}
return result;
}
__execute_swaps() {
let b0_cost;
let b1_cost;
let swap_count = 0;
const boxes = this.boxes;
const box_count = boxes.length;
for (let i = 0; i < box_count - 1; i++) {
const b0 = boxes[i];
if (b0.locked) {
continue;
}
b0_cost = this.__compute_box_cost(b0);
for (let j = i + 1; j < box_count; j++) {
const b1 = boxes[j];
if (b1.locked) {
continue;
}
b1_cost = this.__compute_box_cost(b1);
// attempt swap
const temp_x = b0.bounds.x0;
const temp_y = b0.bounds.y0;
b0.bounds.setPosition(b1.bounds.x0, b1.bounds.y0);
b1.bounds.setPosition(temp_x, temp_y);
const b0_cost_after = this.__compute_box_cost(b0);
const b1_cost_after = this.__compute_box_cost(b0);
if (b0_cost + b1_cost <= b0_cost_after + b1_cost_after) {
// failure, revert
b1.bounds.setPosition(b0.bounds.x0, b0.bounds.y0);
b0.bounds.setPosition(temp_x, temp_y);
} else {
// update cost
b0_cost = b0_cost_after;
swap_count++;
}
}
}
return swap_count;
}
/**
*
* @param {ConnectionLayoutSpec} spec
* @param {number} strength
* @private
*/
__pull_connection(spec, strength) {
const source = spec.source;
const _x0 = source.point.x + source.box.bounds.x0;
const _y0 = source.point.y + source.box.bounds.y0;
const target = spec.target;
const _x1 = target.point.x + target.box.bounds.x0;
const _y1 = target.point.y + target.box.bounds.y0;
const dx = _x1 - _x0;
const dy = _y1 - _y0;
const step_x = dx * strength * 0.5;
const step_y = dy * strength * 0.5;
source.box.bounds.move(step_x, step_y);
target.box.bounds.move(-step_x, -step_y);
}
__pull_connections(strength) {
const connections = this.connections;
const n = connections.length;
for (let i = 0; i < n; i++) {
const spec = connections[i];
this.__pull_connection(spec, strength);
}
}
__execute_many_swaps(limit) {
for (let i = 0; i < limit; i++) {
const swaps = this.__execute_swaps();
if (swaps === 0) {
return;
}
}
}
__execute_initial_placement() {
const random = seededRandom(0);
const boxes = this.boxes;
const box_count = boxes.length;
for (let i = 0; i < box_count; i++) {
const box = boxes[i];
const bounds = box.bounds;
const w = bounds.x1 - bounds.x0;
const h = bounds.y1 - bounds.y0;
const x0 = (random() - 0.5) * 2 * w;
const y0 = (random() - 0.5) * 2 * h;
bounds.set(x0, y0, x0 + w, y0 + h);
}
}
step() {
this.__execute_many_swaps(10);
this.__pull_connections(0.1);
this.__resolve_box_overlap(10);
}
layout() {
this.__execute_initial_placement();
for (let i = 0; i < 7; i++) {
this.__pull_connections(0.2);
this.__resolve_box_overlap(10);
}
for (let i = 0; i < 10; i++) {
this.step();
}
}
}