/* * Copyright 2017 tao. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dongbat.jbump; import static com.dongbat.jbump.Grid.*; import static java.lang.Math.*; import java.util.ArrayList; import java.util.HashMap; /** * * @author tao */ public class World<E> { private HashMap<Float, HashMap<Float, Cell>> rows = new HashMap<Float, HashMap<Float, Cell>>(); private HashMap<Cell, Boolean> nonEmptyCells = new HashMap<Cell, Boolean>(); private Grid grid = new Grid(); private RectHelper rectHelper = new RectHelper(); private boolean tileMode = true; public void setTileMode(boolean tileMode) { this.tileMode = tileMode; } public boolean isTileMode() { return tileMode; } private void addItemToCell(Item<E> item, float cx, float cy) { if (!rows.containsKey(cy)) { rows.put(cy, new HashMap<Float, Cell>()); } HashMap<Float, Cell> row = rows.get(cy); if (!row.containsKey(cx)) { row.put(cx, new Cell()); } Cell cell = row.get(cx); nonEmptyCells.put(cell, true); if (!cell.items.containsKey(item)) { cell.items.put(item, true); cell.itemCount = cell.itemCount + 1; } } private boolean removeItemFromCell(Item item, float cx, float cy) { if (!rows.containsKey(cy)) { return false; } HashMap<Float, Cell> row = rows.get(cy); if (!row.containsKey(cx)) { return false; } Cell cell = row.get(cx); if (!cell.items.containsKey(item)) { return false; } cell.items.remove(item); cell.itemCount = cell.itemCount - 1; if (cell.itemCount == 0) { nonEmptyCells.remove(cell); } return true; } private HashMap<Item, Boolean> getDictItemsInCellRect(float cl, float ct, float cw, float ch, HashMap<Item, Boolean> result) { result.clear(); for (float cy = ct; cy < ct + ch; cy++) { if (rows.containsKey(cy)) { HashMap<Float, Cell> row = rows.get(cy); for (float cx = cl; cx < cl + cw; cx++) { if (row.containsKey(cx)) { Cell cell = row.get(cx); if (cell.itemCount > 0) { for (Item item : cell.items.keySet()) { result.put(item, true); } } } } } } return result; } private float cellSize = 64; private final ArrayList<Cell> getCellsTouchedBySegment_visited = new ArrayList<Cell>(); private ArrayList<Cell> getCellsTouchedBySegment(float x1, float y1, float x2, float y2, final ArrayList<Cell> result) { result.clear(); getCellsTouchedBySegment_visited.clear(); // use set final ArrayList<Cell> visited = getCellsTouchedBySegment_visited; grid.grid_traverse(cellSize, x1, y1, x2, y2, new TraverseCallback() { @Override public void onTraverse(float cx, float cy) { if (!rows.containsKey(cy)) { return; } HashMap<Float, Cell> row = rows.get(cy); if (!row.containsKey(cx)) { return; } Cell cell = row.get(cx); if (visited.contains(cell)) { return; } visited.add(cell); result.add(cell); } }); return result; } public Collisions project(Item item, float x, float y, float w, float h, float goalX, float goalY, Collisions collisions) { return project(item, x, y, w, h, goalX, goalY, CollisionFilter.defaultFilter, collisions); } private final ArrayList<Item> project_visited = new ArrayList<Item>(); private final Rect project_c = new Rect(); private final HashMap<Item, Boolean> project_dictItemsInCellRect = new HashMap<Item, Boolean>(); public Collisions project(Item item, float x, float y, float w, float h, float goalX, float goalY, CollisionFilter filter, Collisions collisions) { collisions.clear(); ArrayList<Item> visited = project_visited; visited.clear(); if (item != null) { visited.add(item); } float tl = min(goalX, x); float tt = min(goalY, y); float tr = max(goalX + w, x + w); float tb = max(goalY + h, y + h); float tw = tr - tl; float th = tb - tt; grid.grid_toCellRect(cellSize, tl, tt, tw, th, project_c); float cl = project_c.x, ct = project_c.y, cw = project_c.w, ch = project_c.h; HashMap<Item, Boolean> dictItemsInCellRect = getDictItemsInCellRect(cl, ct, cw, ch, project_dictItemsInCellRect); for (Item other : dictItemsInCellRect.keySet()) { if (!visited.contains(other)) { visited.add(other); Response response = filter.filter(item, other); if (response != null) { Rect o = getRect(other); float ox = o.x, oy = o.y, ow = o.w, oh = o.h; Collision col = rectHelper.rect_detectCollision(x, y, w, h, ox, oy, ow, oh, goalX, goalY); if (col != null) { collisions.add(col.overlaps, col.ti, col.move.x, col.move.y, col.normal.x, col.normal.y, col.touch.x, col.touch.y, col.itemRect.x, col.itemRect.y, col.itemRect.w, col.itemRect.h, col.otherRect.x, col.otherRect.y, col.otherRect.w, col.otherRect.h, item, other, response); } } } } if (tileMode) { collisions.sort(); } return collisions; } private HashMap<Item, Rect> rects = new HashMap<Item, Rect>(); public Rect getRect(Item item) { return rects.get(item); } public int countCells() { int count = 0; for (HashMap<Float, Cell> row : rows.values()) { for (Float x : row.keySet()) { count++; } } return count; } public boolean hasItem(Item item) { return rects.containsKey(item); } public int countItems() { return rects.keySet().size(); } public Point toWorld(float cx, float cy, Point result) { grid_toWorld(cellSize, cx, cy, result); return result; } public Point toCell(float x, float y, Point result) { grid_toCell(cellSize, x, y, result); return result; } private final Rect add_c = new Rect(); public Item<E> add(Item<E> item, float x, float y, float w, float h) { if (rects.containsKey(item)) { return item; } rects.put(item, new Rect(x, y, w, h)); grid.grid_toCellRect(cellSize, x, y, w, h, add_c); float cl = add_c.x, ct = add_c.y, cw = add_c.w, ch = add_c.h; for (float cy = ct; cy < ct + ch; cy++) { for (float cx = cl; cx < cl + cw; cx++) { addItemToCell(item, cx, cy); } } return item; } private final Rect remove_c = new Rect(); public void remove(Item item) { Rect rect = getRect(item); float x = rect.x, y = rect.y, w = rect.w, h = rect.h; rects.remove(item); grid.grid_toCellRect(cellSize, x, y, w, h, remove_c); float cl = remove_c.x, ct = remove_c.y, cw = remove_c.w, ch = remove_c.h; for (float cy = ct; cy < ct + ch; cy++) { for (float cx = cl; cx < cl + cw; cx++) { removeItemFromCell(item, cx, cy); } } } public void update(Item item, float x2, float y2) { Rect rect = getRect(item); float x = rect.x, y = rect.y, w = rect.w, h = rect.h; update(item, x2, y2, w, h); } private final Rect update_c1 = new Rect(); private final Rect update_c2 = new Rect(); public void update(Item item, float x2, float y2, float w2, float h2) { Rect rect = getRect(item); float x1 = rect.x, y1 = rect.y, w1 = rect.w, h1 = rect.h; if (x1 != x2 || y1 != y2 || w1 != w2 || h1 != h2) { Rect c1 = grid.grid_toCellRect(cellSize, x1, y1, w1, h1, update_c1); Rect c2 = grid.grid_toCellRect(cellSize, x2, y2, w2, h2, update_c2); float cl1 = c1.x, ct1 = c1.y, cw1 = c1.w, ch1 = c1.h; float cl2 = c2.x, ct2 = c2.y, cw2 = c2.w, ch2 = c2.h; if (cl1 != cl2 || ct1 != ct2 || cw1 != cw2 || ch1 != ch2) { float cr1 = cl1 + cw1 - 1, cb1 = ct1 + ch1 - 1; float cr2 = cl2 + cw2 - 1, cb2 = ct2 + ch2 - 1; boolean cyOut; for (float cy = ct1; cy <= cb1; cy++) { cyOut = cy < ct2 || cy > cb2; for (float cx = cl1; cx <= cr1; cx++) { if (cyOut || cx < cl2 || cx > cr2) { removeItemFromCell(item, cx, cy); } } } for (float cy = ct2; cy <= cb2; cy++) { cyOut = cy < ct1 || cy > cb1; for (float cx = cl2; cx <= cr2; cx++) { if (cyOut || cx < cl1 || cy > cr1) { addItemToCell(item, cx, cy); } } } } rect.set(x2, y2, w2, h2); } } private final ArrayList<Item> check_visited = new ArrayList<Item>(); private final Collisions check_cols = new Collisions(); private final Collisions check_projectedCols = new Collisions(); private final Response.Result check_result = new Response.Result(); public Response.Result check(Item item, float goalX, float goalY, final CollisionFilter filter) { final ArrayList<Item> visited = check_visited; visited.clear(); visited.add(item); CollisionFilter visitedFilter = new CollisionFilter() { @Override public Response filter(Item item, Item other) { if (visited.contains(other)) { return null; } if (filter == null) { return defaultFilter.filter(item, other); } return filter.filter(item, other); } }; Rect rect = getRect(item); float x = rect.x, y = rect.y, w = rect.w, h = rect.h; Collisions cols = check_cols; cols.clear(); Collisions projectedCols = project(item, x, y, w, h, goalX, goalY, filter, check_projectedCols); Response.Result result = check_result; while (projectedCols != null && !projectedCols.isEmpty()) { Collision col = projectedCols.get(0); cols.add(col.overlaps, col.ti, col.move.x, col.move.y, col.normal.x, col.normal.y, col.touch.x, col.touch.y, col.itemRect.x, col.itemRect.y, col.itemRect.w, col.itemRect.h, col.otherRect.x, col.otherRect.y, col.otherRect.w, col.otherRect.h, col.item, col.other, col.type); visited.add(col.other); Response response = col.type; response.response(this, col, x, y, w, h, goalX, goalY, visitedFilter, result); goalX = result.goalX; goalY = result.goalY; projectedCols = result.projectedCollisions; } result.set(goalX, goalY); result.projectedCollisions.clear(); for (int i = 0; i < cols.size(); i++) { result.projectedCollisions.add(cols.get(i)); } return result; } public Response.Result move(Item item, float goalX, float goalY, CollisionFilter filter) { Response.Result result = check(item, goalX, goalY, filter); update(item, result.goalX, result.goalY); return result; } }