All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jbox2d.collision.broadphase.DynamicTree Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License (MIT)
 *
 * FXGL - JavaFX Game Library
 *
 * Copyright (c) 2015-2017 AlmasB ([email protected])
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.jbox2d.collision.broadphase;

import com.almasb.fxgl.core.math.Vec2;
import javafx.scene.paint.Color;
import org.jbox2d.callbacks.DebugDraw;
import org.jbox2d.callbacks.TreeCallback;
import org.jbox2d.callbacks.TreeRayCastCallback;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.RayCastInput;
import org.jbox2d.common.JBoxSettings;
import org.jbox2d.common.JBoxUtils;

/**
 * A dynamic tree arranges data in a binary tree to accelerate queries such as volume queries and
 * ray casts. Leafs are proxies with an AABB. In the tree we expand the proxy AABB by _fatAABBFactor
 * so that the proxy AABB is bigger than the client object. This allows the client object to move by
 * small amounts without triggering a tree update.
 *
 * @author daniel
 */
public class DynamicTree implements BroadPhaseStrategy {
    public static final int MAX_STACK_SIZE = 64;
    public static final int NULL_NODE = -1;

    private DynamicTreeNode m_root;
    private DynamicTreeNode[] m_nodes;
    private int m_nodeCount;
    private int m_nodeCapacity;

    private int m_freeList;

    private final Vec2[] drawVecs = new Vec2[4];
    private DynamicTreeNode[] nodeStack = new DynamicTreeNode[20];
    private int nodeStackIndex = 0;

    public DynamicTree() {
        m_root = null;
        m_nodeCount = 0;
        m_nodeCapacity = 16;
        m_nodes = new DynamicTreeNode[16];

        // Build a linked list for the free list.
        for (int i = m_nodeCapacity - 1; i >= 0; i--) {
            m_nodes[i] = new DynamicTreeNode(i);
            m_nodes[i].parent = (i == m_nodeCapacity - 1) ? null : m_nodes[i + 1];
            m_nodes[i].height = -1;
        }
        m_freeList = 0;

        for (int i = 0; i < drawVecs.length; i++) {
            drawVecs[i] = new Vec2();
        }
    }

    @Override
    public final int createProxy(final AABB aabb, Object userData) {
        assert (aabb.isValid());
        final DynamicTreeNode node = allocateNode();
        int proxyId = node.id;
        // Fatten the aabb
        final AABB nodeAABB = node.aabb;
        nodeAABB.lowerBound.x = aabb.lowerBound.x - JBoxSettings.aabbExtension;
        nodeAABB.lowerBound.y = aabb.lowerBound.y - JBoxSettings.aabbExtension;
        nodeAABB.upperBound.x = aabb.upperBound.x + JBoxSettings.aabbExtension;
        nodeAABB.upperBound.y = aabb.upperBound.y + JBoxSettings.aabbExtension;
        node.userData = userData;

        insertLeaf(proxyId);

        return proxyId;
    }

    @Override
    public final void destroyProxy(int proxyId) {
        assert (0 <= proxyId && proxyId < m_nodeCapacity);
        DynamicTreeNode node = m_nodes[proxyId];
        assert (node.child1 == null);

        removeLeaf(node);
        freeNode(node);
    }

