Node.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. /*
  2. * Copyright (c) 2009-2010 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 com.jme3.scene;
  33. import com.jme3.bounding.BoundingVolume;
  34. import com.jme3.collision.Collidable;
  35. import com.jme3.collision.CollisionResults;
  36. import com.jme3.export.JmeExporter;
  37. import com.jme3.export.JmeImporter;
  38. import com.jme3.export.Savable;
  39. import com.jme3.material.Material;
  40. import java.io.IOException;
  41. import java.util.ArrayList;
  42. import java.util.List;
  43. import java.util.Queue;
  44. import java.util.logging.Level;
  45. import java.util.logging.Logger;
  46. /**
  47. * <code>Node</code> defines an internal node of a scene graph. The internal
  48. * node maintains a collection of children and handles merging said children
  49. * into a single bound to allow for very fast culling of multiple nodes. Node
  50. * allows for any number of children to be attached.
  51. *
  52. * @author Mark Powell
  53. * @author Gregg Patton
  54. * @author Joshua Slack
  55. */
  56. public class Node extends Spatial implements Savable {
  57. private static final Logger logger = Logger.getLogger(Node.class.getName());
  58. /**
  59. * This node's children.
  60. */
  61. protected ArrayList<Spatial> children = new ArrayList<Spatial>(1);
  62. /**
  63. * Serialization only. Do not use.
  64. */
  65. public Node() {
  66. }
  67. /**
  68. * Constructor instantiates a new <code>Node</code> with a default empty
  69. * list for containing children.
  70. *
  71. * @param name
  72. * the name of the scene element. This is required for
  73. * identification and comparision purposes.
  74. */
  75. public Node(String name) {
  76. super(name);
  77. }
  78. /**
  79. *
  80. * <code>getQuantity</code> returns the number of children this node
  81. * maintains.
  82. *
  83. * @return the number of children this node maintains.
  84. */
  85. public int getQuantity() {
  86. return children.size();
  87. }
  88. @Override
  89. protected void setTransformRefresh(){
  90. super.setTransformRefresh();
  91. for (Spatial child : children){
  92. if ((child.refreshFlags & RF_TRANSFORM) != 0)
  93. continue;
  94. child.setTransformRefresh();
  95. }
  96. }
  97. @Override
  98. protected void setLightListRefresh(){
  99. super.setLightListRefresh();
  100. for (Spatial child : children){
  101. if ((child.refreshFlags & RF_LIGHTLIST) != 0)
  102. continue;
  103. child.setLightListRefresh();
  104. }
  105. }
  106. @Override
  107. protected void updateWorldBound(){
  108. super.updateWorldBound();
  109. // for a node, the world bound is a combination of all it's children
  110. // bounds
  111. BoundingVolume resultBound = null;
  112. for (int i = 0, cSize = children.size(); i < cSize; i++) {
  113. Spatial child = children.get(i);
  114. // child bound is assumed to be updated
  115. assert (child.refreshFlags & RF_BOUND) == 0;
  116. if (resultBound != null) {
  117. // merge current world bound with child world bound
  118. resultBound.mergeLocal(child.getWorldBound());
  119. } else {
  120. // set world bound to first non-null child world bound
  121. if (child.getWorldBound() != null) {
  122. resultBound = child.getWorldBound().clone(this.worldBound);
  123. }
  124. }
  125. }
  126. this.worldBound = resultBound;
  127. }
  128. @Override
  129. public void updateLogicalState(float tpf){
  130. super.updateLogicalState(tpf);
  131. // FIXME: Iterating through the children list backwards
  132. // to avoid IndexOutOfBoundsException. This is sometimes unreliable,
  133. // a more robust solution is needed.
  134. for (int i = children.size()-1; i >= 0; i--){
  135. Spatial child = children.get(i);
  136. child.updateLogicalState(tpf);
  137. }
  138. }
  139. @Override
  140. public void updateGeometricState(){
  141. if ((refreshFlags & RF_LIGHTLIST) != 0){
  142. updateWorldLightList();
  143. }
  144. if ((refreshFlags & RF_TRANSFORM) != 0){
  145. // combine with parent transforms- same for all spatial
  146. // subclasses.
  147. updateWorldTransforms();
  148. }
  149. // the important part- make sure child geometric state is refreshed
  150. // first before updating own world bound. This saves
  151. // a round-trip later on.
  152. // NOTE 9/19/09
  153. // Although it does save a round trip,
  154. for (int i = 0, cSize = children.size(); i < cSize; i++) {
  155. Spatial child = children.get(i);
  156. child.updateGeometricState();
  157. }
  158. if ((refreshFlags & RF_BOUND) != 0){
  159. updateWorldBound();
  160. }
  161. assert refreshFlags == 0;
  162. }
  163. /**
  164. * <code>getTriangleCount</code> returns the number of triangles contained
  165. * in all sub-branches of this node that contain geometry.
  166. *
  167. * @return the triangle count of this branch.
  168. */
  169. @Override
  170. public int getTriangleCount() {
  171. int count = 0;
  172. if(children != null) {
  173. for(int i = 0; i < children.size(); i++) {
  174. count += children.get(i).getTriangleCount();
  175. }
  176. }
  177. return count;
  178. }
  179. /**
  180. * <code>getVertexCount</code> returns the number of vertices contained
  181. * in all sub-branches of this node that contain geometry.
  182. *
  183. * @return the vertex count of this branch.
  184. */
  185. @Override
  186. public int getVertexCount() {
  187. int count = 0;
  188. if(children != null) {
  189. for(int i = 0; i < children.size(); i++) {
  190. count += children.get(i).getVertexCount();
  191. }
  192. }
  193. return count;
  194. }
  195. /**
  196. * <code>attachChild</code> attaches a child to this node. This node
  197. * becomes the child's parent. The current number of children maintained is
  198. * returned.
  199. * <br>
  200. * If the child already had a parent it is detached from that former parent.
  201. *
  202. * @param child
  203. * the child to attach to this node.
  204. * @return the number of children maintained by this node.
  205. * @throws NullPointerException If child is null.
  206. */
  207. public int attachChild(Spatial child) {
  208. if (child == null)
  209. throw new NullPointerException();
  210. if (child.getParent() != this && child != this) {
  211. if (child.getParent() != null) {
  212. child.getParent().detachChild(child);
  213. }
  214. child.setParent(this);
  215. children.add(child);
  216. // XXX: Not entirely correct? Forces bound update up the
  217. // tree stemming from the attached child. Also forces
  218. // transform update down the tree-
  219. child.setTransformRefresh();
  220. child.setLightListRefresh();
  221. if (logger.isLoggable(Level.INFO)) {
  222. logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
  223. new Object[]{child.getName(), getName()});
  224. }
  225. }
  226. return children.size();
  227. }
  228. /**
  229. *
  230. * <code>attachChildAt</code> attaches a child to this node at an index. This node
  231. * becomes the child's parent. The current number of children maintained is
  232. * returned.
  233. * <br>
  234. * If the child already had a parent it is detached from that former parent.
  235. *
  236. * @param child
  237. * the child to attach to this node.
  238. * @return the number of children maintained by this node.
  239. * @throws NullPointerException if child is null.
  240. */
  241. public int attachChildAt(Spatial child, int index) {
  242. if (child == null)
  243. throw new NullPointerException();
  244. if (child.getParent() != this && child != this) {
  245. if (child.getParent() != null) {
  246. child.getParent().detachChild(child);
  247. }
  248. child.setParent(this);
  249. children.add(index, child);
  250. child.setTransformRefresh();
  251. child.setLightListRefresh();
  252. if (logger.isLoggable(Level.INFO)) {
  253. logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
  254. new Object[]{child.getName(), getName()});
  255. }
  256. }
  257. return children.size();
  258. }
  259. /**
  260. * <code>detachChild</code> removes a given child from the node's list.
  261. * This child will no longer be maintained.
  262. *
  263. * @param child
  264. * the child to remove.
  265. * @return the index the child was at. -1 if the child was not in the list.
  266. */
  267. public int detachChild(Spatial child) {
  268. if (child == null)
  269. throw new NullPointerException();
  270. if (child.getParent() == this) {
  271. int index = children.indexOf(child);
  272. if (index != -1) {
  273. detachChildAt(index);
  274. }
  275. return index;
  276. }
  277. return -1;
  278. }
  279. /**
  280. * <code>detachChild</code> removes a given child from the node's list.
  281. * This child will no longe be maintained. Only the first child with a
  282. * matching name is removed.
  283. *
  284. * @param childName
  285. * the child to remove.
  286. * @return the index the child was at. -1 if the child was not in the list.
  287. */
  288. public int detachChildNamed(String childName) {
  289. if (childName == null)
  290. throw new NullPointerException();
  291. for (int x = 0, max = children.size(); x < max; x++) {
  292. Spatial child = children.get(x);
  293. if (childName.equals(child.getName())) {
  294. detachChildAt( x );
  295. return x;
  296. }
  297. }
  298. return -1;
  299. }
  300. /**
  301. *
  302. * <code>detachChildAt</code> removes a child at a given index. That child
  303. * is returned for saving purposes.
  304. *
  305. * @param index
  306. * the index of the child to be removed.
  307. * @return the child at the supplied index.
  308. */
  309. public Spatial detachChildAt(int index) {
  310. Spatial child = children.remove(index);
  311. if ( child != null ) {
  312. child.setParent( null );
  313. logger.log(Level.INFO, "{0}: Child removed.", this.toString());
  314. // since a child with a bound was detached;
  315. // our own bound will probably change.
  316. setBoundRefresh();
  317. // our world transform no longer influences the child.
  318. // XXX: Not neccessary? Since child will have transform updated
  319. // when attached anyway.
  320. child.setTransformRefresh();
  321. // lights are also inherited from parent
  322. child.setLightListRefresh();
  323. }
  324. return child;
  325. }
  326. /**
  327. *
  328. * <code>detachAllChildren</code> removes all children attached to this
  329. * node.
  330. */
  331. public void detachAllChildren() {
  332. for ( int i = children.size() - 1; i >= 0; i-- ) {
  333. detachChildAt(i);
  334. }
  335. logger.log(Level.INFO, "{0}: All children removed.", this.toString());
  336. }
  337. /**
  338. * <code>getChildIndex</code> returns the index of the given spatial
  339. * in this node's list of children.
  340. * @param sp
  341. * The spatial to look up
  342. * @return
  343. * The index of the spatial in the node's children, or -1
  344. * if the spatial is not attached to this node
  345. */
  346. public int getChildIndex(Spatial sp) {
  347. return children.indexOf(sp);
  348. }
  349. /**
  350. * More efficient than e.g detaching and attaching as no updates are needed.
  351. *
  352. * @param index1 The index of the first child to swap
  353. * @param index2 The index of the second child to swap
  354. */
  355. public void swapChildren(int index1, int index2) {
  356. Spatial c2 = children.get(index2);
  357. Spatial c1 = children.remove(index1);
  358. children.add(index1, c2);
  359. children.remove(index2);
  360. children.add(index2, c1);
  361. }
  362. /**
  363. *
  364. * <code>getChild</code> returns a child at a given index.
  365. *
  366. * @param i
  367. * the index to retrieve the child from.
  368. * @return the child at a specified index.
  369. */
  370. public Spatial getChild(int i) {
  371. return children.get(i);
  372. }
  373. /**
  374. * <code>getChild</code> returns the first child found with exactly the
  375. * given name (case sensitive.)
  376. *
  377. * @param name
  378. * the name of the child to retrieve. If null, we'll return null.
  379. * @return the child if found, or null.
  380. */
  381. public Spatial getChild(String name) {
  382. if (name == null)
  383. return null;
  384. for (int x = 0, cSize = getQuantity(); x < cSize; x++) {
  385. Spatial child = children.get(x);
  386. if (name.equals(child.getName())) {
  387. return child;
  388. } else if(child instanceof Node) {
  389. Spatial out = ((Node)child).getChild(name);
  390. if(out != null) {
  391. return out;
  392. }
  393. }
  394. }
  395. return null;
  396. }
  397. /**
  398. * determines if the provided Spatial is contained in the children list of
  399. * this node.
  400. *
  401. * @param spat
  402. * the child object to look for.
  403. * @return true if the object is contained, false otherwise.
  404. */
  405. public boolean hasChild(Spatial spat) {
  406. if (children.contains(spat))
  407. return true;
  408. for (int i = 0, max = getQuantity(); i < max; i++) {
  409. Spatial child = children.get(i);
  410. if (child instanceof Node && ((Node) child).hasChild(spat))
  411. return true;
  412. }
  413. return false;
  414. }
  415. /**
  416. * Returns all children to this node. Note that modifying that given
  417. * list is not allowed.
  418. *
  419. * @return a list containing all children to this node
  420. */
  421. public List<Spatial> getChildren() {
  422. return children;
  423. }
  424. @Override
  425. public void setMaterial(Material mat){
  426. for (int i = 0; i < children.size(); i++){
  427. children.get(i).setMaterial(mat);
  428. }
  429. }
  430. @Override
  431. public void setLodLevel(int lod){
  432. super.setLodLevel(lod);
  433. for (int i = 0; i < children.size(); i++){
  434. children.get(i).setLodLevel(lod);
  435. }
  436. }
  437. public int collideWith(Collidable other, CollisionResults results){
  438. int total = 0;
  439. for (Spatial child : children){
  440. total += child.collideWith(other, results);
  441. }
  442. return total;
  443. }
  444. /**
  445. * Returns flat list of Spatials implementing the specified class AND
  446. * with name matching the specified pattern.
  447. * </P> <P>
  448. * Note that we are <i>matching</i> the pattern, therefore the pattern
  449. * must match the entire pattern (i.e. it behaves as if it is sandwiched
  450. * between "^" and "$").
  451. * You can set regex modes, like case insensitivity, by using the (?X)
  452. * or (?X:Y) constructs.
  453. * </P> <P>
  454. * By design, it is always safe to code loops like:<CODE><PRE>
  455. * for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
  456. * </PRE></CODE>
  457. * </P> <P>
  458. * "Descendants" does not include self, per the definition of the word.
  459. * To test for descendants AND self, you must do a
  460. * <code>node.matches(aClass, aRegex)</code> +
  461. * <code>node.descendantMatches(aClass, aRegex)</code>.
  462. * <P>
  463. *
  464. * @param spatialSubclass Subclass which matching Spatials must implement.
  465. * Null causes all Spatials to qualify.
  466. * @param nameRegex Regular expression to match Spatial name against.
  467. * Null causes all Names to qualify.
  468. * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
  469. *
  470. * @see java.util.regex.Pattern
  471. * @see Spatial#matches(java.lang.Class, java.lang.String)
  472. */
  473. @SuppressWarnings("unchecked")
  474. public <T extends Spatial>List<T> descendantMatches(
  475. Class<T> spatialSubclass, String nameRegex) {
  476. List<T> newList = new ArrayList<T>();
  477. if (getQuantity() < 1) return newList;
  478. for (Spatial child : getChildren()) {
  479. if (child.matches(spatialSubclass, nameRegex))
  480. newList.add((T)child);
  481. if (child instanceof Node)
  482. newList.addAll(((Node) child).descendantMatches(
  483. spatialSubclass, nameRegex));
  484. }
  485. return newList;
  486. }
  487. /**
  488. * Convenience wrapper.
  489. *
  490. * @see #descendantMatches(java.lang.Class, java.lang.String)
  491. */
  492. public <T extends Spatial>List<T> descendantMatches(
  493. Class<T> spatialSubclass) {
  494. return descendantMatches(spatialSubclass, null);
  495. }
  496. /**
  497. * Convenience wrapper.
  498. *
  499. * @see #descendantMatches(java.lang.Class, java.lang.String)
  500. */
  501. public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
  502. return descendantMatches(null, nameRegex);
  503. }
  504. @Override
  505. public Node clone(boolean cloneMaterials){
  506. Node nodeClone = (Node) super.clone(cloneMaterials);
  507. // nodeClone.children = new ArrayList<Spatial>();
  508. // for (Spatial child : children){
  509. // Spatial childClone = child.clone();
  510. // childClone.parent = nodeClone;
  511. // nodeClone.children.add(childClone);
  512. // }
  513. return nodeClone;
  514. }
  515. @Override
  516. public Spatial deepClone(){
  517. Node nodeClone = (Node) super.clone();
  518. nodeClone.children = new ArrayList<Spatial>();
  519. for (Spatial child : children){
  520. Spatial childClone = child.deepClone();
  521. childClone.parent = nodeClone;
  522. nodeClone.children.add(childClone);
  523. }
  524. return nodeClone;
  525. }
  526. @Override
  527. public void write(JmeExporter e) throws IOException {
  528. super.write(e);
  529. e.getCapsule(this).writeSavableArrayList(children, "children", null);
  530. }
  531. @Override
  532. public void read(JmeImporter e) throws IOException {
  533. super.read(e);
  534. children = e.getCapsule(this).readSavableArrayList("children", null);
  535. // go through children and set parent to this node
  536. if (children != null) {
  537. for (int x = 0, cSize = children.size(); x < cSize; x++) {
  538. Spatial child = children.get(x);
  539. child.parent = this;
  540. }
  541. }
  542. }
  543. @Override
  544. public void setModelBound(BoundingVolume modelBound) {
  545. if(children != null) {
  546. for(int i = 0, max = children.size(); i < max; i++) {
  547. children.get(i).setModelBound(modelBound != null ? modelBound.clone(null) : null);
  548. }
  549. }
  550. }
  551. @Override
  552. public void updateModelBound() {
  553. if(children != null) {
  554. for(int i = 0, max = children.size(); i < max; i++) {
  555. children.get(i).updateModelBound();
  556. }
  557. }
  558. }
  559. @Override
  560. public void depthFirstTraversal(SceneGraphVisitor visitor) {
  561. for(int i = 0, max = children.size(); i < max; i++) {
  562. children.get(i).depthFirstTraversal(visitor);
  563. }
  564. visitor.visit(this);
  565. }
  566. @Override
  567. protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
  568. queue.addAll(children);
  569. }
  570. }