flow like the river

This commit is contained in:
root 2025-11-07 00:06:12 +01:00
commit 013fe673f3
42435 changed files with 5764238 additions and 0 deletions

View file

@ -0,0 +1,10 @@
var test = require('tap').test;
var dimensions = 2;
test('can debug setters', function (t) {
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions, true);
let b = new Body();
t.throws(() => b.pos.x = 'foo', /Cannot set non-numbers to x/);
t.end();
});

View file

@ -0,0 +1,21 @@
var test = require('tap').test;
var dimensions = 2;
var createDragForce = require('../lib/codeGenerators/generateCreateDragForce')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
test('reduces force value', function (t) {
var body = new Body();
body.force.x = 1; body.force.y = 1;
body.velocity.x = 1; body.velocity.y = 1;
var dragForce = createDragForce({ dragCoefficient: 0.1 });
dragForce.update(body);
t.ok(body.force.x < 1 && body.force.y < 1, 'Force value is reduced');
t.end();
});
test('Initialized with default value', function (t) {
t.throws(() => createDragForce());
t.end();
});

View file

@ -0,0 +1,68 @@
var test = require('tap').test;
var dimensions = 2;
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var integrate = require('../lib/codeGenerators/generateIntegrator')(dimensions);
test('Body preserves velocity without forces', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.velocity.x = 1;
integrate([body], timeStep);
t.equal(body.pos.x, 1, 'Should move by 1 pixel on first iteration');
timeStep = 2; // let's increase time step:
integrate([body], timeStep);
t.equal(body.pos.x, 3, 'Should move by 2 pixel on second iteration');
t.end();
});
test('Body gains velocity under force', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.force.x = 0.1;
// F = m * a;
// since mass = 1 => F = a = y';
integrate([body], timeStep);
t.equal(body.velocity.x, 0.1, 'Should increase velocity');
integrate([body], timeStep);
t.equal(body.velocity.x, 0.2, 'Should increase velocity');
// floating point math:
t.ok(0.29 < body.pos.x && body.pos.x < 0.31, 'Position should be at 0.3 now');
t.end();
});
test('No bodies yield 0 movement', function (t) {
var movement = integrate([], 2);
t.equal(movement, 0, 'Nothing has moved');
t.end();
});
test('Body does not move faster than 1px', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.force.x = 2;
integrate([body], timeStep);
t.ok(body.velocity.x <= 1, 'Velocity should be within speed limit');
integrate([body], timeStep);
t.ok(body.velocity.x <= 1, 'Velocity should be within speed limit');
t.end();
});
test('Can get total system movement', function (t) {
var body = new Body();
var timeStep = 1;
body.mass = 1; body.velocity.x = 0.2;
var movement = integrate([body], timeStep);
// to improve performance, integrator does not take square root, thus
// total movement is .2 * .2 = 0.04;
t.ok(0.04 <= movement && movement <= 0.041, 'System should travel by 0.2 pixels');
t.end();
});

78
VISUALIZACION/node_modules/ngraph.forcelayout/test/insert.js generated vendored Executable file
View file