    @Override
    public final boolean moveProxy(int proxyId, final AABB aabb, Vec2 displacement) {
        assert (aabb.isValid());
        assert (0 <= proxyId && proxyId < m_nodeCapacity);
        final DynamicTreeNode node = m_nodes[proxyId];
        assert (node.child1 == null);

        final AABB nodeAABB = node.aabb;
        // if (nodeAABB.contains(aabb)) {
        if (nodeAABB.lowerBound.x <= aabb.lowerBound.x && nodeAABB.lowerBound.y <= aabb.lowerBound.y
                && aabb.upperBound.x <= nodeAABB.upperBound.x && aabb.upperBound.y <= nodeAABB.upperBound.y) {
            return false;
        }

        removeLeaf(node);

        // Extend AABB
        final Vec2 lowerBound = nodeAABB.lowerBound;
        final Vec2 upperBound = nodeAABB.upperBound;
        lowerBound.x = aabb.lowerBound.x - JBoxSettings.aabbExtension;
        lowerBound.y = aabb.lowerBound.y - JBoxSettings.aabbExtension;
        upperBound.x = aabb.upperBound.x + JBoxSettings.aabbExtension;
        upperBound.y = aabb.upperBound.y + JBoxSettings.aabbExtension;

        // Predict AABB displacement.
        final float dx = displacement.x * JBoxSettings.aabbMultiplier;
        final float dy = displacement.y * JBoxSettings.aabbMultiplier;
        if (dx < 0.0f) {
            lowerBound.x += dx;
        } else {
            upperBound.x += dx;
        }

        if (dy < 0.0f) {
            lowerBound.y += dy;
        } else {
            upperBound.y += dy;
        }

        insertLeaf(proxyId);
        return true;
    }

    @Override
    public final Object getUserData(int proxyId) {
        assert (0 <= proxyId && proxyId < m_nodeCapacity);
        return m_nodes[proxyId].userData;
    }

    @Override
    public final AABB getFatAABB(int proxyId) {
        assert (0 <= proxyId && proxyId < m_nodeCapacity);
        return m_nodes[proxyId].aabb;
    }

    @Override
    public final void query(TreeCallback callback, AABB aabb) {
        assert (aabb.isValid());
        nodeStackIndex = 0;
        nodeStack[nodeStackIndex++] = m_root;

        while (nodeStackIndex > 0) {
            DynamicTreeNode node = nodeStack[--nodeStackIndex];
            if (node == null) {
                continue;
            }

            if (AABB.testOverlap(node.aabb, aabb)) {
                if (node.child1 == null) {
                    boolean proceed = callback.treeCallback(node.id);
                    if (!proceed) {
                        return;
                    }
                } else {
                    if (nodeStack.length - nodeStackIndex - 2 <= 0) {
                        DynamicTreeNode[] newBuffer = new DynamicTreeNode[nodeStack.length * 2];
                        System.arraycopy(nodeStack, 0, newBuffer, 0, nodeStack.length);
                        nodeStack = newBuffer;
                    }
                    nodeStack[nodeStackIndex++] = node.child1;
                    nodeStack[nodeStackIndex++] = node.child2;
                }
            }
        }
    }

    private final Vec2 r = new Vec2();
    private final AABB aabb = new AABB();
    private final RayCastInput subInput = new RayCastInput();

