/* * The MIT License (MIT) * * Copyright (c) 2014-2017 Sri Harsha Chilakapati * * 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 com.shc.silenceengine.collision.broadphase; import com.shc.silenceengine.math.geom2d.Rectangle; import com.shc.silenceengine.scene.components.CollisionComponent2D; import java.util.ArrayList; import java.util.List; /** * A QuadTree implementation to reduce collision checks. Every level contains a maximum of 10 objects and the tree sub * divides on exceeding this limit. * * @author Sri Harsha Chilakapati */ public class QuadTree implements IBroadphase2D { // The MAX_OBJECTS and LEVEL constants private static final int MAX_OBJECTS = 10; private int level; // The objects list private List<CollisionComponent2D> objects; // The retrieve list private List<CollisionComponent2D> retrieveList; // The bounds of this tree private Rectangle bounds; // Branches of this tree a.k.a the quadrants private QuadTree[] nodes; /** * Constructs a QuadTree that covers a rectangle [0, 0, mapWidth, mapHeight] * * @param mapWidth The width of the map (in pixels) * @param mapHeight The height of the map (in pixels) */ public QuadTree(int mapWidth, int mapHeight) { this(0, new Rectangle(0, 0, mapWidth, mapHeight)); } /** * Construct a QuadTree with custom values. Used to create sub trees or branches * * @param l The level of this tree * @param b The bounds of this tree */ public QuadTree(int l, Rectangle b) { level = l; bounds = b; objects = new ArrayList<>(); retrieveList = new ArrayList<>(); nodes = new QuadTree[4]; } /** * Set's the bounds of this tree. * * @param x The x-coordinate * @param y The y-coordinate * @param width The width * @param height The height */ public void setBounds(int x, int y, int width, int height) { bounds.set(x, y, width, height); clear(); split(); } /** * Clear this tree. Also clears any subtrees. */ public void clear() { objects.clear(); for (int i = 0; i < nodes.length; i++) { if (nodes[i] != null) { nodes[i].clear(); nodes[i] = null; } } } /** * Insert an object into this tree */ public void insert(CollisionComponent2D r) { if (nodes[0] != null) { int index = getIndex(r); if (index != -1) { nodes[index].insert(r); return; } } objects.add(r); if (objects.size() > MAX_OBJECTS) { if (nodes[0] == null) { split(); } for (int i = 0; i < objects.size(); i++) { int index = getIndex(objects.get(i)); if (index != -1) { nodes[index].insert(objects.remove(i)); } } } } public void remove(CollisionComponent2D e) { if (nodes[0] != null) { int index = getIndex(e); if (index != -1) { nodes[index].remove(e); return; } } objects.remove(e); } /** * Returns the collidable objects with the given rectangle */ public List<CollisionComponent2D> retrieve(Rectangle r) { retrieveList.clear(); int index = getIndex(r); if (index != -1 && nodes[0] != null) { retrieveList = nodes[index].retrieve(r); } retrieveList.addAll(objects); return retrieveList; } // Split the tree into 4 quadrants private void split() { int subWidth = (int) (bounds.width / 2); int subHeight = (int) (bounds.height / 2); int x = (int) bounds.x; int y = (int) bounds.y; nodes[0] = new QuadTree(level + 1, new Rectangle(x + subWidth, y, subWidth, subHeight)); nodes[1] = new QuadTree(level + 1, new Rectangle(x, y, subWidth, subHeight)); nodes[2] = new QuadTree(level + 1, new Rectangle(x, y + subHeight, subWidth, subHeight)); nodes[3] = new QuadTree(level + 1, new Rectangle(x + subWidth, y + subHeight, subWidth, subHeight)); } // Get the index of an object private int getIndex(CollisionComponent2D entity) { return getIndex(entity.polygon.getBounds()); } // Get the index of a rectangle private int getIndex(Rectangle r) { int index = -1; double verticalMidpoint = bounds.x + (bounds.width / 2); double horizontalMidpoint = bounds.y + (bounds.height / 2); boolean topQuadrant = (r.y < horizontalMidpoint && r.y + r.height < horizontalMidpoint); boolean bottomQuadrant = (r.y > horizontalMidpoint); if (r.x < verticalMidpoint && r.x + r.width < verticalMidpoint) { if (topQuadrant) { index = 1; } else if (bottomQuadrant) { index = 2; } } else if (r.x > verticalMidpoint) { if (topQuadrant) { index = 0; } else if (bottomQuadrant) { index = 3; } } return index; } /** * Insert an ArrayList of objects into this tree. * * @param entities The list of entities. */ public void insertAll(List<CollisionComponent2D> entities) { for (CollisionComponent2D entity : entities) insert(entity); } }