@ -0,0 +1,78 @@
var test = require('tap').test;
var dimensions = 2;
var createQuadTree = require('../lib/codeGenerators/generateQuadTree')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var random = require('ngraph.random').random(42);
test('insert and update update forces', function (t) {
var tree = createQuadTree({}, random);
var body = new Body();
var clone = JSON.parse(JSON.stringify(body));
tree.insertBodies([body]);
tree.updateBodyForce(body);
t.same(body, clone, 'The body should not be changed - there are no forces acting on it');
t.end();
});
test('it can get root', function (t) {
var tree = createQuadTree({}, random);
var body = new Body();
tree.insertBodies([body]);
var root = tree.getRoot();
t.ok(root, 'Root is present');
t.equal(root.body, body, 'Body is initialized');
t.end();
});
test('Two bodies repel each other', function (t) {
var tree = createQuadTree({}, random);
var bodyA = new Body(); bodyA.pos.x = 1; bodyA.pos.y = 0;
var bodyB = new Body(); bodyB.pos.x = 2; bodyB.pos.y = 0;
tree.insertBodies([bodyA, bodyB]);
tree.updateBodyForce(bodyA);
tree.updateBodyForce(bodyB);
// based on our physical model construction forces should be equivalent, with
// opposite sign:
t.ok(bodyA.force.x + bodyB.force.x === 0, 'Forces should be same, with opposite sign');
t.ok(bodyA.force.x !== 0, 'X-force for body A should not be zero');
t.ok(bodyB.force.x !== 0, 'X-force for body B should not be zero');
// On the other hand, our bodies should not move by Y axis:
t.ok(bodyA.force.y === 0, 'Y-force for body A should be zero');
t.ok(bodyB.force.y === 0, 'Y-force for body B should be zero');
t.end();
});
test('Can handle two bodies at the same location', function (t) {
var tree = createQuadTree({}, random);
var bodyA = new Body();
var bodyB = new Body();
tree.insertBodies([bodyA, bodyB]);
tree.updateBodyForce(bodyA);
tree.updateBodyForce(bodyB);
t.end();
});
test('it does not stuck', function(t) {
var count = 60000;
var bodies = [];
for (var i = 0; i < count; ++i) {
bodies.push(new Body(Math.random(), Math.random()));
}
var quadTree = createQuadTree({}, random);
quadTree.insertBodies(bodies);
bodies.forEach(function(body) {
quadTree.updateBodyForce(body);
});
t.ok(1);
t.end();
});

463
VISUALIZACION/node_modules/ngraph.forcelayout/test/layout.js generated vendored Executable file
View file

