/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2011 Dirk Beyer * * CCVisu is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * CCVisu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CCVisu; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please find the GNU Lesser General Public License in file * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt * * Andreas Noack (an@informatik.tu-cottbus.de) * University of Technology at Cottbus, Germany * Dirk Beyer (firstname.lastname@uni-passau.de) * University of Passau, Bavaria, Germany */ package org.sosy_lab.ccvisu.layout; import java.util.EventObject; import java.util.List; import org.sosy_lab.ccvisu.Options; import org.sosy_lab.ccvisu.Options.OptionsEnum; import org.sosy_lab.ccvisu.Options.Verbosity; import org.sosy_lab.ccvisu.graph.GraphData; import org.sosy_lab.ccvisu.graph.GraphEdge; import org.sosy_lab.ccvisu.graph.GraphVertex; import org.sosy_lab.ccvisu.graph.Position; /** * Minimizer for the (weighted) (edge-repulsion) LinLog energy model, * based on the Barnes-Hut algorithm. * * Changed: * Extended to edge-repulsion, according to the IWPC 2005 paper. * Data structures changed to achieve O(n log n) space complexity. * Energy model extended to a weighted version. * * 2006-02-08: * Energy model changed to flexible repulsion exponent. */ public class MinimizerBarnesHut extends Minimizer { /** * The graph that this layout is computed for. * node. fixedPos: The minimizer does not change the position * of a node with fixedPos == true. * pos: Position matrix (3 dimensions for every node). * Is not copied and serves as input and output * of <code>minimizeEnergy</code>. * If the input is two-dimensional (i.e. pos[i][2] == 0 * for all i), the output is also two-dimensional. * Random initial positions are appropriate. * Preconditions: dimension at least [nodeNr][3]; * no two different nodes have the same position * The input positions should be scaled such that * the average Euclidean distance between connected nodes * is roughly 1. s*/ private final GraphData graph; /** The following two must be symmetric. */ /** * Node indexes of the similarity list for each node. * Is not copied and not modified by minimizeEnergy. * (attrIndexes[i][k] == j) represents the k-th edge * of node i, namely (i,j). * Omit edges with weight 0.0 (i.e. non-edges). * Preconditions: * (attrIndexes[i][k] != i) for all i,k (irreflexive); * (attrIndexes[i][k1] == j) iff (attrIndexes[j][k2] == i) * for all i, j, exists k1, k2 (symmetric). */ private final int attrIndexes[][]; /** Similarity values of the similarity lists for each node. * Is not copied and not modified by minimizeEnergy. * For each (attrIndexes[i][k] == j), (attrValues[i][k] == w) * represents the weight w of edge (i,j). * For unweighted graphs use only 1.0f as edge weight. * Preconditions: * exists k1: (attrIndexes[i][k1] == j) and (attrValues[i][k1] == w) iff * exists k2: (attrIndexes[j][k2] == i) and (attrValues[j][k2] == w) * (symmetric). */ private final float attrValues[][]; /** Repulsion vector (node weights). * Is not copied and not modified by minimizeEnergy. * For for node repulsion use 1.0f for all nodes. * Preconditions: dimension at least [nodeNr]; * repu[i] >= 1 for all i. */ private final float repu[]; /** Exponent of the Euclidean distance in the attraction term of the energy model. * 1.0f for the LinLog models, 3.0f for the energy * version of the Fruchterman-Reingold model. * If 0.0f, the log of the distance is taken instead of a constant fun. * (Value 0.0f not yet tested.) */ private float attrExponent = 1.0f; /** Exponent of the Euclidean distance in the repulsion term of the energy model. * 0.0f for the LinLog models, 0.0f for the energy * version of the Fruchterman-Reingold model. * If 0.0f, the log of the distance is taken instead of a constant fun. */ private float repuExponent = 0.0f; /** Position of the barycenter of the nodes. */ private Position baryCenter = new Position(); /** Factor for the gravitation energy (attraction to the barycenter), * 0.0f for no gravitation. */ private float gravitationFactor = 0.0f; /** Factor for repulsion energy that normalizes average distance * between pairs of nodes with maximum similarity to (roughly) 1. */ private float repuFactor = 1.0f; /** Factors for the repulsion force for pulsing. */ private static final float[] repuStrategy = { 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1.0f, 0.95f, 0.9f, 0.85f, 0.8f, 0.75f, 0.8f, 0.85f, 0.9f, 0.95f }; /** OctTree for repulsion computation. */ private OctTree octTree = null; /** * Sets the number of nodes, the similarity matrices (edge weights), and the * position matrix. */ public MinimizerBarnesHut(Options options) { super(options); graph = options.graph; attrExponent = options.attrExponent; repuExponent = options.repuExponent; gravitationFactor = options.gravitation; // Create graph layout data structure, allocate memory. // Positions are already initialized (given by graph). int vertexCount = graph.getVertices().size(); // Initialize repulsions. repu = new float[vertexCount]; for (int i = 0; i < vertexCount; ++i) { // Set repulsion according to the energy model. if (options.vertRepu) { repu[i] = 1.0f; } else { GraphVertex graphVertex = graph.getVertices().get(i); repu[i] = graphVertex.getDegree(); } } // Initialize Attractions in steps 1-4 // 1. Allocate the columns. // Vertex indexes of the similarity lists. attrIndexes = new int[vertexCount][]; // Similarity values of the similarity lists. attrValues = new float[vertexCount][]; // 2. Compute length of rows, i.e., the number of in- and outgoing // non-reflexive edges for every GraphVertex. int[] attrCounter = new int[vertexCount]; for (GraphEdge edge : graph.getEdges()) { if (edge.getSource() == edge.getTarget()) { // We must not add reflexive edges. } else { ++attrCounter[edge.getSource().getId()]; ++attrCounter[edge.getTarget().getId()]; } } // 3. Allocate the rows (see step 2). for (int i = 0; i < vertexCount; i++) { attrIndexes[i] = new int[attrCounter[i]]; attrValues[i] = new float[attrCounter[i]]; } // 4. Transfer the edges to the similarity lists, i.e., fill the rows. int[] edgeCounter = new int[vertexCount]; for (GraphEdge edge : graph.getEdges()) { if (edge.getSource() == edge.getTarget()) { // We must not add reflexive edges. } else { int sourceId = edge.getSource().getId(); int targetId = edge.getTarget().getId(); // Similarity list must be symmetric. attrIndexes[sourceId][edgeCounter[sourceId]] = targetId; attrIndexes[targetId][edgeCounter[targetId]] = sourceId; // Set similarities according to the energy model. if (options.noWeight) { attrValues[sourceId][edgeCounter[sourceId]] = 1.0f; attrValues[targetId][edgeCounter[targetId]] = 1.0f; } else { attrValues[sourceId][edgeCounter[sourceId]] = edge.getWeight(); attrValues[targetId][edgeCounter[targetId]] = edge.getWeight(); } ++edgeCounter[sourceId]; ++edgeCounter[targetId]; } } } /** * Iteratively minimizes energy using the Barnes-Hut algorithm. * Starts from the positions in <code>pos</code>, * and stores the computed positions in <code>pos</code>. * @throws InterruptedException */ @Override public void minimizeEnergy() { int nodeCount = graph.getVertices().size(); if (nodeCount <= 1) { return; } int numberOfIterations = options.getOption(OptionsEnum.iter).getInt(); if (options.verbosity.isAtLeast(Verbosity.WARNING)) { System.err.println(); System.err.println("Note: Minimizer will run " + numberOfIterations + " iterations,"); System.err.println(" increase (decrease) this number with option -iter to"); System.err.println(" increase quality of layout (decrease runtime)."); } if ((numberOfIterations == 0) && (options.getOption(OptionsEnum.disableDISPGuiControls).getBool())) { // We don't need to minimize the energy and the gui controls are disabled, // so just return. return; } setProgress(0, nodeCount, "Analyzing distances."); // Print stats. analyzeDistances(); final float finalRepuFactor = computeRepuFactor(); repuFactor = finalRepuFactor; // compute initial energy setProgress(0, nodeCount, "Computing initial energy."); buildOctTree(); float energySum = 0.0f; for (int i = 0; i < nodeCount; i++) { energySum += getEnergy(i); } if (options.verbosity.isAtLeast(Verbosity.WARNING)) { System.err.println(); System.err.println("initial energy: " + energySum + "; repulsion: " + repuFactor); } // Draw initial layout before minimizing notifyAboutChangedLayout(false); System.out.println("Starting minimizer."); performMinimization(nodeCount, numberOfIterations, finalRepuFactor, energySum); System.out.println("Minimizer finished."); setProgress(1, 1, "Minimizer finished."); // Print stats. analyzeDistances(); } /** * This method performs the minimization prepared by method minimizeEnergy. */ private void performMinimization(int nodeCount, int numberOfIterations, final float finalRepuFactor, float energySum) { int iterationsLeft = numberOfIterations; // minimize energy float prevEnergy = energySum; int step = 0; //float energyMinimum = Float.MAX_VALUE; //int energyMinimumOnIterationNumber = -1; while (true) { // Maybe the thread is interrupted by the caller. if (Thread.interrupted()) { return; } // Notify about the progress of the thread. setProgress(step, numberOfIterations, "Minimizing energy."); // Determine mode of operation. if (iterationsLeft > 0) { // Normal iteration of the minimizer. step++; iterationsLeft--; } else { break; } computeBaryCenter(); buildOctTree(); if (options.getOption(OptionsEnum.energyDiffThreshold).getFloat() == 0.0f) { // except in the last 20 iterations, vary the repulsion factor // according to repuStrategy if ((iterationsLeft + 1) > 20) { int index = (iterationsLeft + 1) % MinimizerBarnesHut.repuStrategy.length; repuFactor = finalRepuFactor * (float) Math.pow(MinimizerBarnesHut.repuStrategy[index], attrExponent - repuExponent); } else { repuFactor = finalRepuFactor; } } else { repuFactor = finalRepuFactor; } // for all non-fixed nodes: minimize energy, i.e., move each node energySum = 0.0f; for (int i = 0; i < nodeCount; i++) { GraphVertex vertex = graph.getVertices().get(i); if (!vertex.hasFixedPos()) { // compute direction of the move of the node Position bestDir = new Position(); getDirection(i, bestDir); // line search: compute length of the move Position oldPos = new Position(vertex.getPosition()); float oldEnergy = getEnergy(i); float bestEnergy = oldEnergy; int bestMultiple = 0; bestDir.div(32); for (int multiple = 32; multiple >= 1 && (bestMultiple == 0 || bestMultiple / 2 == multiple); multiple /= 2) { // vertex.position = oldPos + bestDir * multiple; vertex.setPosition(Position.add(oldPos, Position.mult(bestDir, multiple))); float energy = getEnergy(i); if (energy < bestEnergy) { bestEnergy = energy; bestMultiple = multiple; } } for (int multiple = 64; multiple <= 128 && bestMultiple == multiple / 2; multiple *= 2) { // vertex.position = oldPos + bestDir * multiple; vertex.setPosition(Position.add(oldPos, Position.mult(bestDir, multiple))); float energy = getEnergy(i); if (energy < bestEnergy) { bestEnergy = energy; bestMultiple = multiple; } } // vertex.pos = oldPos + bestDir * bestMultiple; vertex.setPosition(Position.add(oldPos, Position.mult(bestDir, bestMultiple))); if (bestMultiple > 0) { octTree.moveNode(oldPos, vertex.getPosition(), repu[i]); //1.0f); } energySum += bestEnergy; } } // for if (options.verbosity.isAtLeast(Verbosity.WARNING)) { System.out.println("iteration\t" + step + "\tenergy\t" + energySum + "\trepulsion\t" + repuFactor); setProgress(step, numberOfIterations, "Minimizing energy."); } if (options.getOption(OptionsEnum.energyDiffThreshold).getFloat() != 0.0f) { float energyDiff = Math.abs(prevEnergy - energySum) / nodeCount; if (energyDiff < options.getOption(OptionsEnum.energyDiffThreshold).getFloat()) { iterationsLeft = 0; } } // Note: I also commented out the unused declarations of energyMinimum and // energyMinimumOnIterationNumber. @see: begin of method // if (energyMinimum > energySum) { // energyMinimumOnIterationNumber = step; // energyMinimum = energySum; // } else { // int minAtEarlierIterationThan = (int)(step * 0.9f); // if (step > 100 && (energyMinimumOnIterationNumber < minAtEarlierIterationThan)) { // iterationsLeft = 0; // System.out.println(String.format("Minimum %e on step %d becuase < %d", energyMinimum, energyMinimumOnIterationNumber, minAtEarlierIterationThan)); // } // } prevEnergy = energySum; notifyAboutChangedLayout(iterationsLeft == 0); } } private void notifyAboutChangedLayout(boolean forceNotify) { if ((options.getOption(OptionsEnum.anim).getBool()) || forceNotify) { graph.notifyAboutLayoutChange(new EventObject(this)); } } /** * Returns the repulsion energy between the node with the specified index * and the nodes in the octtree. * * @param index Index of the repulsing node. * @param tree Octtree containing repulsing nodes. * @return Repulsion energy between the node with the specified index * and the nodes in the octtree. */ private float getRepulsionEnergy(final Position vertexIndexPos, final int index, OctTree tree) { if (tree == null || tree.index == index || index >= repu.length) { return 0.0f; } float dist = vertexIndexPos.distanceTo(tree.position); if (tree.index < 0 && dist < 2.0f * tree.width()) { float energy = 0.0f; for (OctTree lElement : tree.children) { energy += getRepulsionEnergy(vertexIndexPos, index, lElement); } return energy; } if (repuExponent == 0.0f) { return -repuFactor * tree.weight * (float) Math.log(dist) * repu[index]; } else { return -repuFactor * tree.weight * (float) Math.pow(dist, repuExponent) / repuExponent * repu[index]; } } /** * Returns the energy of the specified node. * @param index Index of a node. * @return Energy of the node. */ private float getEnergy(final int index) { List<GraphVertex> vertices = graph.getVertices(); GraphVertex vertex = vertices.get(index); Position vertexPosition = vertex.getPosition(); // repulsion energy float energy = getRepulsionEnergy(vertexPosition, index, octTree); // attraction energy for (int i = 0; i < attrIndexes[index].length; i++) { if (attrIndexes[index][i] != index) { float dist = vertices.get(attrIndexes[index][i]).distanceTo(vertexPosition); if (attrExponent == 0.0f) { energy += attrValues[index][i] * (float) Math.log(dist); } else { energy += attrValues[index][i] * (float) Math.pow(dist, attrExponent) / attrExponent; } } } // gravitation energy float dist = vertexPosition.distanceTo(baryCenter); if (attrExponent == 0.0f) { energy += gravitationFactor * repuFactor * repu[index] * (float) Math.log(dist); } else { energy += gravitationFactor * repuFactor * repu[index] * (float) Math.pow(dist, attrExponent) / attrExponent; } return energy; } /** * Computes the direction of the repulsion force from the tree * on the specified node. * @param index Index of the repulsed node. * @param tree Repulsing octtree. * @param dir Direction of the repulsion force acting on the node * is added to this variable (output parameter). * @return Approximate second derivation of the repulsion energy. */ private float addRepulsionDir(final int index, OctTree tree, Position dir) { if (tree == null || tree.index == index) { return 0.0f; } GraphVertex vertex = graph.getVertices().get(index); float dist = vertex.distanceTo(tree.position); if (tree.index < 0 && dist < tree.width()) { float dir2 = 0.0f; for (OctTree lElement : tree.children) { dir2 += addRepulsionDir(index, lElement, dir); } return dir2; } if (dist != 0.0) { float tmp = repuFactor * tree.weight * repu[index] * (float) Math.pow(dist, repuExponent - 2); // dir -= (tree.position - lVertexIndex) * tmp; dir.subtract(Position.mult(Position.subtract(tree.position, vertex.getPosition()), tmp)); return tmp * Math.abs(repuExponent - 1); } return 0.0f; } /** * Computes the direction of the total force acting on the specified node. * @param index Index of a node. * @param dir Direction of the total force acting on the node * (output parameter). */ private void getDirection(final int index, Position dir) { Position vertexPosition = graph.getVertices().get(index).getPosition(); dir.x = 0.0f; dir.y = 0.0f; dir.z = 0.0f; // compute repulsion force vector float dir2 = addRepulsionDir(index, octTree, dir); // compute attraction force vector for (int i = 0; i < attrIndexes[index].length; i++) { if (attrIndexes[index][i] != index) { GraphVertex vertex = graph.getVertices().get(attrIndexes[index][i]); float dist = vertex.distanceTo(vertexPosition); float tmp = attrValues[index][i] * (float) Math.pow(dist, attrExponent - 2); // dir += (graph.vertices.get(attrIndexes[index][i]).pos - lVertexIndexPos) // * tmp; dir.add(Position.mult(Position.subtract(vertex.getPosition(), vertexPosition), tmp)); dir2 += tmp * Math.abs(attrExponent - 1); } } // compute gravitation force vector float dist = vertexPosition.distanceTo(baryCenter); dir2 += gravitationFactor * repuFactor * repu[index] * (float) Math.pow(dist, attrExponent - 2) * Math.abs(attrExponent - 1); // dir += gravitationFactor * repuFactor * repu[index] // * (float) Math.pow(dist, attrExponent - 2) // * (baryCenter - lVertexIndexPos); dir.add(Position.mult(Position.subtract(baryCenter, vertexPosition), gravitationFactor * repuFactor * repu[index] * (float) Math.pow(dist, attrExponent - 2))); // normalize force vector with second derivation of energy dir.div(dir2); // ensure that the length of dir is at most 1/8 // of the maximum Euclidean distance between nodes float length = (float) Math.sqrt(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z); if (length > octTree.width() / 8) { length /= octTree.width() / 8; dir.div(length); } } /** * Builds the octtree. */ private void buildOctTree() { // compute minima and maxima of positions in each dimension Position minPos = Position.min(graph.getVertices(), false); Position maxPos = Position.max(graph.getVertices(), false); // add nodes to the octtree octTree = new OctTree(0, graph.getVertices().get(0).getPosition(), repu[0], minPos, maxPos); int nodeCount = this.graph.getVertices().size(); for (int i = 1; i < nodeCount; i++) { octTree.addNode(i, graph.getVertices().get(i).getPosition(), repu[i]); // 1.0f); } } /** * Computes the factor for repulsion forces <code>repuFactor</code> * such that in the energy minimum the average Euclidean distance * between pairs of nodes with similarity 1.0 is approximately 1. */ private float computeRepuFactor() { float attrSum = 0.0f; int nodeCount = this.graph.getVertices().size(); for (int i = 1; i < nodeCount; i++) { for (int j = 0; j < attrValues[i].length; j++) { attrSum += attrValues[i][j]; } } float repuSum = 0.0f; for (int i = 0; i < nodeCount; i++) { repuSum += repu[i]; } if (repuSum > 0 && attrSum > 0) { return attrSum / (repuSum * repuSum) * (float) Math.pow(repuSum, 0.5f * (attrExponent - repuExponent)); } return 1.0f; } /** * Computes the position of the barycenter <code>baryCenter</code> * of all nodes. */ private void computeBaryCenter() { // Reset. int vertexCount = 0; baryCenter = new Position(); for (GraphVertex vertex : graph.getVertices()) { baryCenter.add(vertex.getPosition()); vertexCount++; } baryCenter.div(vertexCount); } /** * Computes and outputs some statistics. */ private void analyzeDistances() { float edgeLengthSum = 0.0f; float edgeLengthLogSum = 0.0f; float attrSum = 0.0f; int nodeCount = graph.getVertices().size(); for (int i = 0; i < nodeCount; i++) { GraphVertex vertex = graph.getVertices().get(i); for (int j = 0; j < attrValues[i].length; j++) { float dist = vertex.distanceTo(graph.getVertices().get(attrIndexes[i][j])); float distLog = (float) Math.log(dist); edgeLengthSum += attrValues[i][j] * dist; edgeLengthLogSum += attrValues[i][j] * distLog; attrSum += attrValues[i][j]; } } edgeLengthSum /= 2; edgeLengthLogSum /= 2; attrSum /= 2; if (options.verbosity.isAtLeast(Verbosity.WARNING)) { System.err.println(); System.err.println("Number of Nodes: " + nodeCount); System.err.println("Overall Attraction: " + attrSum); System.err.println("Arithmetic mean of edge lengths: " + edgeLengthSum / attrSum); System.err.println("Geometric mean of edge lengths: " + (float) Math.exp(edgeLengthLogSum / attrSum)); } } /** * Octtree for graph nodes with positions in 3D space. * Contains all graph nodes that are located in a given cuboid in 3D space. */ public static class OctTree { /** For leafs, the unique index of the graph node; for non-leafs -1. */ private int index; /** Children of this tree node. */ private final OctTree[] children = new OctTree[8]; /** Barycenter of the contained graph nodes. */ private Position position; /** Total weight of the contained graph nodes. */ private float weight; /** Minimum coordinates of the cuboid in each of the 3 dimensions. */ private final Position minPos; /** Maximum coordinates of the cuboid in each of the 3 dimensions. */ private final Position maxPos; /** * Creates an octtree containing one graph node. * * @param index Unique index of the graph node. * @param position Position of the graph node. * @param weight Weight of the graph node. * @param minPos Minimum coordinates of the cuboid. * @param maxPos Maximum coordinates of the cuboid. */ public OctTree(int index, Position position, float weight, Position minPos, Position maxPos) { this.index = index; this.position = new Position(position); this.weight = weight; this.minPos = minPos; this.maxPos = maxPos; } /** * Adds a graph node to the octtree. * * @param nodeIndex Unique index of the graph node. * @param nodePos Position of the graph node. * @param nodeWeight Weight of the graph node. */ private void addNode(int nodeIndex, Position nodePos, float nodeWeight) { if (nodeWeight == 0.0f) { return; } if (index >= 0) { addNode2(index, position, weight); index = -1; } // position = (position * weight + nodePos * nodeWeight) // / (weight + nodeWeight) position = Position.div(Position.add(Position.mult(position, weight), Position.mult(nodePos, nodeWeight)), weight + nodeWeight); weight += nodeWeight; addNode2(nodeIndex, nodePos, nodeWeight); } /** * Adds a graph node to the octtree, * without changing the position and weight of the root. * * @param nodeIndex Unique index of the graph node. * @param nodePos Position of the graph node. * @param nodeWeight Weight of the graph node. */ private void addNode2(int nodeIndex, Position nodePos, float nodeWeight) { int childIndex = 0; if (nodePos.x > (minPos.x + maxPos.x) / 2) { childIndex += 1 << 0; } if (nodePos.y > (minPos.y + maxPos.y) / 2) { childIndex += 1 << 1; } if (nodePos.z > (minPos.z + maxPos.z) / 2) { childIndex += 1 << 2; } if (children[childIndex] == null) { Position newMinPos = new Position(); Position newMaxPos = new Position(); if ((childIndex & 1 << 0) == 0) { newMinPos.x = minPos.x; newMaxPos.x = (minPos.x + maxPos.x) / 2; } else { newMinPos.x = (minPos.x + maxPos.x) / 2; newMaxPos.x = maxPos.x; } if ((childIndex & 1 << 1) == 0) { newMinPos.y = minPos.y; newMaxPos.y = (minPos.y + maxPos.y) / 2; } else { newMinPos.y = (minPos.y + maxPos.y) / 2; newMaxPos.y = maxPos.y; } if ((childIndex & 1 << 2) == 0) { newMinPos.z = minPos.z; newMaxPos.z = (minPos.z + maxPos.z) / 2; } else { newMinPos.z = (minPos.z + maxPos.z) / 2; newMaxPos.z = maxPos.z; } children[childIndex] = new OctTree(nodeIndex, nodePos, nodeWeight, newMinPos, newMaxPos); } else { children[childIndex].addNode(nodeIndex, nodePos, nodeWeight); } } /** * Updates the positions of the octtree nodes * when the position of a graph node has changed. * * @param oldPos Previous position of the graph node. * @param newPos New position of the graph node. * @param nodeWeight Weight of the graph node. */ private void moveNode(Position oldPos, Position newPos, float nodeWeight) { //position += (newPos - oldPos) * (nodeWeight / weight); position.add(Position.mult(Position.subtract(newPos, oldPos), nodeWeight / weight)); int childIndex = 0; if (oldPos.x > (minPos.x + maxPos.x) / 2) { childIndex += 1 << 0; } if (oldPos.y > (minPos.y + maxPos.y) / 2) { childIndex += 1 << 1; } if (oldPos.z > (minPos.z + maxPos.z) / 2) { childIndex += 1 << 2; } if (children[childIndex] != null) { children[childIndex].moveNode(oldPos, newPos, nodeWeight); } } /** * Returns the maximum extension of the octtree. * * @return Maximum over all dimensions of the extension of the octtree. */ private float width() { return Position.width(maxPos, minPos); } } }