    @Override
    public void raycast(TreeRayCastCallback callback, RayCastInput input) {
        final Vec2 p1 = input.p1;
        final Vec2 p2 = input.p2;
        float p1x = p1.x, p2x = p2.x, p1y = p1.y, p2y = p2.y;
        float vx, vy;
        float rx, ry;
        float absVx, absVy;
        float cx, cy;
        float hx, hy;
        float tempx, tempy;
        r.x = p2x - p1x;
        r.y = p2y - p1y;
        assert ((r.x * r.x + r.y * r.y) > 0f);
        r.normalize();
        rx = r.x;
        ry = r.y;

        // v is perpendicular to the segment.
        vx = -1f * ry;
        vy = 1f * rx;
        absVx = JBoxUtils.abs(vx);
        absVy = JBoxUtils.abs(vy);

        // Separating axis for segment (Gino, p80).
        // |dot(v, p1 - c)| > dot(|v|, h)

        float maxFraction = input.maxFraction;

        // Build a bounding box for the segment.
        final AABB segAABB = aabb;
        // Vec2 t = p1 + maxFraction * (p2 - p1);
        // before inline
        // temp.set(p2).subLocal(p1).mulLocal(maxFraction).addLocal(p1);
        // Vec2.minToOut(p1, temp, segAABB.lowerBound);
        // Vec2.maxToOut(p1, temp, segAABB.upperBound);
        tempx = (p2x - p1x) * maxFraction + p1x;
        tempy = (p2y - p1y) * maxFraction + p1y;
        segAABB.lowerBound.x = p1x < tempx ? p1x : tempx;
        segAABB.lowerBound.y = p1y < tempy ? p1y : tempy;
        segAABB.upperBound.x = p1x > tempx ? p1x : tempx;
        segAABB.upperBound.y = p1y > tempy ? p1y : tempy;
        // end inline

        nodeStackIndex = 0;
        nodeStack[nodeStackIndex++] = m_root;
        while (nodeStackIndex > 0) {
            final DynamicTreeNode node = nodeStack[--nodeStackIndex];
            if (node == null) {
                continue;
            }

            final AABB nodeAABB = node.aabb;
            if (!AABB.testOverlap(nodeAABB, segAABB)) {
                continue;
            }

            // Separating axis for segment (Gino, p80).
            // |dot(v, p1 - c)| > dot(|v|, h)
            // node.aabb.getCenterToOut(c);
            // node.aabb.getExtentsToOut(h);
            cx = (nodeAABB.lowerBound.x + nodeAABB.upperBound.x) * .5f;
            cy = (nodeAABB.lowerBound.y + nodeAABB.upperBound.y) * .5f;
            hx = (nodeAABB.upperBound.x - nodeAABB.lowerBound.x) * .5f;
            hy = (nodeAABB.upperBound.y - nodeAABB.lowerBound.y) * .5f;
            tempx = p1x - cx;
            tempy = p1y - cy;
            float separation = JBoxUtils.abs(vx * tempx + vy * tempy) - (absVx * hx + absVy * hy);
            if (separation > 0.0f) {
                continue;
            }

            if (node.child1 == null) {
                subInput.p1.x = p1x;
                subInput.p1.y = p1y;
                subInput.p2.x = p2x;
                subInput.p2.y = p2y;
                subInput.maxFraction = maxFraction;

                float value = callback.raycastCallback(subInput, node.id);

                if (value == 0.0f) {
                    // The client has terminated the ray cast.
                    return;
                }

                if (value > 0.0f) {
                    // Update segment bounding box.
                    maxFraction = value;
                    // temp.set(p2).subLocal(p1).mulLocal(maxFraction).addLocal(p1);
                    // Vec2.minToOut(p1, temp, segAABB.lowerBound);
                    // Vec2.maxToOut(p1, temp, segAABB.upperBound);
                    tempx = (p2x - p1x) * maxFraction + p1x;
                    tempy = (p2y - p1y) * maxFraction + p1y;
                    segAABB.lowerBound.x = p1x < tempx ? p1x : tempx;
                    segAABB.lowerBound.y = p1y < tempy ? p1y : tempy;
                    segAABB.upperBound.x = p1x > tempx ? p1x : tempx;
                    segAABB.upperBound.y = p1y > tempy ? p1y : tempy;
                }
            } else {
                if (nodeStack.length - nodeStackIndex - 2 <= 0) {
                    DynamicTreeNode[] newBuffer = new DynamicTreeNode[nodeStack.length * 2];
                    System.arraycopy(nodeStack, 0, newBuffer, 0, nodeStack.length);
                    nodeStack = newBuffer;
                }
                nodeStack[nodeStackIndex++] = node.child1;
                nodeStack[nodeStackIndex++] = node.child2;
            }
        }
    }

    @Override
    public final int computeHeight() {
        return computeHeight(m_root);
    }

    private final int computeHeight(DynamicTreeNode node) {
        assert (0 <= node.id && node.id < m_nodeCapacity);

        if (node.child1 == null) {
            return 0;
        }
        int height1 = computeHeight(node.child1);
        int height2 = computeHeight(node.child2);
        return 1 + JBoxUtils.max(height1, height2);
    }

    /**
     * Validate this tree. For testing.
     */
    public void validate() {
        validateStructure(m_root);
        validateMetrics(m_root);

        int freeCount = 0;
        DynamicTreeNode freeNode = m_freeList != NULL_NODE ? m_nodes[m_freeList] : null;
        while (freeNode != null) {
            assert (0 <= freeNode.id && freeNode.id < m_nodeCapacity);
            assert (freeNode == m_nodes[freeNode.id]);
            freeNode = freeNode.parent;
            ++freeCount;
        }

        assert (getHeight() == computeHeight());

        assert (m_nodeCount + freeCount == m_nodeCapacity);
    }