@ -0,0 +1,463 @@
/* eslint-disable no-shadow */
var test = require('tap').test,
createGraph = require('ngraph.graph'),
createLayout = require('..');
test('it exposes simulator', function(t) {
t.ok(typeof createLayout.simulator === 'function', 'Simulator is exposed');
t.end();
});
test('it returns spring', function(t) {
var g = createGraph();
var layout = createLayout(g);
var link = g.addLink(1, 2);
var springForLink = layout.getSpring(link);
var springForLinkId = layout.getSpring(link.id);
var springForFromTo = layout.getSpring(1, 2);
t.ok(springForLink, 'spring is here');
t.ok(springForLinkId === springForLink, 'Spring is the same');
t.ok(springForFromTo === springForLink, 'Spring is the same');
t.end();
});
test('it returns same position', function(t) {
var g = createGraph();
var layout = createLayout(g);
g.addLink(1, 2);
var firstNodePos = layout.getNodePosition(1);
layout.step();
t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object');
layout.step();
t.ok(firstNodePos === layout.getNodePosition(1), 'Position is the same object after multiple steps');
t.end();
});
test('it returns body', function(t) {
var g = createGraph();
var layout = createLayout(g);
g.addLink(1, 2);
t.ok(layout.getBody(1), 'node 1 has body');
t.ok(layout.getBody(2), 'node 2 has body');
t.notOk(layout.getBody(4), 'there is no node 4');
var body = layout.getBody(1);
t.ok(body.pos.x && body.pos.y, 'Body has a position');
t.ok(body.mass, 'Body has a mass');
t.end();
});
test('it can set node mass', function(t) {
var g = createGraph();
g.addNode('anvaka');
var layout = createLayout(g, {
nodeMass: function (nodeId) {
t.equal(nodeId, 'anvaka', 'correct node is called');
return 84; // my mass in kilograms :P
}
});
var body = layout.getBody('anvaka');
t.equal(body.mass, 84, 'Mass is okay');
t.end();
});
test('does not tolerate bad input', function (t) {
t.throws(missingGraph);
t.throws(invalidNodeId);
t.end();
function missingGraph() {
// graph is missing:
createLayout();
}
function invalidNodeId() {
var graph = createGraph();
var layout = createLayout(graph);
// we don't have nodes in the graph. This should throw:
layout.getNodePosition(1);
}
});
test('it fires stable on empty graph', function(t) {
var graph = createGraph();
var layout = createLayout(graph);
layout.on('stable', endTest);
layout.step();
function endTest() {
t.end();
}
});
test('can add bodies which are standard prototype names', function (t) {
var graph = createGraph();
graph.addLink('constructor', 'watch');
var layout = createLayout(graph);
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
t.end();
});
test('it can step when no links present', function (t) {
var graph = createGraph();
graph.addNode('constructor');
graph.addNode('watch');
var layout = createLayout(graph);
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
t.end();
});
test('layout initializes nodes positions', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
// perform one iteration of layout:
layout.step();
graph.forEachNode(function (node) {
var pos = layout.getNodePosition(node.id);
t.ok(pos && typeof pos.x === 'number' &&
typeof pos.y === 'number', 'Position is defined');
});
graph.forEachLink(function (link) {
var linkPos = layout.getLinkPosition(link.id);
t.ok(linkPos && linkPos.from && linkPos.to, 'Link position is defined');
var fromPos = layout.getNodePosition(link.fromId);
t.ok(linkPos.from === fromPos, '"From" should be identical to getNodePosition');
var toPos = layout.getNodePosition(link.toId);
t.ok(linkPos.to === toPos, '"To" should be identical to getNodePosition');
});
t.end();
});
test('Layout can set node position', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.pinNode(graph.getNode(1), true);
layout.setNodePosition(1, 42, 42);
// perform one iteration of layout:
layout.step();
// and make sure node 1 was not moved:
var actualPosition = layout.getNodePosition(1);
t.equal(actualPosition.x, 42, 'X has not changed');
t.equal(actualPosition.y, 42, 'Y has not changed');
t.end();
});
test('Layout updates bounding box when it sets node position', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.setNodePosition(1, 42, 42);
layout.setNodePosition(2, 40, 40);
var rect = layout.getGraphRect();
t.ok(rect.max_x <= 42); t.ok(rect.max_y <= 42);
t.ok(rect.min_x >= 40); t.ok(rect.min_y >= 40);
t.end();
});
test('layout initializes links', function (t) {
var graph = createGraph();
var node1 = graph.addNode(1); node1.position = {x : -1000, y: 0};
var node2 = graph.addNode(2); node2.position = {x : 1000, y: 0};
graph.addLink(1, 2);
var layout = createLayout(graph);
// perform one iteration of layout:
layout.step();
// since both nodes are connected by spring and distance is too large between
// them, they should start attracting each other
var pos1 = layout.getNodePosition(1);
var pos2 = layout.getNodePosition(2);
t.ok(pos1.x > -1000, 'Node 1 moves towards node 2');
t.ok(pos2.x < 1000, 'Node 1 moves towards node 2');
t.end();
});
test('layout respects proposed original position', function (t) {
var graph = createGraph();
var node = graph.addNode(1);
var initialPosition = {x: 100, y: 100};
node.position = copy(initialPosition);
var layout = createLayout(graph);
layout.step();
t.same(layout.getNodePosition(node.id), initialPosition, 'original position preserved');
t.end();
});
test('layout has defined graph rectangle', function (t) {
t.test('empty graph', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
var rect = layout.getGraphRect();
var expectedProperties = ['min_x', 'min_y', 'max_x', 'max_y'];
t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present before step()');
layout.step();
t.ok(rect && expectedProperties.reduce(hasProperties, true), 'Values are present after step()');
t.end();
function hasProperties(result, key) {
return result && typeof rect[key] === 'number';
}
});
t.test('two nodes', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.step();
var rect = layout.getGraphRect();
t.ok(!rectangleIsEmpty(rect), 'Graph rectangle is not empty');
t.end();
});
t.end();
});
test('it does not move pinned nodes', function (t) {
t.test('respects original data.isPinned attribute', function (t) {
var graph = createGraph();
var testNode = graph.addNode(1, { isPinned: true });
var layout = createLayout(graph);
t.ok(layout.isNodePinned(testNode), 'Node is pinned');
t.end();
});
t.test('respects node.isPinned attribute', function (t) {
var graph = createGraph();
var testNode = graph.addNode(1);
// this was possible in vivagraph. Port it over to ngraph:
testNode.isPinned = true;
var layout = createLayout(graph);
t.ok(layout.isNodePinned(testNode), 'Node is pinned');
t.end();
});
t.test('can pin nodes after graph is initialized', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph);
layout.pinNode(graph.getNode(1), true);
layout.step();
var pos1 = copy(layout.getNodePosition(1));
var pos2 = copy(layout.getNodePosition(2));
// make one more step and make sure node 1 did not move:
layout.step();
t.ok(!positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 was not moved');
t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
t.end();
});
t.end();
});
test('it listens to graph events', function (t) {
// we first initialize with empty graph:
var graph = createGraph();
var layout = createLayout(graph);
// and only then add nodes:
graph.addLink(1, 2);
// make two iterations
layout.step();
var pos1 = copy(layout.getNodePosition(1));
var pos2 = copy(layout.getNodePosition(2));
layout.step();
t.ok(positionChanged(pos1, layout.getNodePosition(1)), 'Node 1 has moved');
t.ok(positionChanged(pos2, layout.getNodePosition(2)), 'Node 2 has moved');
t.end();
});
test('can stop listen to events', function (t) {
// we first initialize with empty graph:
var graph = createGraph();
var layout = createLayout(graph);
layout.dispose();
graph.addLink(1, 2);
layout.step();
t.ok(layout.simulator.bodies.length === 0, 'No bodies in the simulator');
t.end();
});
test('physics simulator', function (t) {
t.test('has default simulator', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
t.ok(layout.simulator, 'physics simulator is present');
t.end();
});
t.test('can override default settings', function (t) {
var graph = createGraph();
var layout = createLayout(graph, {
theta: 1.5
});
t.equal(layout.simulator.theta(), 1.5, 'Simulator settings are overridden');
t.end();
});
t.end();
});
test('it removes removed nodes', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
graph.addLink(1, 2);
layout.step();
graph.clear();
// since we removed everything from graph rect should be empty:
var rect = layout.getGraphRect();
t.ok(rectangleIsEmpty(rect), 'Graph rect is empty');
t.end();
});
test('it can iterate over bodies', function(t) {
var graph = createGraph();
var layout = createLayout(graph);
graph.addLink(1, 2);
var calledCount = 0;
layout.forEachBody(function(body, bodyId) {
t.ok(body.pos, bodyId + ' has position');
t.ok(graph.getNode(bodyId), bodyId + ' matches a graph node');
calledCount += 1;
});
t.equal(calledCount, 2, 'Both bodies are visited');
t.end();
});
test('it handles large graphs', function (t) {
var graph = createGraph();
var layout = createLayout(graph);
var count = 60000;
var i = count;
while (i--) {
graph.addNode(i);
}
// link each node to 2 other random nodes
i = count;
while (i--) {
graph.addLink(i, Math.ceil(Math.random() * count));
graph.addLink(i, Math.ceil(Math.random() * count));
}
layout.step();
t.ok(layout.simulator.bodies.length !== 0, 'Bodies in the simulator');
t.end();
});
test('it can create high dimensional layout', function(t) {
var graph = createGraph();
graph.addLink(1, 2);
var layout = createLayout(graph, {dimensions: 6});
layout.step();
var pos = layout.getNodePosition(1);
t.ok(pos.x !== undefined, 'Position has x');
t.ok(pos.y !== undefined, 'Position has y');
t.ok(pos.z !== undefined, 'Position has z');
t.ok(pos.c4 !== undefined, 'Position has c4');
t.ok(pos.c5 !== undefined, 'Position has c5');
t.ok(pos.c6 !== undefined, 'Position has c6');
t.end();
});
test('it can layout two graphs independently', function(t) {
var graph1 = createGraph();
var graph2 = createGraph();
var layout1 = createLayout(graph1);
var layout2 = createLayout(graph2);
graph1.addLink(1, 2);
graph2.addLink(1, 2);
layout1.step();
layout2.step();
layout2.step();
t.ok(layout1.getNodePosition(1).x !== layout2.getNodePosition(1).x, 'Positions are different');
t.end();
});
function positionChanged(pos1, pos2) {
return (pos1.x !== pos2.x) || (pos1.y !== pos2.y);
}
function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function rectangleIsEmpty(rect) {
return rect.min_x === 0 && rect.min_y === 0 && rect.max_x === 0 && rect.max_y === 0;
}

