Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

581 wiersze
23 KiB

  1. /*
  2. * Copyright (c) 2009-2012 jMonkeyEngine
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  17. * may be used to endorse or promote products derived from this software
  18. * without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  22. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. package roadtrip.view;
  33. import com.jme3.bounding.BoundingBox;
  34. import com.jme3.export.InputCapsule;
  35. import com.jme3.export.JmeExporter;
  36. import com.jme3.export.JmeImporter;
  37. import com.jme3.export.OutputCapsule;
  38. import com.jme3.material.Material;
  39. import com.jme3.math.FastMath;
  40. import com.jme3.math.Vector2f;
  41. import com.jme3.math.Vector3f;
  42. import com.jme3.scene.Spatial;
  43. import com.jme3.scene.control.LodControl;
  44. import com.jme3.scene.control.UpdateControl;
  45. import com.jme3.terrain.Terrain;
  46. import com.jme3.terrain.geomipmap.LRUCache;
  47. import com.jme3.terrain.geomipmap.NormalRecalcControl;
  48. import com.jme3.terrain.geomipmap.TerrainGridListener;
  49. import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
  50. import com.jme3.terrain.geomipmap.TerrainQuad;
  51. import com.jme3.terrain.heightmap.HeightMap;
  52. import com.jme3.terrain.heightmap.HeightMapGrid;
  53. import java.io.IOException;
  54. import java.util.HashSet;
  55. import java.util.List;
  56. import java.util.Set;
  57. import java.util.concurrent.Callable;
  58. import java.util.concurrent.CancellationException;
  59. import java.util.concurrent.ExecutionException;
  60. import java.util.concurrent.ExecutorService;
  61. import java.util.concurrent.Future;
  62. import java.util.concurrent.LinkedBlockingQueue;
  63. import java.util.concurrent.ThreadFactory;
  64. import java.util.concurrent.ThreadPoolExecutor;
  65. import java.util.concurrent.TimeUnit;
  66. import java.util.logging.Level;
  67. import java.util.logging.Logger;
  68. /**
  69. * <p>
  70. * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.</p>
  71. * </p><p>
  72. * The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0.
  73. * TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its
  74. * updates as well. It does this in the overwritten update() method.
  75. * </p><p>
  76. * It uses an LRU (Least Recently Used) cache of 16 terrain tiles (full TerrainQuadTrees). The
  77. * center 4 are the ones that are visible. As the camera moves, it checks what camera cell it is in
  78. * and will attach the now visible tiles.
  79. * </p><p>
  80. * The 'quadIndex' variable is a 4x4 array that represents the tiles. The center
  81. * four (index numbers: 5, 6, 9, 10) are what is visible. Each quadIndex value is an
  82. * offset vector. The vector contains whole numbers and represents how many tiles in offset
  83. * this location is from the center of the map. So for example the index 11 [Vector3f(2, 0, 1)]
  84. * is located 2*terrainSize in X axis and 1*terrainSize in Z axis.
  85. * </p><p>
  86. * As the camera moves, it tests what cameraCell it is in. Each camera cell covers four quad tiles
  87. * and is half way inside each one.
  88. * </p><pre>
  89. * +-------+-------+
  90. * | 1 | 3 | Four terrainQuads that make up the grid
  91. * | *..|..* | with the cameraCell in the middle, covering
  92. * |----|--|--|----| all four quads.
  93. * | *..|..* |
  94. * | 2 | 4 |
  95. * +-------+-------+
  96. * </pre><p>
  97. * This results in the effect of when the camera gets half way across one of the sides of a quad to
  98. * an empty (non-loaded) area, it will trigger the system to load in the next tiles.
  99. * </p><p>
  100. * The tile loading is done on a background thread, and once the tile is loaded, then it is
  101. * attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from
  102. * the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile.
  103. * </p><p>
  104. * The loading of new tiles triggers events for any TerrainGridListeners. The events are:
  105. * <ul>
  106. * <li>tile Attached
  107. * <li>tile Detached
  108. * <li>grid moved.
  109. * </ul>
  110. * <p>
  111. * These allow physics to update, and other operation (often needed for loading the terrain) to occur
  112. * at the right time.
  113. * </p>
  114. * @author Anthyon
  115. */
  116. public class FineTerrainGrid extends TerrainQuad {
  117. protected static final Logger log = Logger.getLogger(FineTerrainGrid.class.getCanonicalName());
  118. protected Vector3f currentCamCell = Vector3f.ZERO;
  119. protected int quarterSize; // half of quadSize
  120. protected int quadSize;
  121. protected HeightMapGrid heightMapGrid;
  122. private TerrainGridTileLoader gridTileLoader;
  123. protected Vector3f[] quadIndex;
  124. protected Set<TerrainGridListener> listeners = new HashSet<>();
  125. protected Material material;
  126. //cache needs to be 1 row (4 cells) larger than what we care is cached
  127. protected LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<>(
  128. getSubdivisionsPerSide() * getSubdivisionsPerSide() + getSubdivisionsPerSide());
  129. protected int cellsLoaded = 0;
  130. protected int[] gridOffset;
  131. protected boolean runOnce = false;
  132. protected ExecutorService cacheExecutor;
  133. public int getSize() {
  134. return size;
  135. }
  136. public int getSubdivisionsPerSide()
  137. {
  138. return 8;
  139. }
  140. protected class UpdateQuadCache implements Runnable {
  141. protected final Vector3f location;
  142. public UpdateQuadCache(Vector3f location) {
  143. this.location = location;
  144. }
  145. /**
  146. * This is executed if the camera has moved into a new CameraCell and will load in
  147. * the new TerrainQuad tiles to be children of this TerrainGrid parent.
  148. * It will first check the LRU cache to see if the terrain tile is already there,
  149. * if it is not there, it will load it in and then cache that tile.
  150. * The terrain tiles get added to the quad tree back on the OGL thread using the
  151. * attachQuadAt() method. It also resets any cached values in TerrainQuad (such as
  152. * neighbours).
  153. */
  154. public void run() {
  155. for (int i = 0; i < getSubdivisionsPerSide(); i++) {
  156. for (int j = 0; j < getSubdivisionsPerSide(); j++) {
  157. int quadIdx = i * getSubdivisionsPerSide() + j;
  158. final Vector3f quadCell = location.add(quadIndex[quadIdx]);
  159. TerrainQuad q = cache.get(quadCell);
  160. if (q == null) {
  161. if (heightMapGrid != null) {
  162. // create the new Quad since it doesn't exist
  163. HeightMap heightMapAt = heightMapGrid.getHeightMapAt(quadCell);
  164. q = new TerrainQuad(getName() + "Quad" + quadCell, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
  165. q.setMaterial(material.clone());
  166. log.log(Level.FINE, "Loaded TerrainQuad {0} from HeightMapGrid", q.getName());
  167. } else if (gridTileLoader != null) {
  168. q = gridTileLoader.getTerrainQuadAt(quadCell);
  169. // only clone the material to the quad if it doesn't have a material of its own
  170. if(q.getMaterial()==null) q.setMaterial(material.clone());
  171. log.log(Level.FINE, "Loaded TerrainQuad {0} from TerrainQuadGrid", q.getName());
  172. }
  173. }
  174. cache.put(quadCell, q);
  175. final int quadrant = getQuadrant(quadIdx);
  176. final TerrainQuad newQuad = q;
  177. if (isCenter(quadIdx)) {
  178. // if it should be attached as a child right now, attach it
  179. getControl(UpdateControl.class).enqueue(new Callable() {
  180. // back on the OpenGL thread:
  181. public Object call() throws Exception {
  182. if (newQuad.getParent() != null) {
  183. attachQuadAt(newQuad, quadrant, quadCell, true);
  184. }
  185. else {
  186. attachQuadAt(newQuad, quadrant, quadCell, false);
  187. }
  188. return null;
  189. }
  190. });
  191. } else {
  192. getControl(UpdateControl.class).enqueue(new Callable() {
  193. public Object call() throws Exception {
  194. removeQuad(newQuad);
  195. return null;
  196. }
  197. });
  198. }
  199. }
  200. }
  201. getControl(UpdateControl.class).enqueue(new Callable() {
  202. // back on the OpenGL thread:
  203. public Object call() throws Exception {
  204. for (Spatial s : getChildren()) {
  205. if (s instanceof TerrainQuad) {
  206. TerrainQuad tq = (TerrainQuad)s;
  207. tq.resetCachedNeighbours();
  208. }
  209. }
  210. System.out.println("fixed normals "+location.clone().mult(size));
  211. setNeedToRecalculateNormals();
  212. return null;
  213. }
  214. });
  215. }
  216. }
  217. protected boolean isCenter(int quadIndex) {
  218. // The only thing that is not a center for us here are the edges of the grid.
  219. int w = getSubdivisionsPerSide();
  220. // left / right edge
  221. if (quadIndex % w == 0 || (quadIndex + 1) % w == 0) return false;
  222. // top / down edge
  223. if (quadIndex < w || quadIndex >= (w*w - w)) return false;
  224. // center
  225. return true;
  226. }
  227. protected int getQuadrant(int quadIndex) {
  228. return quadIndex + 1; // whatever, just not 0
  229. }
  230. public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid,
  231. Vector2f offset, float offsetAmount) {
  232. this.name = name;
  233. this.patchSize = patchSize;
  234. this.size = maxVisibleSize;
  235. this.stepScale = scale;
  236. this.offset = offset;
  237. this.offsetAmount = offsetAmount;
  238. initData();
  239. this.gridTileLoader = terrainQuadGrid;
  240. terrainQuadGrid.setPatchSize(this.patchSize);
  241. terrainQuadGrid.setQuadSize(this.quadSize);
  242. addControl(new UpdateControl());
  243. fixNormalEdges(new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2));
  244. addControl(new NormalRecalcControl(this));
  245. }
  246. public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid) {
  247. this(name, patchSize, maxVisibleSize, scale, terrainQuadGrid, new Vector2f(), 0);
  248. }
  249. public FineTerrainGrid(String name, int patchSize, int maxVisibleSize, TerrainGridTileLoader terrainQuadGrid) {
  250. this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, terrainQuadGrid);
  251. }
  252. public FineTerrainGrid() {
  253. }
  254. private void initData() {
  255. int maxVisibleSize = size;
  256. this.quarterSize = maxVisibleSize / getSubdivisionsPerSide();
  257. this.quadSize = (maxVisibleSize / getSubdivisionsPerSide()) + 1;
  258. this.totalSize = maxVisibleSize;
  259. this.gridOffset = new int[]{0, 0};
  260. /*
  261. * -z
  262. * |
  263. * 1|3
  264. * -x ----+---- x
  265. * 2|4
  266. * |
  267. * z
  268. */
  269. // generate a grid of quad positions
  270. this.quadIndex = new Vector3f[getSubdivisionsPerSide() * getSubdivisionsPerSide()];
  271. int i = 0;
  272. for (int z = -getSubdivisionsPerSide() / 2 + 1; z <= getSubdivisionsPerSide() / 2; z++) {
  273. for (int x = -getSubdivisionsPerSide() / 2 + 1; x <= getSubdivisionsPerSide() / 2; x++) {
  274. quadIndex[i++] = new Vector3f(x, 0, z);
  275. }
  276. }
  277. }
  278. /**
  279. * Get the location in cell-coordinates of the specified location.
  280. * Cell coordinates are integer corrdinates, usually with y=0, each
  281. * representing a cell in the world.
  282. * For example, moving right in the +X direction:
  283. * (0,0,0) (1,0,0) (2,0,0), (3,0,0)
  284. * and then down the -Z direction:
  285. * (3,0,-1) (3,0,-2) (3,0,-3)
  286. */
  287. public Vector3f getCamCell(Vector3f location) {
  288. Vector3f tile = getTileCell(location);
  289. Vector3f offsetHalf = new Vector3f(-0.5f, 0, -0.5f);
  290. Vector3f shifted = tile.subtract(offsetHalf);
  291. return new Vector3f(FastMath.floor(shifted.x), 0, FastMath.floor(shifted.z));
  292. }
  293. /**
  294. * Centered at 0,0.
  295. * Get the tile index location in integer form:
  296. * @param location world coordinate
  297. */
  298. public Vector3f getTileCell(Vector3f location) {
  299. Vector3f tileLoc = location.divide(this.getWorldScale().mult(this.quadSize));
  300. return tileLoc;
  301. }
  302. public TerrainGridTileLoader getGridTileLoader() {
  303. return gridTileLoader;
  304. }
  305. /**
  306. * Get the terrain tile at the specified world location, in XZ coordinates.
  307. */
  308. public Terrain getTerrainAt(Vector3f worldLocation) {
  309. if (worldLocation == null)
  310. return null;
  311. Vector3f tileCell = getTileCell(worldLocation.setY(0));
  312. tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z));
  313. return cache.get(tileCell);
  314. }
  315. /**
  316. * Get the terrain tile at the specified XZ cell coordinate (not world coordinate).
  317. * @param cellCoordinate integer cell coordinates
  318. * @return the terrain tile at that location
  319. */
  320. public Terrain getTerrainAtCell(Vector3f cellCoordinate) {
  321. return cache.get(cellCoordinate);
  322. }
  323. /**
  324. * Convert the world location into a cell location (integer coordinates)
  325. */
  326. public Vector3f toCellSpace(Vector3f worldLocation) {
  327. Vector3f tileCell = getTileCell(worldLocation);
  328. tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z));
  329. return tileCell;
  330. }
  331. /**
  332. * Convert the cell coordinate (integer coordinates) into world coordinates.
  333. */
  334. public Vector3f toWorldSpace(Vector3f cellLocation) {
  335. return cellLocation.mult(getLocalScale()).multLocal(quadSize - 1);
  336. }
  337. protected void removeQuad(TerrainQuad q) {
  338. if (q != null && ( q.getParent() != null) ) {
  339. for (TerrainGridListener l : listeners) {
  340. l.tileDetached(getTileCell(q.getWorldTranslation()), q);
  341. }
  342. q.setQuadrant((short)0);
  343. this.detachChild(q);
  344. cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this.
  345. }
  346. }
  347. /**
  348. * Runs on the rendering thread
  349. * @param shifted quads are still attached to the parent and don't need to re-load
  350. */
  351. protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f quadCell, boolean shifted) {
  352. q.setQuadrant((short) quadrant);
  353. if (!shifted)
  354. this.attachChild(q);
  355. Vector3f loc = quadCell.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now
  356. q.setLocalTranslation(loc);
  357. if (!shifted) {
  358. for (TerrainGridListener l : listeners) {
  359. l.tileAttached(quadCell, q);
  360. }
  361. }
  362. updateModelBound();
  363. }
  364. /**
  365. * Called when the camera has moved into a new cell. We need to
  366. * update what quads are in the scene now.
  367. *
  368. * Step 1: touch cache
  369. * LRU cache is used, so elements that need to remain
  370. * should be touched.
  371. *
  372. * Step 2: load new quads in background thread
  373. * if the camera has moved into a new cell, we load in new quads
  374. * @param camCell the cell the camera is in
  375. */
  376. protected void updateChildren(Vector3f camCell) {
  377. int dx = 0;
  378. int dy = 0;
  379. if (currentCamCell != null) {
  380. dx = (int) (camCell.x - currentCamCell.x);
  381. dy = (int) (camCell.z - currentCamCell.z);
  382. }
  383. int xMin = 0;
  384. int xMax = getSubdivisionsPerSide();
  385. int yMin = 0;
  386. int yMax = getSubdivisionsPerSide();
  387. if (dx < 0) { // camera moved to -X direction
  388. xMax = getSubdivisionsPerSide() - 1;
  389. } else if (dx > 0) { // camera moved to +X direction
  390. xMin = 1;
  391. }
  392. if (dy < 0) { // camera moved to -Y direction
  393. yMax = getSubdivisionsPerSide() - 1;
  394. } else if (dy > 0) { // camera moved to +Y direction
  395. yMin = 1;
  396. }
  397. // Touch the items in the cache that we are and will be interested in.
  398. // We activate cells in the direction we are moving. If we didn't move
  399. // either way in one of the axes (say X or Y axis) then they are all touched.
  400. for (int i = yMin; i < yMax; i++) {
  401. for (int j = xMin; j < xMax; j++) {
  402. cache.get(camCell.add(quadIndex[i * getSubdivisionsPerSide() + j]));
  403. }
  404. }
  405. // ---------------------------------------------------
  406. // ---------------------------------------------------
  407. if (cacheExecutor == null) {
  408. // use the same executor as the LODControl
  409. cacheExecutor = createExecutorService();
  410. }
  411. cacheExecutor.submit(new UpdateQuadCache(camCell));
  412. this.currentCamCell = camCell;
  413. }
  414. public void addListener(TerrainGridListener listener) {
  415. this.listeners.add(listener);
  416. }
  417. public Vector3f getCurrentCell() {
  418. return this.currentCamCell;
  419. }
  420. public void removeListener(TerrainGridListener listener) {
  421. this.listeners.remove(listener);
  422. }
  423. @Override
  424. public void setMaterial(Material mat) {
  425. this.material = mat;
  426. super.setMaterial(mat);
  427. }
  428. public void setQuadSize(int quadSize) {
  429. this.quadSize = quadSize;
  430. }
  431. @Override
  432. public void adjustHeight(List<Vector2f> xz, List<Float> height) {
  433. Vector3f currentGridLocation = getCurrentCell().mult(getLocalScale()).multLocal(quadSize - 1);
  434. for (Vector2f vect : xz) {
  435. vect.x -= currentGridLocation.x;
  436. vect.y -= currentGridLocation.z;
  437. }
  438. super.adjustHeight(xz, height);
  439. }
  440. @Override
  441. protected float getHeightmapHeight(int x, int z) {
  442. return super.getHeightmapHeight(x - gridOffset[0], z - gridOffset[1]);
  443. }
  444. @Override
  445. public int getNumMajorSubdivisions() {
  446. return 2;
  447. }
  448. @Override
  449. public Material getMaterial(Vector3f worldLocation) {
  450. if (worldLocation == null)
  451. return null;
  452. Vector3f tileCell = getTileCell(worldLocation);
  453. Terrain terrain = cache.get(tileCell);
  454. if (terrain == null)
  455. return null; // terrain not loaded for that cell yet!
  456. return terrain.getMaterial(worldLocation);
  457. }
  458. /**
  459. * This will print out any exceptions from the thread
  460. */
  461. protected ExecutorService createExecutorService() {
  462. final ThreadFactory threadFactory = new ThreadFactory() {
  463. public Thread newThread(Runnable r) {
  464. Thread th = new Thread(r);
  465. th.setName("jME TerrainGrid Thread");
  466. th.setDaemon(true);
  467. return th;
  468. }
  469. };
  470. ThreadPoolExecutor ex = new ThreadPoolExecutor(1, 1,
  471. 0L, TimeUnit.MILLISECONDS,
  472. new LinkedBlockingQueue<Runnable>(),
  473. threadFactory) {
  474. protected void afterExecute(Runnable r, Throwable t) {
  475. super.afterExecute(r, t);
  476. if (t == null && r instanceof Future<?>) {
  477. try {
  478. Future<?> future = (Future<?>) r;
  479. if (future.isDone())
  480. future.get();
  481. } catch (CancellationException ce) {
  482. t = ce;
  483. } catch (ExecutionException ee) {
  484. t = ee.getCause();
  485. } catch (InterruptedException ie) {
  486. Thread.currentThread().interrupt(); // ignore/reset
  487. }
  488. }
  489. if (t != null)
  490. t.printStackTrace();
  491. }
  492. };
  493. return ex;
  494. }
  495. @Override
  496. public void read(JmeImporter im) throws IOException {
  497. super.read(im);
  498. InputCapsule c = im.getCapsule(this);
  499. name = c.readString("name", null);
  500. size = c.readInt("size", 0);
  501. patchSize = c.readInt("patchSize", 0);
  502. stepScale = (Vector3f) c.readSavable("stepScale", null);
  503. offset = (Vector2f) c.readSavable("offset", null);
  504. offsetAmount = c.readFloat("offsetAmount", 0);
  505. gridTileLoader = (TerrainGridTileLoader) c.readSavable("terrainQuadGrid", null);
  506. material = (Material) c.readSavable("material", null);
  507. initData();
  508. if (gridTileLoader != null) {
  509. gridTileLoader.setPatchSize(this.patchSize);
  510. gridTileLoader.setQuadSize(this.quadSize);
  511. }
  512. }
  513. @Override
  514. public void write(JmeExporter ex) throws IOException {
  515. super.write(ex);
  516. OutputCapsule c = ex.getCapsule(this);
  517. c.write(gridTileLoader, "terrainQuadGrid", null);
  518. c.write(size, "size", 0);
  519. c.write(patchSize, "patchSize", 0);
  520. c.write(stepScale, "stepScale", null);
  521. c.write(offset, "offset", null);
  522. c.write(offsetAmount, "offsetAmount", 0);
  523. c.write(material, "material", null);
  524. }
  525. }