    @Override
    public int getHeight() {
        if (m_root == null) {
            return 0;
        }
        return m_root.height;
    }

    @Override
    public int getMaxBalance() {
        int maxBalance = 0;
        for (int i = 0; i < m_nodeCapacity; ++i) {
            final DynamicTreeNode node = m_nodes[i];
            if (node.height <= 1) {
                continue;
            }

            assert (node.child1 == null == false);

            DynamicTreeNode child1 = node.child1;
            DynamicTreeNode child2 = node.child2;
            int balance = JBoxUtils.abs(child2.height - child1.height);
            maxBalance = JBoxUtils.max(maxBalance, balance);
        }

        return maxBalance;
    }

    @Override
    public float getAreaRatio() {
        if (m_root == null) {
            return 0.0f;
        }

        final DynamicTreeNode root = m_root;
        float rootArea = root.aabb.getPerimeter();

        float totalArea = 0.0f;
        for (int i = 0; i < m_nodeCapacity; ++i) {
            final DynamicTreeNode node = m_nodes[i];
            if (node.height < 0) {
                // Free node in pool
                continue;
            }

            totalArea += node.aabb.getPerimeter();
        }

        return totalArea / rootArea;
    }

    /**
     * Build an optimal tree. Very expensive. For testing.
     */
    public void rebuildBottomUp() {
        int[] nodes = new int[m_nodeCount];
        int count = 0;

        // Build array of leaves. Free the rest.
        for (int i = 0; i < m_nodeCapacity; ++i) {
            if (m_nodes[i].height < 0) {
                // free node in pool
                continue;
            }

            DynamicTreeNode node = m_nodes[i];
            if (node.child1 == null) {
                node.parent = null;
                nodes[count] = i;
                ++count;
            } else {
                freeNode(node);
            }
        }

        AABB b = new AABB();
        while (count > 1) {
            float minCost = Float.MAX_VALUE;
            int iMin = -1, jMin = -1;
            for (int i = 0; i < count; ++i) {
                AABB aabbi = m_nodes[nodes[i]].aabb;

                for (int j = i + 1; j < count; ++j) {
                    AABB aabbj = m_nodes[nodes[j]].aabb;
                    b.combine(aabbi, aabbj);
                    float cost = b.getPerimeter();
                    if (cost < minCost) {
                        iMin = i;
                        jMin = j;
                        minCost = cost;
                    }
                }
            }

            int index1 = nodes[iMin];
            int index2 = nodes[jMin];
            DynamicTreeNode child1 = m_nodes[index1];
            DynamicTreeNode child2 = m_nodes[index2];

            DynamicTreeNode parent = allocateNode();
            parent.child1 = child1;
            parent.child2 = child2;
            parent.height = 1 + JBoxUtils.max(child1.height, child2.height);
            parent.aabb.combine(child1.aabb, child2.aabb);
            parent.parent = null;

            child1.parent = parent;
            child2.parent = parent;

            nodes[jMin] = nodes[count - 1];
            nodes[iMin] = parent.id;
            --count;
        }

        m_root = m_nodes[nodes[0]];

        validate();
    }

    private final DynamicTreeNode allocateNode() {
        if (m_freeList == NULL_NODE) {
            assert (m_nodeCount == m_nodeCapacity);

            DynamicTreeNode[] old = m_nodes;
            m_nodeCapacity *= 2;
            m_nodes = new DynamicTreeNode[m_nodeCapacity];
            System.arraycopy(old, 0, m_nodes, 0, old.length);

            // Build a linked list for the free list.
            for (int i = m_nodeCapacity - 1; i >= m_nodeCount; i--) {
                m_nodes[i] = new DynamicTreeNode(i);
                m_nodes[i].parent = (i == m_nodeCapacity - 1) ? null : m_nodes[i + 1];
                m_nodes[i].height = -1;
            }
            m_freeList = m_nodeCount;
        }
        int nodeId = m_freeList;
        final DynamicTreeNode treeNode = m_nodes[nodeId];
        m_freeList = treeNode.parent != null ? treeNode.parent.id : NULL_NODE;

        treeNode.parent = null;
        treeNode.child1 = null;
        treeNode.child2 = null;
        treeNode.height = 0;
        treeNode.userData = null;
        ++m_nodeCount;
        return treeNode;
    }