View file

@ -0,0 +1,100 @@
var test = require('tap').test;
var {generateCreateBodyFunctionBody} = require('../lib/codeGenerators/generateCreateBody');
function primitive(dimension) {
let res = (new Function(generateCreateBodyFunctionBody(dimension)))();
return res;
}
test('Body has properties force, pos and mass', function(t) {
debugger;
var body = new (primitive(2).Body)();
t.ok(body.force, 'Force attribute is missing on body');
t.ok(body.pos, 'Pos attribute is missing on body');
t.ok(body.velocity, 'Velocity attribute is missing on body');
t.ok(typeof body.mass === 'number' && body.mass !== 0, 'Body should have a mass');
t.end();
});
test('Vector has x and y', function(t) {
var vector = new (primitive(2).Vector)();
t.ok(typeof vector.x === 'number', 'Vector has x coordinates');
t.ok(typeof vector.y === 'number', 'Vector has y coordinates');
var initialized = new (primitive(2).Vector)(1, 2);
t.equal(initialized.x, 1, 'Vector initialized properly');
t.equal(initialized.y, 2, 'Vector initialized properly');
var badInput = new (primitive(2).Vector)('hello world');
t.equal(badInput.x, 0, 'Vector should be resilient to bed input');
t.equal(badInput.y, 0, 'Vector should be resilient to bed input');
t.end();
});
test('Body3d has properties force, pos and mass', function(t) {
var body = new (primitive(3).Body)();
t.ok(body.force, 'Force attribute is missing on body');
t.ok(body.pos, 'Pos attribute is missing on body');
t.ok(body.velocity, 'Velocity attribute is missing on body');
t.ok(typeof body.mass === 'number' && body.mass !== 0, 'Body should have a mass');
t.end();
});
test('Vector3d has x and y and z', function(t) {
var vector = new (primitive(3).Vector)();
t.ok(typeof vector.x === 'number', 'Vector has x coordinates');
t.ok(typeof vector.y === 'number', 'Vector has y coordinates');
t.ok(typeof vector.z === 'number', 'Vector has z coordinates');
var initialized = new (primitive(3).Vector)(1, 2, 3);
t.equal(initialized.x, 1, 'Vector initialized properly');
t.equal(initialized.y, 2, 'Vector initialized properly');
t.equal(initialized.z, 3, 'Vector initialized properly');
var badInput = new (primitive(3).Vector)('hello world');
t.equal(badInput.x, 0, 'Vector should be resilient to bed input');
t.equal(badInput.y, 0, 'Vector should be resilient to bed input');
t.equal(badInput.z, 0, 'Vector should be resilient to bed input');
t.end();
});
test('reset vector', function(t) {
var v3 = new (primitive(3).Vector)(1, 2, 3);
v3.reset();
t.equal(v3.x, 0, 'Reset to 0');
t.equal(v3.y, 0, 'Reset to 0');
t.equal(v3.z, 0, 'Reset to 0');
var v2 = new (primitive(2).Vector)(1, 2);
v2.reset();
t.equal(v2.x, 0, 'Reset to 0');
t.equal(v2.y, 0, 'Reset to 0');
t.end();
});
test('vector can use copy constructor', function(t) {
var a = new (primitive(3).Vector)(1, 2, 3);
var b = new (primitive(3).Vector)(a);
t.equal(b.x, a.x, 'Value copied');
t.equal(b.y, a.y, 'Value copied');
t.equal(b.z, a.z, 'Value copied');
t.end();
});
test('Body3d can set position', function(t) {
var body = new (primitive(3).Body)();
body.setPosition(10, 11, 12);
t.equal(body.pos.x, 10, 'x is correct');
t.equal(body.pos.y, 11, 'y is correct');
t.equal(body.pos.z, 12, 'z is correct');
t.end();
});
test('Body can set position', function(t) {
var body = new (primitive(2).Body)();
body.setPosition(10, 11);
t.equal(body.pos.x, 10, 'x is correct');
t.equal(body.pos.y, 11, 'y is correct');
t.end();
});

