123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- const createPatternBuilder = require('./createPatternBuilder');
- const getVariableName = require('./getVariableName');
- module.exports = generateQuadTreeFunction;
- module.exports.generateQuadTreeFunctionBody = generateQuadTreeFunctionBody;
- // These exports are for InlineTransform tool.
- // InlineTransform: getInsertStackCode
- module.exports.getInsertStackCode = getInsertStackCode;
- // InlineTransform: getQuadNodeCode
- module.exports.getQuadNodeCode = getQuadNodeCode;
- // InlineTransform: isSamePosition
- module.exports.isSamePosition = isSamePosition;
- // InlineTransform: getChildBodyCode
- module.exports.getChildBodyCode = getChildBodyCode;
- // InlineTransform: setChildBodyCode
- module.exports.setChildBodyCode = setChildBodyCode;
- function generateQuadTreeFunction(dimension) {
- let code = generateQuadTreeFunctionBody(dimension);
- return (new Function(code))();
- }
- function generateQuadTreeFunctionBody(dimension) {
- let pattern = createPatternBuilder(dimension);
- let quadCount = Math.pow(2, dimension);
- let code = `
- ${getInsertStackCode()}
- ${getQuadNodeCode(dimension)}
- ${isSamePosition(dimension)}
- ${getChildBodyCode(dimension)}
- ${setChildBodyCode(dimension)}
- function createQuadTree(options, random) {
- options = options || {};
- options.gravity = typeof options.gravity === 'number' ? options.gravity : -1;
- options.theta = typeof options.theta === 'number' ? options.theta : 0.8;
- var gravity = options.gravity;
- var updateQueue = [];
- var insertStack = new InsertStack();
- var theta = options.theta;
- var nodesCache = [];
- var currentInCache = 0;
- var root = newNode();
- return {
- insertBodies: insertBodies,
- /**
- * Gets root node if it is present
- */
- getRoot: function() {
- return root;
- },
- updateBodyForce: update,
- options: function(newOptions) {
- if (newOptions) {
- if (typeof newOptions.gravity === 'number') {
- gravity = newOptions.gravity;
- }
- if (typeof newOptions.theta === 'number') {
- theta = newOptions.theta;
- }
- return this;
- }
- return {
- gravity: gravity,
- theta: theta
- };
- }
- };
- function newNode() {
- // To avoid pressure on GC we reuse nodes.
- var node = nodesCache[currentInCache];
- if (node) {
- ${assignQuads(' node.')}
- node.body = null;
- node.mass = ${pattern('node.mass_{var} = ', {join: ''})}0;
- ${pattern('node.min_{var} = node.max_{var} = ', {join: ''})}0;
- } else {
- node = new QuadNode();
- nodesCache[currentInCache] = node;
- }
- ++currentInCache;
- return node;
- }
- function update(sourceBody) {
- var queue = updateQueue;
- var v;
- ${pattern('var d{var};', {indent: 4})}
- var r;
- ${pattern('var f{var} = 0;', {indent: 4})}
- var queueLength = 1;
- var shiftIdx = 0;
- var pushIdx = 1;
- queue[0] = root;
- while (queueLength) {
- var node = queue[shiftIdx];
- var body = node.body;
- queueLength -= 1;
- shiftIdx += 1;
- var differentBody = (body !== sourceBody);
- if (body && differentBody) {
- // If the current node is a leaf node (and it is not source body),
- // calculate the force exerted by the current node on body, and add this
- // amount to body's net force.
- ${pattern('d{var} = body.pos.{var} - sourceBody.pos.{var};', {indent: 8})}
- r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
- if (r === 0) {
- // Poor man's protection against zero distance.
- ${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
- r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
- }
- // This is standard gravitation force calculation but we divide
- // by r^3 to save two operations when normalizing force vector.
- v = gravity * body.mass * sourceBody.mass / (r * r * r);
- ${pattern('f{var} += v * d{var};', {indent: 8})}
- } else if (differentBody) {
- // Otherwise, calculate the ratio s / r, where s is the width of the region
- // represented by the internal node, and r is the distance between the body
- // and the node's center-of-mass
- ${pattern('d{var} = node.mass_{var} / node.mass - sourceBody.pos.{var};', {indent: 8})}
- r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
- if (r === 0) {
- // Sorry about code duplication. I don't want to create many functions
- // right away. Just want to see performance first.
- ${pattern('d{var} = (random.nextDouble() - 0.5) / 50;', {indent: 10})}
- r = Math.sqrt(${pattern('d{var} * d{var}', {join: ' + '})});
- }
- // If s / r < θ, treat this internal node as a single body, and calculate the
- // force it exerts on sourceBody, and add this amount to sourceBody's net force.
- if ((node.max_${getVariableName(0)} - node.min_${getVariableName(0)}) / r < theta) {
- // in the if statement above we consider node's width only
- // because the region was made into square during tree creation.
- // Thus there is no difference between using width or height.
- v = gravity * node.mass * sourceBody.mass / (r * r * r);
- ${pattern('f{var} += v * d{var};', {indent: 10})}
- } else {
- // Otherwise, run the procedure recursively on each of the current node's children.
- // I intentionally unfolded this loop, to save several CPU cycles.
- ${runRecursiveOnChildren()}
- }
- }
- }
- ${pattern('sourceBody.force.{var} += f{var};', {indent: 4})}
- }
- function insertBodies(bodies) {
- ${pattern('var {var}min = Number.MAX_VALUE;', {indent: 4})}
- ${pattern('var {var}max = Number.MIN_VALUE;', {indent: 4})}
- var i = bodies.length;
- // To reduce quad tree depth we are looking for exact bounding box of all particles.
- while (i--) {
- var pos = bodies[i].pos;
- ${pattern('if (pos.{var} < {var}min) {var}min = pos.{var};', {indent: 6})}
- ${pattern('if (pos.{var} > {var}max) {var}max = pos.{var};', {indent: 6})}
- }
- // Makes the bounds square.
- var maxSideLength = -Infinity;
- ${pattern('if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;', {indent: 4})}
- currentInCache = 0;
- root = newNode();
- ${pattern('root.min_{var} = {var}min;', {indent: 4})}
- ${pattern('root.max_{var} = {var}min + maxSideLength;', {indent: 4})}
- i = bodies.length - 1;
- if (i >= 0) {
- root.body = bodies[i];
- }
- while (i--) {
- insert(bodies[i], root);
- }
- }
- function insert(newBody) {
- insertStack.reset();
- insertStack.push(root, newBody);
- while (!insertStack.isEmpty()) {
- var stackItem = insertStack.pop();
- var node = stackItem.node;
- var body = stackItem.body;
- if (!node.body) {
- // This is internal node. Update the total mass of the node and center-of-mass.
- ${pattern('var {var} = body.pos.{var};', {indent: 8})}
- node.mass += body.mass;
- ${pattern('node.mass_{var} += body.mass * {var};', {indent: 8})}
- // Recursively insert the body in the appropriate quadrant.
- // But first find the appropriate quadrant.
- var quadIdx = 0; // Assume we are in the 0's quad.
- ${pattern('var min_{var} = node.min_{var};', {indent: 8})}
- ${pattern('var max_{var} = (min_{var} + node.max_{var}) / 2;', {indent: 8})}
- ${assignInsertionQuadIndex(8)}
- var child = getChild(node, quadIdx);
- if (!child) {
- // The node is internal but this quadrant is not taken. Add
- // subnode to it.
- child = newNode();
- ${pattern('child.min_{var} = min_{var};', {indent: 10})}
- ${pattern('child.max_{var} = max_{var};', {indent: 10})}
- child.body = body;
- setChild(node, quadIdx, child);
- } else {
- // continue searching in this quadrant.
- insertStack.push(child, body);
- }
- } else {
- // We are trying to add to the leaf node.
- // We have to convert current leaf into internal node
- // and continue adding two nodes.
- var oldBody = node.body;
- node.body = null; // internal nodes do not cary bodies
- if (isSamePosition(oldBody.pos, body.pos)) {
- // Prevent infinite subdivision by bumping one node
- // anywhere in this quadrant
- var retriesCount = 3;
- do {
- var offset = random.nextDouble();
- ${pattern('var d{var} = (node.max_{var} - node.min_{var}) * offset;', {indent: 12})}
- ${pattern('oldBody.pos.{var} = node.min_{var} + d{var};', {indent: 12})}
- retriesCount -= 1;
- // Make sure we don't bump it out of the box. If we do, next iteration should fix it
- } while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos));
- if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) {
- // This is very bad, we ran out of precision.
- // if we do not return from the method we'll get into
- // infinite loop here. So we sacrifice correctness of layout, and keep the app running
- // Next layout iteration should get larger bounding box in the first step and fix this
- return;
- }
- }
- // Next iteration should subdivide node further.
- insertStack.push(node, oldBody);
- insertStack.push(node, body);
- }
- }
- }
- }
- return createQuadTree;
- `;
- return code;
- function assignInsertionQuadIndex(indentCount) {
- let insertionCode = [];
- let indent = Array(indentCount + 1).join(' ');
- for (let i = 0; i < dimension; ++i) {
- insertionCode.push(indent + `if (${getVariableName(i)} > max_${getVariableName(i)}) {`);
- insertionCode.push(indent + ` quadIdx = quadIdx + ${Math.pow(2, i)};`);
- insertionCode.push(indent + ` min_${getVariableName(i)} = max_${getVariableName(i)};`);
- insertionCode.push(indent + ` max_${getVariableName(i)} = node.max_${getVariableName(i)};`);
- insertionCode.push(indent + `}`);
- }
- return insertionCode.join('\n');
- // if (x > max_x) { // somewhere in the eastern part.
- // quadIdx = quadIdx + 1;
- // left = right;
- // right = node.right;
- // }
- }
- function runRecursiveOnChildren() {
- let indent = Array(11).join(' ');
- let recursiveCode = [];
- for (let i = 0; i < quadCount; ++i) {
- recursiveCode.push(indent + `if (node.quad${i}) {`);
- recursiveCode.push(indent + ` queue[pushIdx] = node.quad${i};`);
- recursiveCode.push(indent + ` queueLength += 1;`);
- recursiveCode.push(indent + ` pushIdx += 1;`);
- recursiveCode.push(indent + `}`);
- }
- return recursiveCode.join('\n');
- // if (node.quad0) {
- // queue[pushIdx] = node.quad0;
- // queueLength += 1;
- // pushIdx += 1;
- // }
- }
- function assignQuads(indent) {
- // this.quad0 = null;
- // this.quad1 = null;
- // this.quad2 = null;
- // this.quad3 = null;
- let quads = [];
- for (let i = 0; i < quadCount; ++i) {
- quads.push(`${indent}quad${i} = null;`);
- }
- return quads.join('\n');
- }
- }
- function isSamePosition(dimension) {
- let pattern = createPatternBuilder(dimension);
- return `
- function isSamePosition(point1, point2) {
- ${pattern('var d{var} = Math.abs(point1.{var} - point2.{var});', {indent: 2})}
-
- return ${pattern('d{var} < 1e-8', {join: ' && '})};
- }
- `;
- }
- function setChildBodyCode(dimension) {
- var quadCount = Math.pow(2, dimension);
- return `
- function setChild(node, idx, child) {
- ${setChildBody()}
- }`;
- function setChildBody() {
- let childBody = [];
- for (let i = 0; i < quadCount; ++i) {
- let prefix = (i === 0) ? ' ' : ' else ';
- childBody.push(`${prefix}if (idx === ${i}) node.quad${i} = child;`);
- }
- return childBody.join('\n');
- // if (idx === 0) node.quad0 = child;
- // else if (idx === 1) node.quad1 = child;
- // else if (idx === 2) node.quad2 = child;
- // else if (idx === 3) node.quad3 = child;
- }
- }
- function getChildBodyCode(dimension) {
- return `function getChild(node, idx) {
- ${getChildBody()}
- return null;
- }`;
- function getChildBody() {
- let childBody = [];
- let quadCount = Math.pow(2, dimension);
- for (let i = 0; i < quadCount; ++i) {
- childBody.push(` if (idx === ${i}) return node.quad${i};`);
- }
- return childBody.join('\n');
- // if (idx === 0) return node.quad0;
- // if (idx === 1) return node.quad1;
- // if (idx === 2) return node.quad2;
- // if (idx === 3) return node.quad3;
- }
- }
- function getQuadNodeCode(dimension) {
- let pattern = createPatternBuilder(dimension);
- let quadCount = Math.pow(2, dimension);
- var quadNodeCode = `
- function QuadNode() {
- // body stored inside this node. In quad tree only leaf nodes (by construction)
- // contain bodies:
- this.body = null;
- // Child nodes are stored in quads. Each quad is presented by number:
- // 0 | 1
- // -----
- // 2 | 3
- ${assignQuads(' this.')}
- // Total mass of current node
- this.mass = 0;
- // Center of mass coordinates
- ${pattern('this.mass_{var} = 0;', {indent: 2})}
- // bounding box coordinates
- ${pattern('this.min_{var} = 0;', {indent: 2})}
- ${pattern('this.max_{var} = 0;', {indent: 2})}
- }
- `;
- return quadNodeCode;
- function assignQuads(indent) {
- // this.quad0 = null;
- // this.quad1 = null;
- // this.quad2 = null;
- // this.quad3 = null;
- let quads = [];
- for (let i = 0; i < quadCount; ++i) {
- quads.push(`${indent}quad${i} = null;`);
- }
- return quads.join('\n');
- }
- }
- function getInsertStackCode() {
- return `
- /**
- * Our implementation of QuadTree is non-recursive to avoid GC hit
- * This data structure represent stack of elements
- * which we are trying to insert into quad tree.
- */
- function InsertStack () {
- this.stack = [];
- this.popIdx = 0;
- }
- InsertStack.prototype = {
- isEmpty: function() {
- return this.popIdx === 0;
- },
- push: function (node, body) {
- var item = this.stack[this.popIdx];
- if (!item) {
- // we are trying to avoid memory pressure: create new element
- // only when absolutely necessary
- this.stack[this.popIdx] = new InsertStackElement(node, body);
- } else {
- item.node = node;
- item.body = body;
- }
- ++this.popIdx;
- },
- pop: function () {
- if (this.popIdx > 0) {
- return this.stack[--this.popIdx];
- }
- },
- reset: function () {
- this.popIdx = 0;
- }
- };
- function InsertStackElement(node, body) {
- this.node = node; // QuadTree node
- this.body = body; // physical body which needs to be inserted to node
- }
- `;
- }
|