    /**
     * returns a node to the pool
     */
    private final void freeNode(DynamicTreeNode node) {
        assert (node != null);
        assert (0 < m_nodeCount);
        node.parent = m_freeList != NULL_NODE ? m_nodes[m_freeList] : null;
        node.height = -1;
        m_freeList = node.id;
        m_nodeCount--;
    }

    private final AABB combinedAABB = new AABB();

    private final void insertLeaf(int leaf_index) {
        DynamicTreeNode leaf = m_nodes[leaf_index];
        if (m_root == null) {
            m_root = leaf;
            m_root.parent = null;
            return;
        }

        // find the best sibling
        AABB leafAABB = leaf.aabb;
        DynamicTreeNode index = m_root;
        while (index.child1 != null) {
            final DynamicTreeNode node = index;
            DynamicTreeNode child1 = node.child1;
            DynamicTreeNode child2 = node.child2;

            float area = node.aabb.getPerimeter();

            combinedAABB.combine(node.aabb, leafAABB);
            float combinedArea = combinedAABB.getPerimeter();

            // Cost of creating a new parent for this node and the new leaf
            float cost = 2.0f * combinedArea;

            // Minimum cost of pushing the leaf further down the tree
            float inheritanceCost = 2.0f * (combinedArea - area);

            // Cost of descending into child1
            float cost1;
            if (child1.child1 == null) {
                combinedAABB.combine(leafAABB, child1.aabb);
                cost1 = combinedAABB.getPerimeter() + inheritanceCost;
            } else {
                combinedAABB.combine(leafAABB, child1.aabb);
                float oldArea = child1.aabb.getPerimeter();
                float newArea = combinedAABB.getPerimeter();
                cost1 = (newArea - oldArea) + inheritanceCost;
            }

            // Cost of descending into child2
            float cost2;
            if (child2.child1 == null) {
                combinedAABB.combine(leafAABB, child2.aabb);
                cost2 = combinedAABB.getPerimeter() + inheritanceCost;
            } else {
                combinedAABB.combine(leafAABB, child2.aabb);
                float oldArea = child2.aabb.getPerimeter();
                float newArea = combinedAABB.getPerimeter();
                cost2 = newArea - oldArea + inheritanceCost;
            }

            // Descend according to the minimum cost.
            if (cost < cost1 && cost < cost2) {
                break;
            }

            // Descend
            if (cost1 < cost2) {
                index = child1;
            } else {
                index = child2;
            }
        }

        DynamicTreeNode sibling = index;
        DynamicTreeNode oldParent = m_nodes[sibling.id].parent;
        final DynamicTreeNode newParent = allocateNode();
        newParent.parent = oldParent;
        newParent.userData = null;
        newParent.aabb.combine(leafAABB, sibling.aabb);
        newParent.height = sibling.height + 1;

        if (oldParent != null) {
            // The sibling was not the root.
            if (oldParent.child1 == sibling) {
                oldParent.child1 = newParent;
            } else {
                oldParent.child2 = newParent;
            }

            newParent.child1 = sibling;
            newParent.child2 = leaf;
            sibling.parent = newParent;
            leaf.parent = newParent;
        } else {
            // The sibling was the root.
            newParent.child1 = sibling;
            newParent.child2 = leaf;
            sibling.parent = newParent;
            leaf.parent = newParent;
            m_root = newParent;
        }

        // Walk back up the tree fixing heights and AABBs
        index = leaf.parent;
        while (index != null) {
            index = balance(index);

            DynamicTreeNode child1 = index.child1;
            DynamicTreeNode child2 = index.child2;

            assert (child1 != null);
            assert (child2 != null);

            index.height = 1 + JBoxUtils.max(child1.height, child2.height);
            index.aabb.combine(child1.aabb, child2.aabb);

            index = index.parent;
        }
        // validate();
    }