View file

@ -0,0 +1,336 @@
/* eslint-disable no-shadow */
var test = require('tap').test;
var dimensions = 2;
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var createSimulator = require('../lib/createPhysicsSimulator');
test('Can step without bodies', function (t) {
var simulator = createSimulator();
t.equal(simulator.bodies.length, 0, 'There should be no bodies');
t.equal(simulator.springs.length, 0, 'There should be no springs');
simulator.step();
t.end();
});
test('it has settings exposed', function(t) {
var mySettings = { };
var simulator = createSimulator(mySettings);
t.ok(mySettings === simulator.settings, 'settings are exposed');
t.end();
});
test('it gives amount of total movement', function(t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
var totalMoved = simulator.getTotalMovement();
t.ok(!isNaN(totalMoved), 'Amount of total movement is returned');
t.end();
});
test('it can add a body at given position', function(t) {
var simulator = createSimulator();
var pos1 = {x: -10, y: 0};
var pos2 = {x: 10, y: 0};
simulator.addBodyAt(pos1);
simulator.addBodyAt(pos2);
t.equal(simulator.bodies.length, 2, 'All bodies are added');
var body1 = simulator.bodies[0];
t.equal(body1.pos.x, -10, 'X is there');
t.equal(body1.pos.y, 0, 'Y is there');
var body2 = simulator.bodies[1];
t.equal(body2.pos.x, 10, 'X is there');
t.equal(body2.pos.y, 0, 'Y is there');
t.end();
});
test('Does not update position of one body', function (t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
simulator.step(1);
t.equal(simulator.bodies.length, 1, 'Number of bodies is 1');
t.equal(simulator.springs.length, 0, 'Number of springs is 0');
t.equal(simulator.bodies[0], body, 'Body points to actual object');
t.equal(body.pos.x, 0, 'X is not changed');
t.equal(body.pos.y, 0, 'Y is not changed');
t.end();
});
test('throws on no body or no pos', t => {
var simulator = createSimulator();
t.throws(() => simulator.addBody(), /Body is required/);
t.throws(() => simulator.addBodyAt(), /Body position is required/);
t.end();
});
test('throws on no spring', t => {
var simulator = createSimulator();
t.throws(() => simulator.addSpring(), /Cannot add null spring to force simulator/);
t.end();
});
test('Can add and remove forces', function (t) {
var simulator = createSimulator();
var testForce = function () {};
simulator.addForce('foo', testForce);
t.equal(simulator.getForces().get('foo'), testForce);
simulator.removeForce('foo');
t.equal(simulator.getForces().get('foo'), undefined);
simulator.removeForce('foo');
// should still be good
t.end();
});
test('Can configure forces', function (t) {
t.test('Gravity', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(1, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
// by default gravity is negative, bodies should repel each other:
var x1 = body1.pos.x;
var x2 = body2.pos.x;
t.ok(x1 < 0, 'Body 1 moves away from body 2');
t.ok(x2 > 1, 'Body 2 moves away from body 1');
// now reverse gravity, and bodies should attract each other:
simulator.gravity(100);
simulator.step();
t.ok(body1.pos.x > x1, 'Body 1 moved towards body 2');
t.ok(body2.pos.x < x2, 'Body 2 moved towards body 1');
t.end();
});
t.test('Drag', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
body1.velocity.x = -1; // give it small impulse
simulator.addBody(body1);
simulator.step();
var x1 = body1.velocity.x;
// by default drag force will slow down entire system:
t.ok(x1 > -1, 'Body 1 moves at reduced speed');
// Restore original velocity, but now set drag force to 0
body1.velocity.x = -1;
simulator.dragCoefficient(0);
simulator.step();
t.ok(body1.velocity.x === -1, 'Velocity should remain unchanged');
t.end();
});
t.end();
});
test('Can remove bodies', function (t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
t.equal(simulator.bodies.length, 1, 'Number of bodies is 1');
var result = simulator.removeBody(body);
t.equal(result, true, 'body successfully removed');
t.equal(simulator.bodies.length, 0, 'Number of bodies is 0');
t.end();
});
test('Updates position for two bodies', function (t) {
var simulator = createSimulator();
var body1 = new Body(-1, 0);
var body2 = new Body(1, 0);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step();
t.equal(simulator.bodies.length, 2, 'Number of bodies is 2');
t.ok(body1.pos.x !== 0, 'Body1.X has changed');
t.ok(body2.pos.x !== 0, 'Body2.X has changed');
t.equal(body1.pos.y, 0, 'Body1.Y has not changed');
t.equal(body2.pos.y, 0, 'Body2.Y has not changed');
t.end();
});
test('add spring should not add bodies', function (t) {
var simulator = createSimulator();
var body1 = new Body(-1, 0);
var body2 = new Body(1, 0);
simulator.addSpring(body1, body2, 10);
t.equal(simulator.bodies.length, 0, 'Should not add two bodies');
t.equal(simulator.bodies.length, 0, 'Should not add two bodies');
t.equal(simulator.springs.length, 1, 'Should have a spring');
t.end();
});
test('Spring affects bodies positions', function (t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
// If you take this out, bodies will repel each other:
simulator.addSpring(body1, body2, 1);
simulator.step();
t.ok(body1.pos.x > -10, 'Body 1 should move towards body 2');
t.ok(body2.pos.x < 10, 'Body 2 should move towards body 1');
t.end();
});
test('Can remove springs', function (t) {
var simulator = createSimulator();
var body1 = new Body(-10, 0);
var body2 = new Body(10, 0);
simulator.addBody(body1);
simulator.addBody(body2);
var spring = simulator.addSpring(body1, body2, 1);
simulator.removeSpring(spring);
simulator.step();
t.ok(body1.pos.x < -10, 'Body 1 should move away from body 2');
t.ok(body2.pos.x > 10, 'Body 2 should move away from body 1');
t.end();
});
test('Get bounding box', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(10, 10);
simulator.addBody(body1);
simulator.addBody(body2);
simulator.step(); // this will move bodies farther away
var bbox = simulator.getBBox();
t.ok(bbox.min_x <= 0, 'Left is 0');
t.ok(bbox.min_y <= 0, 'Top is 0');
t.ok(bbox.max_x >= 10, 'right is 10');
t.ok(bbox.max_y >= 10, 'bottom is 10');
t.end();
});
test('it updates bounding box', function (t) {
var simulator = createSimulator();
var body1 = new Body(0, 0);
var body2 = new Body(10, 10);
simulator.addBody(body1);
simulator.addBody(body2);
var bbox = simulator.getBBox();
t.ok(bbox.min_x === 0, 'Left is 0');
t.ok(bbox.min_y === 0, 'Top is 0');
t.ok(bbox.max_x === 10, 'right is 10');
t.ok(bbox.max_y === 10, 'bottom is 10');
body1.setPosition(15, 15);
simulator.invalidateBBox();
bbox = simulator.getBBox();
t.ok(bbox.min_x === 10, 'Left is 10');
t.ok(bbox.min_y === 10, 'Top is 10');
t.ok(bbox.max_x === 15, 'right is 15');
t.ok(bbox.max_y === 15, 'bottom is 15');
t.end();
});
test('Get best position', function (t) {
t.test('can get with empty simulator', function (t) {
var simulator = createSimulator();
var empty = simulator.getBestNewBodyPosition([]);
t.ok(typeof empty.x === 'number', 'Has X');
t.ok(typeof empty.y === 'number', 'Has Y');
t.end();
});
t.end();
});
test('it can change settings', function(t) {
var simulator = createSimulator();
var currentTheta = simulator.theta();
t.ok(typeof currentTheta === 'number', 'theta is here');
simulator.theta(1.2);
t.equal(simulator.theta(), 1.2, 'theta is changed');
var currentSpringCoefficient = simulator.springCoefficient();
t.ok(typeof currentSpringCoefficient === 'number', 'springCoefficient is here');
simulator.springCoefficient(0.8);
t.equal(simulator.springCoefficient(), 0.8, 'springCoefficient is changed');
var gravity = simulator.gravity();
t.ok(typeof gravity === 'number', 'gravity is here');
simulator.gravity(-0.8);
t.equal(simulator.gravity(), -0.8, 'gravity is changed');
var springLength = simulator.springLength();
t.ok(typeof springLength === 'number', 'springLength is here');
simulator.springLength(80);
t.equal(simulator.springLength(), 80, 'springLength is changed');
var dragCoefficient = simulator.dragCoefficient();
t.ok(typeof dragCoefficient === 'number', 'dragCoefficient is here');
simulator.dragCoefficient(0.8);
t.equal(simulator.dragCoefficient(), 0.8, 'dragCoefficient is changed');
var timeStep = simulator.timeStep();
t.ok(typeof timeStep === 'number', 'timeStep is here');
simulator.timeStep(8);
t.equal(simulator.timeStep(), 8, 'timeStep is changed');
t.end();
});
test('it can augment string setter values', function (t) {
var simulator = createSimulator({
name: 'John'
});
simulator.name('Alisa');
t.equal(simulator.name(), 'Alisa', 'name is Alisa');
t.end();
});
test('it ignores body that does not exist', function(t) {
var simulator = createSimulator();
var body = new Body(0, 0);
simulator.addBody(body);
simulator.removeBody({});
t.equal(simulator.bodies.length, 1, 'Should ignore body that does not exist');
t.end();
});
test('it throws on springCoeff', function (t) {
t.throws(function () {
createSimulator({springCoeff: 1});
}, 'springCoeff was renamed to springCoefficient');
t.end();
});
test('it throws on dragCoeff', function (t) {
t.throws(function () {
createSimulator({dragCoeff: 1});
}, 'dragCoeff was renamed to dragCoefficient');
t.end();
});