    private final void removeLeaf(DynamicTreeNode leaf) {
        if (leaf == m_root) {
            m_root = null;
            return;
        }

        DynamicTreeNode parent = leaf.parent;
        DynamicTreeNode grandParent = parent.parent;
        DynamicTreeNode sibling;
        if (parent.child1 == leaf) {
            sibling = parent.child2;
        } else {
            sibling = parent.child1;
        }

        if (grandParent != null) {
            // Destroy parent and connect sibling to grandParent.
            if (grandParent.child1 == parent) {
                grandParent.child1 = sibling;
            } else {
                grandParent.child2 = sibling;
            }
            sibling.parent = grandParent;
            freeNode(parent);

            // Adjust ancestor bounds.
            DynamicTreeNode index = grandParent;
            while (index != null) {
                index = balance(index);

                DynamicTreeNode child1 = index.child1;
                DynamicTreeNode child2 = index.child2;

                index.aabb.combine(child1.aabb, child2.aabb);
                index.height = 1 + JBoxUtils.max(child1.height, child2.height);

                index = index.parent;
            }
        } else {
            m_root = sibling;
            sibling.parent = null;
            freeNode(parent);
        }

        // validate();
    }

    // Perform a left or right rotation if node A is imbalanced.
    // Returns the new root index.
    private DynamicTreeNode balance(DynamicTreeNode iA) {
        assert (iA != null);

        DynamicTreeNode A = iA;
        if (A.child1 == null || A.height < 2) {
            return iA;
        }

        DynamicTreeNode iB = A.child1;
        DynamicTreeNode iC = A.child2;
        assert (0 <= iB.id && iB.id < m_nodeCapacity);
        assert (0 <= iC.id && iC.id < m_nodeCapacity);

        DynamicTreeNode B = iB;
        DynamicTreeNode C = iC;

        int balance = C.height - B.height;

        // Rotate C up
        if (balance > 1) {
            DynamicTreeNode iF = C.child1;
            DynamicTreeNode iG = C.child2;
            DynamicTreeNode F = iF;
            DynamicTreeNode G = iG;
            assert (F != null);
            assert (G != null);
            assert (0 <= iF.id && iF.id < m_nodeCapacity);
            assert (0 <= iG.id && iG.id < m_nodeCapacity);

            // Swap A and C
            C.child1 = iA;
            C.parent = A.parent;
            A.parent = iC;

            // A's old parent should point to C
            if (C.parent != null) {
                if (C.parent.child1 == iA) {
                    C.parent.child1 = iC;
                } else {
                    assert (C.parent.child2 == iA);
                    C.parent.child2 = iC;
                }
            } else {
                m_root = iC;
            }

            // Rotate
            if (F.height > G.height) {
                C.child2 = iF;
                A.child2 = iG;
                G.parent = iA;
                A.aabb.combine(B.aabb, G.aabb);
                C.aabb.combine(A.aabb, F.aabb);

                A.height = 1 + JBoxUtils.max(B.height, G.height);
                C.height = 1 + JBoxUtils.max(A.height, F.height);
            } else {
                C.child2 = iG;
                A.child2 = iF;
                F.parent = iA;
                A.aabb.combine(B.aabb, F.aabb);
                C.aabb.combine(A.aabb, G.aabb);

                A.height = 1 + JBoxUtils.max(B.height, F.height);
                C.height = 1 + JBoxUtils.max(A.height, G.height);
            }

            return iC;
        }

        // Rotate B up
        if (balance < -1) {
            DynamicTreeNode iD = B.child1;
            DynamicTreeNode iE = B.child2;
            DynamicTreeNode D = iD;
            DynamicTreeNode E = iE;
            assert (0 <= iD.id && iD.id < m_nodeCapacity);
            assert (0 <= iE.id && iE.id < m_nodeCapacity);

            // Swap A and B
            B.child1 = iA;
            B.parent = A.parent;
            A.parent = iB;

            // A's old parent should point to B
            if (B.parent != null) {
                if (B.parent.child1 == iA) {
                    B.parent.child1 = iB;
                } else {
                    assert (B.parent.child2 == iA);
                    B.parent.child2 = iB;
                }
            } else {
                m_root = iB;
            }

            // Rotate
            if (D.height > E.height) {
                B.child2 = iD;
                A.child1 = iE;
                E.parent = iA;
                A.aabb.combine(C.aabb, E.aabb);
                B.aabb.combine(A.aabb, D.aabb);

                A.height = 1 + JBoxUtils.max(C.height, E.height);
                B.height = 1 + JBoxUtils.max(A.height, D.height);
            } else {
                B.child2 = iE;
                A.child1 = iD;
                D.parent = iA;
                A.aabb.combine(C.aabb, D.aabb);
                B.aabb.combine(A.aabb, E.aabb);

                A.height = 1 + JBoxUtils.max(C.height, D.height);
                B.height = 1 + JBoxUtils.max(A.height, E.height);
            }

            return iB;
        }

        return iA;
    }