View file

@ -0,0 +1,63 @@
/* eslint-disable no-shadow */
var test = require('tap').test;
var dimensions = 2;
var createSpringForce = require('../lib/codeGenerators/generateCreateSpringForce')(dimensions);
var Body = require('../lib/codeGenerators/generateCreateBody')(dimensions);
var Spring = require('../lib/spring');
var random = require('ngraph.random')(42);
test('Initialized with default value', function (t) {
t.throws(() => createSpringForce());
t.end();
});
test('Should bump bodies at same position', function (t) {
var body1 = new Body(0, 0);
var body2 = new Body(0, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 1;
var spring = new Spring(body1, body2, idealLength);
var springForce = createSpringForce({springCoefficient: 0.1, springLength: 1}, random);
springForce.update(spring);
t.ok(body1.force.x > 0, 'Body 1 should go right');
t.ok(body2.force.x < 0, 'Body 2 should go left');
t.end();
});
test('Check spring force direction', function (t) {
var springForce = createSpringForce({springCoefficient: 0.1, springLength: 1});
t.test('Should contract two bodies when ideal length is smaller than actual', function (t) {
var body1 = new Body(-1, 0);
var body2 = new Body(+1, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 1;
var spring = new Spring(body1, body2, idealLength);
springForce.update(spring);
t.ok(body1.force.x > 0, 'Body 1 should go right');
t.ok(body2.force.x < 0, 'Body 2 should go left');
t.end();
});
t.test('Should repel two bodies when ideal length is larger than actual', function (t) {
var body1 = new Body(-1, 0);
var body2 = new Body(+1, 0);
// length between two bodies is 2, while ideal length is 1. Each body
// should start moving towards each other after force update
var idealLength = 3;
var spring = new Spring(body1, body2, idealLength);
springForce.update(spring);
t.ok(body1.force.x < 0, 'Body 1 should go left');
t.ok(body2.force.x > 0, 'Body 2 should go right');
t.end();
});
t.end();
});