    private void validateStructure(DynamicTreeNode node) {
        if (node == null) {
            return;
        }
        assert (node == m_nodes[node.id]);

        if (node == m_root) {
            assert (node.parent == null);
        }

        DynamicTreeNode child1 = node.child1;
        DynamicTreeNode child2 = node.child2;

        if (node.child1 == null) {
            assert (child1 == null);
            assert (child2 == null);
            assert (node.height == 0);
            return;
        }

        assert (child1 != null && 0 <= child1.id && child1.id < m_nodeCapacity);
        assert (child2 != null && 0 <= child2.id && child2.id < m_nodeCapacity);

        assert (child1.parent == node);
        assert (child2.parent == node);

        validateStructure(child1);
        validateStructure(child2);
    }

    private void validateMetrics(DynamicTreeNode node) {
        if (node == null) {
            return;
        }

        DynamicTreeNode child1 = node.child1;
        DynamicTreeNode child2 = node.child2;

        if (node.child1 == null) {
            assert (child1 == null);
            assert (child2 == null);
            assert (node.height == 0);
            return;
        }

        assert (child1 != null && 0 <= child1.id && child1.id < m_nodeCapacity);
        assert (child2 != null && 0 <= child2.id && child2.id < m_nodeCapacity);

        int height1 = child1.height;
        int height2 = child2.height;
        int height;
        height = 1 + JBoxUtils.max(height1, height2);
        assert (node.height == height);

        AABB aabb = new AABB();
        aabb.combine(child1.aabb, child2.aabb);

        assert (aabb.lowerBound.equals(node.aabb.lowerBound));
        assert (aabb.upperBound.equals(node.aabb.upperBound));

        validateMetrics(child1);
        validateMetrics(child2);
    }

    @Override
    public void drawTree(DebugDraw argDraw) {
        if (m_root == null) {
            return;
        }
        int height = computeHeight();
        drawTree(argDraw, m_root, 0, height);
    }

    private final Vec2 textVec = new Vec2();

    public void drawTree(DebugDraw argDraw, DynamicTreeNode node, int spot, int height) {
        node.aabb.getVertices(drawVecs);

        Color color = Color.color(1, (height - spot) * 1f / height, (height - spot) * 1f / height);
        argDraw.drawPolygon(drawVecs, 4, color);

        argDraw.getViewportTranform().getWorldToScreen(node.aabb.upperBound, textVec);
        argDraw.drawString(textVec.x, textVec.y, node.id + "-" + (spot + 1) + "/" + height, color);

        if (node.child1 != null) {
            drawTree(argDraw, node.child1, spot + 1, height);
        }
        if (node.child2 != null) {
            drawTree(argDraw, node.child2, spot + 1, height);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy