android game development

63
Android Game Development Android Game Engine Basics

Upload: minowa

Post on 24-Feb-2016

94 views

Category:

Documents


0 download

DESCRIPTION

Android Game Development. Android Game Engine Basics. Base project. Download our base project and open in NetBeans cg.iit.bme.hu/ gamedev /KIC/11_AndroidDevelopment/11_02_Android_EngineBasics_Base.zip Similar to our OpenGL example Init is called by resize - PowerPoint PPT Presentation

TRANSCRIPT

Billboard, Particle System

Android Game Development Android Game Engine Basics1Base projectDownload our base project and open in NetBeanscg.iit.bme.hu/gamedev/KIC/11_AndroidDevelopment/11_02_Android_EngineBasics_Base.zipSimilar to our OpenGL exampleInit is called by resizeChange the android sdk location pathPer-user properties (local.properties)sdk.dir=change path hereStart an emulatorBuildRunIn the next few lessons we will create a simple game engine and use it to create a platform game for Android. The engine will use OpenGL ES 1.1 for rendering and Box2D for physics. It will contain the basic functionalities an engine should have: resources, resource managers, scene graph etc.

Our base project is similar to our OpenGL example. The difference is that the init function is called when the window is resized instead of when the surface is created. This suites better to the Android platform, as if the window is resized (the device is rotated for e.g.) our application will loose the opengl context, so we should reinit our resources.

To compile the downloaded project in Netbeans we should set our android sdk location, which is located in local.properties. We can start an emulator and run the basic project.2MainEngineOne instant is created in MainRendererAll events are forwarded to this classWindow inited, window resized, draw a new frame, onTouch eventNew frame: update()Calculates elapsed timeChanges game state (rotation angles)Renders scene (quad)If we examine the code we will see a class named MainEngine. This will be our main application class, only one instance will be created, and all event will be forwarded to this class. The event handler for a new frame is the update() function. This method calculates elapsed time, gamestate changes will be placed here (now it contains setting of rotation angles) and finally renders the scene to the screen.3UEngine.EngineCreate new package:UEngine.EngineReusable classes will be created hereAll reusable classes will be part of our Engine and placed in a separate package: UEngine.Engine.The classes used only by our concrete game will be placed in Uengine.PlatformGame4Basic classes - GlobalsUEngine.Engine.GlobalsWe can create a class that will hold global valuesStatic variablesE.g.:public class Globals { public static float importantThing = 2;}Easy accessAlways use global variables carefully!Now lets take a look at some classes.We can create a helper class named Globals, which contains only static public attributes. If some attributes need to be accessed by several objects it will be placed here. This provides easier access.

We should note that global variables (static variables are like global variables) can be dangerous, special attention is needed for them (multi threaded systems, unit testing )5Basic classesCopy all class source files from tmp_baseclasses to src\gamedev\android\UEngine\Engine\Basic math util classesVectorQuaternionMatrixWe need some math related objects like vectors and matrices. The source code for them is located in tmp_baseclasses folder, just copy them to the Engine package. We wont walk through each of them in detail, only describe they main functionalities.6Basic Classes Vectors I.Vector classpublic class Vector{ public float[] d;Undefined dimensionBasic operationsaddition/substractionMultiplication/division (element by element)dot productlengthNormalizationadd or addSelf?Self will modify the object itself will (not create new instance)Use this whenever possible (efficient)The vector class represents a high dimensional vector. The dimension can be arbitrary, the values are stored in a float array. Basic operations are implemented for vectors like element wise addition, substraction, multiplication and division. We have normalization, length calculation and dot product operator too. For each basic operators we have two possibilities: we can put the result vector into a new Vector instance or overwrite the actual object instance with the result (methods having the Self postfix). As dynamic memory allocation can decrease performance, we should use the self modifying version whenever it is possible.7Basic classes- Vectors II.Vector3Specialization of Vector to 3 dimensionDefine cross operationDefine often used vectors (unit, zero, x dir, y dir etc.)Vector4Specialization of Vector to 4 dimensionUsed to represent vectors of the projective space (homogenous coordinates)or RGBA color valuesDefine often used vectors (unit, zero, x dir, y dir etc.)

We have two specializations for the generic vector class: Vector3 and Vector4, describing a three and a four dimensional vector respectively. We use Vector3 to represent points in the three dimensional virtual world (Euclidian coordinates), and use Vector4 to represent homogenous coordinates, or RGBA color values. We also have predefined instances for the most often used vector values like zero, unit, x direction etc.8Basic Classes - Quaternion4 dimensional Vector representing a quaternionOrientation described with a rotation axis and angleDefine initialization methods from jaw-pitch-rollDefine Vector3 transformationDefine concatenation of quaternions (mul/mulSelf)

The Quaternion class, which is a 4 dimensional Vector specialization, represents orientation described with a rotation axis and the rotation angle around that axis. We can initialize a quaternion with jaw-pitch-roll angles too. We have methods to transform a 3D point with the quaternion, and to concatenate two quaternions to get their summed transformation.9Basic Classes - Matrix4x4 matrix classUsed to describe transformationUsually describes a rigid transformation in our codeTranslationRotationDefine initialization for translation and rotationDefine concatenation (mul)Define inverse if the matrix is a rigid transformAs our engine is based on 3D space we use 4x4 transformation matrices, which can describe translation, rotation and projection too.10ResourcesCreate package: UEngine.Engine.ResourcesHere we place:TexturesMaterialsMeshesThey are usually loaded from a fileThey have a unique IDShould be loaded only oneCan be shared by objectsThe first classes we will create are about resources, and we will place them in the Resources package under Engine. Resources are the data needed for rendering: textures, material descriptions, mesh data. These resources should be unique, thus will have a unique identifier (we will use Strings for this to be human readable), they should be loaded only once and should be stored only once in memory, they can be shared by several objects. Resources usually have large amount of data loaded from files.11AssetManagerGlobals.java:import android.content.res.AssetManager;

public class Globals { public static AssetManager assetManager = null;}MainActivity, onCreate():gamedev.android.UEngine.Engine.Globals.assetManager = getAssets();As resources often need file. In an android environment we can store our files in two locations: the res folder (stands for resources), and in the assets folder. Resources are restricted to certain file types (image files, audio files for e.g.), they have a built in management system to provide different quality versions of one resource for different Android platforms (for e.g. a higher resolution display can use higher resolution images). Resources are references by static variables in code, these variables are placed in the autogenerated R class.Assets dont have this management mechanism (but implementing this not so hard), but their type is not restricted. They are referenced by their file path. These files should be placed in the asset folder (or in its subfolder, their access path is relative from the asset folder).We will use assets in our engine. To load an asset we should have a reference to the AssetManager of our application. As this object will be used by several classes we place it in our Globals class as a static attribute.12ResourceCreate new class: Resourcepackage gamedev.android.UEngine.Engine.Resources;

public class Resource{ String name; public Resource(String name){ this.name = name; } public String getName(){ return this.name; } public void load(){ } public void destroy(){ }}The Resource is the base class of all our resources. Resources have a unique name (in practice they have a unique name within one resource type, a texture and a material can have the same name). They will be loaded t memory with the load function, and all memory will be released with the destroy function.13Resources - TextureCreate new class: Texturepublic class Texture extends Resource{ protected int[] id = new int[1]; protected String filename = null; protected float repeatU = 1; protected float repeatV = 1;

public Texture(String name) { super(name); id[0] = -1; } public int getID() { return this.id[0]; } public void setFileName(String filename) { this.filename = filename; } public void setRepeatU(float v){repeatU = v;} public void setRepeatV(float v){repeatV = v;}OpenGL idHow many times we should repeate the texture on each direction (coordinate scaling)Our first resource is the texture. It should store the opengl id of the texture. The textures can be used with a scaled texture matrix to create a dense texture tiling.14Resources Texture II.public void load(){ super.load(); GLES10.glGenTextures(1, this.id, 0); Bitmap bitmap; try{ bitmap = BitmapFactory.decodeStream(Globals.assetManager.open(this.filename == null ? this.name : this.filename)); } catch (IOException e){ e.printStackTrace(); return; } GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, this.id[0]); GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST); GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_NEAREST); GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); bitmap.recycle(); }Creation of the resource consists of creating the opengl texture object, loading the texture data from the image file (see how AssetManager can be used to do this), and finally upload the texture data to the opengl texture object. We can see familiar opengl calls (if we are familiar with opengl).15Resources Texture III. public void destroy() { super.destroy(); GLES10.glDeleteTextures(1, this.id, 0); } public void enable() { GLES10.glEnable(GLES10.GL_TEXTURE_2D); GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, this.id[0]); GLES10.glMatrixMode(GLES10.GL_TEXTURE); GLES10.glLoadIdentity(); if(repeatU * repeatV != 1){ GLES10.glScalef(repeatU, repeatV, 1.0f); } GLES10.glMatrixMode(GLES10.GL_MODELVIEW); } public static void disable() { GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, 0); GLES10.glDisable(GLES10.GL_TEXTURE_2D); }}Textures will have an enable method, which is used during the rendering of the scene. To enable a texture before drawing an object, we should call enable (this will make the texture object as the active texture object). If we want to turn off texturing we can call the static disble method.16Resources Material I.public class Material extends Resource{ public static int ALPHA_BLEND_MODE_NONE = 0; public static int ALPHA_BLEND_MODE_BLEND = 1; public static int ALPHA_BLEND_MODE_ADD = 2; protected Texture texture = null; protected boolean lighting = false; protected int alphaBlendMode = ALPHA_BLEND_MODE_NONE;

public Material(String name) { super(name); }Our next resource is material. In its current simple state it can contain a reference to a texture and can turn alpha blending on or off (it has an attribute to turn lighting on or off but it is not implemented yet). We can use two most commonly used alpha blending modes: additive blending (for emissive materials like fire effects) and alpha blending (for semi transparent, light absorbing materials like smoke, or the display masked images).17Resources Material II.public void setTexture(Texture texture) { this.texture = texture; } public Texture getTexture() { return this.texture; } public void setLighting(boolean on) { this.lighting = on; } public boolean getLighting() { return this.lighting; } public int getAlphaBlendMode() { return alphaBlendMode; } public void setAlhpaBlendMode(int mode){ alphaBlendMode = mode; }Getters/setters.18Resources Material III.public void load() { } public void destroy(){ } public void set(){ if (this.texture != null) this.texture.enable(); else Texture.disable(); if(alphaBlendMode != ALPHA_BLEND_MODE_NONE){ GLES10.glEnable(GLES10.GL_BLEND); if(alphaBlendMode == ALPHA_BLEND_MODE_ADD) GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE); else GLES10.glBlendFunc(GLES10.GL_SRC_ALPHA, GLES10.GL_ONE_MINUS_SRC_ALPHA); } else{ GLES10.glDisable(GLES10.GL_BLEND); } } }To make the material properties active we should call the set() function. This will automatically enable or disable texturing.19Resources MeshCreate class: Meshpublic class Mesh extends Resource{ protected FloatBuffer VB = null; protected FloatBuffer TB = null; protected FloatBuffer NB = null; protected int vertexcount = 0;

public Mesh(String name){ super(name); } public void load(){ super.load(); } public void destroy(){ super.destroy(); this.VB = null; this.TB = null; this.NB = null; }The final main resource type is Mesh. This represents a triangle geometry with normal vectors and texture coordinates. The base Mesh class only defines the vertex buffers and the rendering code, but the actual upload of these buffers is the responsibility of derived classes.20Resources Mesh II.public void render(){ if (this.VB == null) { return; } GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, this.VB); if (this.TB != null) { GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, this.TB); } if (this.NB != null) { GLES10.glEnableClientState(GLES10.GL_NORMAL_ARRAY); GLES10.glNormalPointer(GLES10.GL_FLOAT, 0, this.NB); } GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, this.vertexcount); GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); GLES10.glDisableClientState(GLES10.GL_NORMAL_ARRAY); }

The rendering code.21Resources Quad I.Create new class: Quadpublic class Quad extends Mesh{ public Quad(String name){ super(name); this.vertexcount = 6; }

public void load(){ float[] VCoords = { -1.0F, -1.0F, 0.0F, 1.0F, -1.0F, 0.0F, 1.0F, 1.0F, 0.0F, //triangle 1 -1.0F, -1.0F, 0.0F, 1.0F, 1.0F, 0.0F, -1.0F, 1.0F, 0.0F }; //triangle 2

float[] TCoords = { 0.0F, 1.0F, 1.0F, 1.0F, 1.0F, 0.0F, //triangle 1 0.0F, 1.0F, 1.0F, 0.0F, 0.0F, 0.0F }; //triangle 2

float[] NCoords = { 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, //triangle 1 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F }; //triangle 2

The simplest and most widely used mesh is a quad. In our game we will use our 3D game engine to implement a 2D game. All objects of this 2D world will be represented with textures quads. This class fills the vertex buffer of a quad.22Resources Quad II. ByteBuffer vbb = ByteBuffer.allocateDirect(VCoords.length * 4); vbb.order(ByteOrder.nativeOrder()); this.VB = vbb.asFloatBuffer(); this.VB.put(VCoords); this.VB.position(0);

ByteBuffer nbb = ByteBuffer.allocateDirect(NCoords.length * 4); nbb.order(ByteOrder.nativeOrder()); this.NB = nbb.asFloatBuffer(); this.NB.put(NCoords); this.NB.position(0);

ByteBuffer tbb = ByteBuffer.allocateDirect(TCoords.length * 4); tbb.order(ByteOrder.nativeOrder()); this.TB = tbb.asFloatBuffer(); this.TB.put(TCoords); this.TB.position(0); } }Try our classes I.MainEngine:Remove protected FloatBuffer VB = null;Add: Material m; Texture t; Mesh mesh;Init: public void init() { GLES10.glClearColor(0.5F, 0.5F, 1.0F, 1.0F); t = new Texture("TestTexture"); t.setFileName("brick.jpg"); t.load(); m = new Material("TestMaterial"); m.setTexture(t); m.load(); mesh = new Quad("TestQuad"); mesh.load(); }Now we can try our resource classes. We dont need vertex buffer creation code in our MainEngine class anymore, so remove FloatBuffer VB from the attributes. But we should add a reference to a texture, a material and to a mesh. The initialization function should create a texture, a material, pass the texture to the material, it should create a quad. All resources should be loaded too!24Try our classes II.MainEngine update:Remove:GLES10.glColor4f(1, 0, 0, 1);

GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, this.VB); GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 6); GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); Add: GLES10.glClearColor(0.5F, 0.5F, 1.0F, 1.0F); m.set(); mesh.render(); }In the update function (which is responsible to render the scene), we also dont need the explicit draw calls but we should activate our material, and ask our mesh to render itself.25Compile and Run

Resource ManagersResources should be loaded only onceManagers track them, place them in a mapEach resource type will have its managerAll resources should be registered to its resource managerAll resources should be aquired through the resource managerWe mentioned before, that all resources should be loaded only once, so we need some objects that manages all the added resources. These will be the ResourceManagers.27ResourceManager I. public abstract class ResourceManager{ protected HashMap resources = new HashMap();

public final R get(String name) { if (this.resources.containsKey(name)) { return this.resources.get(name); } return load(name); } protected abstract R loadImpl(String paramString); protected final R load(String name) { if (this.resources.containsKey(name)) { return this.resources.get(name); } R r = loadImpl(name); if (r != null) { this.resources.put(name, r); } return r; } Create class ResourceManager in Resources packageResourceManagers will store their resources in a map with a String key representing the resource name. This will ensure that a resource is stored only once. Resources should be aquired through the resource managers with their get function.

(The load function will be used to ask the manager to load the resource, this will be implemented much later, but this is the preferred way to use resources instead of loading them and registering them with the resource manager.)28ResourceManager II. public final void destroy(String name){ if (this.resources.containsKey(name)) ((Resource)this.resources.get(name)).destroy(); }

public final boolean add(R r){ if (!this.resources.containsKey(r.getName())) { this.resources.put(r.getName(), r); } return false; } public void clear(){ for(Resource r : resources.values()){ r.destroy(); } resources.clear(); }}

Resources should also be registered with the add function.29TextureManagerpublic class TextureManager extends ResourceManager{ private static TextureManager instance = new null; public static TextureManager getSingleton(){ if(instance == null) instance = new TextureManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Texture loadImpl(String name){ return null; }}The TextureManager stores texture resources. This is a singleton class, that can be accessed easily from anywhere.30MaterialManagerpublic class MaterialManager extends ResourceManager{ private static MaterialManager instance = null; public static MaterialManager getSingleton(){ if(instance == null) instance = new MaterialManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Material loadImpl(String name){ return null; }}The MaterialManager stores material resources. This is a singleton class, that can be accessed easily from anywhere.31MeshManagerpublic class MeshManager extends ResourceManager{ private static MeshManager instance = null; private MeshManager(){ Quad quad = new Quad("UnitQuad"); quad.load(); add(quad); } public static MeshManager getSingleton() { if(instance == null) instance = new MeshManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Mesh loadImpl(String name) { return null; }}The MeshManager stores mesh resources. This is a singleton class, that can be accessed easily from anywhere. It creates and registers a unit sized quad mesh at startup with the name: UnitQuad. This way when implementing our 2D game, we dont even need to define any mesh resources, just use this built in one.32Try the managersMainEngine: remove t, m, mesh attributesInit:Material m;Texture t;Mesh mesh;t = new Texture("TestTexture");t.setFileName("brick.jpg");t.load();TextureManager.getSingleton().add(t);m = new Material("TestMaterial");m.setTexture(t);m.load();MaterialManager.getSingleton().add(m); update:MaterialManager.getSingleton().get("TestMaterial").set();MeshManager.getSingleton().get("UnitQuad").render();Now we can try our manager classes. We dont need a reference to our resources anymore as we can get them from the managers by their names. After creating and loading a resource, we should register it with the proper resource manager. If we did this, we can get a reference to them later, we only need to know their names.33Try the managersMainEngine add new method:public void destroy(){ MaterialManager.clearSingleton(); TextureManager.clearSingleton(); MeshManager.clearSingleton(); }MainSurfaceView add method:public void onPause() {super.onPause();this.mRenderer.engine.destroy();} MainActivity add method:public void onPause(){ super.onPause(); mGLView.onPause(); }Compile and runWe should also ensure, that our resources a re cleaned when our application quits. This is extremely important in case of an Android application. Remember that when we quit our application it is not terminated instantly only the next activity from the activity stack is chosen. If we start our application again, the class loader of our application still exists, so our static class attributes, thus all our singletons too. This can lead to some unpredictable problems. To robustly handle this we can clear our singletons: all managers tells to their resources to destroy themselves, clears their resource map, and even the singleton reference to null (clearSingleton()).This destroy mechanism is best called when an onPause event arrives as after this event it is not guaranteed that we will receive any event (recall the activity lifecycle).34Compile and Run

Scene GraphAll actors of the virtual world will be placed in a scene graphScene graph: tree hierarchy of transformationsEach scene graph node (scene node) can contain objects of the virtual world (movables)The concatenated transformation from the root node will be applied for the objects of scene nodeEach scene node and movable should have a unique nameThey should be registered to the scene graphOnly objects attached to scene nodes will be renderedCreate new package SceneGraph for the classes

Our next main topic the scene graph.36SceneManager I.public class SceneManager { public class NameCollisionException extends Exception { public NameCollisionException(String message) { super(message); } } private static SceneManager instance = null; protected int uniqueID = 0; protected int uniqueIDM = 0; protected HashMap nodes = new HashMap(); protected HashMap movables = new HashMap(); LinkedList zOrderedNodes = new LinkedList(); protected SceneNode rootNode;

private SceneManager() {} public static SceneManager getSingleton() { if(instance == null) instance = new SceneManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; }The scene graph is managed by the SceneManager class. This is also a singleton class for easy access. It stores the Scene nodes and all Movable objects attached to these nodes (see later). The tree hierarch can be walked with starting from the rootNode and recursively visiting all of its children. As the evaluation of the scene nodes depend only on their order of registering, sometimes we can encounter some visibility problems. This can easily happen with alpha blending enabled: we need to draw the nodes that are closer to the camera after the ones that are further to prevent unwanted depth test clipping. Our 2D game contains lots of alpha blended quads, so we should also implement an ordered evaluation of the scene nodes according to camera distance. zOrderedNodes contains the ordered list of sceneNodes.37SceneManager II.void registerNode(SceneNode node) throws Exception { if (node.getName() == null) { while (true) { this.uniqueID += 1; node.name = ("SceneNode" + this.uniqueID); try { registerNode(node); break; } catch (NameCollisionException localException) { } } }

if (this.nodes.containsKey(node.getName())) { throw new NameCollisionException("SceneNode with the same name already exsists: " + node.getName()); }

this.nodes.put(node.getName(), node); }All nodes should be registered to the scene graph. Nodes should have a unique name, which can be given by the user, or assigned automatically if no name is given.38SceneManager III.void registerMovable(Movable m) throws Exception { if (m.getName() == null) { while (true) { this.uniqueIDM += 1; m.name = ("UnknownMovable" + this.uniqueIDM); try { registerMovable(m); break; } catch (NameCollisionException localException) { } } }

if (this.movables.containsKey(m.getName())) { throw new NameCollisionException("Movable with the same name already exsists: " + m.getName()); }

this.movables.put(m.getName(), m); }SceneManager IV.public void zOrderedRender() { if (zOrderedNodes.size() == 0) { class SceneGraphVisitor { public void visit(SceneNode node) { for (SceneNode n : node.children) { insert(n); visit(n); } } protected void insert(SceneNode node) { int location = 0; for (SceneNode n : zOrderedNodes) { if (n.getPosition().z() > node.getPosition().z()) { break; } location++; } zOrderedNodes.add(location, node); } }; new SceneGraphVisitor().visit(rootNode); } for (SceneNode n : zOrderedNodes) { n.render(false); } }Z ordered rendering of the scene graph. It fills the z ordered list only once at first call, so the scene graph should be completely filled in initialization time before first render call. Note that this strategy is a restiction on dynamic scene graph handling, but in case of handheld devices dynamic allocation can have large costs, so initializing the whole scene at startup is usually a good strategy.40SceneManager V. public void update(float t, float dt) { this.rootNode.update(t, dt); } public SceneNode getNode(String name) { return (SceneNode) this.nodes.get(name); } public Movable getMovable(String name) { return (Movable) this.movables.get(name); } public SceneNode getRootNode() { return this.rootNode; } public void render() { this.rootNode.render(true); } public void clear(){ nodes.clear(); movables.clear(); zOrderedNodes.clear(); rootNode = null; }}Scene Graph SceneNode I.public class SceneNode{ protected String name = null;

protected SceneNode parent = null; protected LinkedList children = new LinkedList(); protected LinkedList movables = new LinkedList();

protected Vector3 position = new Vector3(); protected Quaternion orientation = new Quaternion();

protected Matrix4 transform = new Matrix4(); protected Matrix4 derivedTransform = this.transform; protected Matrix4 transformInv = new Matrix4(); protected Matrix4 derivedTransformInv = this.transformInv; protected boolean inverseDirty = false;The basic element of the scene graph is the scene node. As it is a graph node, it has one parent and several children. Several movable objects can also be attached to it. It has an orientation and translation. It also stores its transform as a Matrix, and stores the concatenated transformation matrix all the way up to the root node. The inverse of these matrices is also computed.42Scene Graph SceneNode II.public SceneNode(String name) throws Exception { this.name = name; if (SceneManager.getSingleton() != null) SceneManager.getSingleton().registerNode(this); this.derivedTransform = this.transform; this.derivedTransformInv = this.transformInv; } public SceneNode() { if (SceneManager.getSingleton() != null) try { SceneManager.getSingleton().registerNode(this); } catch (Exception localException) { } this.derivedTransform = this.transform; this.derivedTransformInv = this.transformInv; }Scene nodes are automatically registered to the scene manager during their construction.43Scene Graph SceneNode III.public SceneNode(String name, SceneNode parent) throws Exception { this.name = name; if (SceneManager.getSingleton() != null) SceneManager.getSingleton().registerNode(this); if (parent != null) { this.parent = parent; this.derivedTransform = new Matrix4(parent.derivedTransform); this.derivedTransformInv = new Matrix4(parent.derivedTransformInv); this.inverseDirty = true; } } public SceneNode(SceneNode parent) { if (SceneManager.getSingleton() != null) { try { SceneManager.getSingleton().registerNode(this); } catch (Exception localException){} } if (parent != null) { this.parent = parent; this.derivedTransform = new Matrix4(parent.derivedTransform); this.derivedTransformInv = new Matrix4(parent.derivedTransformInv); this.inverseDirty = true; } }Scene Graph SceneNode IV.private void parentChanged() { refreshDerivedTransform(); } private void refreshDerivedTransform() { if (this.parent != null) { this.derivedTransform = this.transform.mul(this.parent.derivedTransform); } this.inverseDirty = true; for (SceneNode n : this.children) n.refreshDerivedTransform(); }

private void refreshInvTransform() { this.transformInv = this.transform.inverseAffine(); if (this.parent != null) { this.derivedTransformInv = this.derivedTransform.inverseAffine(); } this.inverseDirty = false; }Scene Graph SceneNode V.public SceneNode getParent(){ return parent; } public Matrix4 getInverseTransform() { if (this.inverseDirty) refreshInvTransform(); return this.transformInv; } public Matrix4 getInverseDerivedTransform() { if (this.inverseDirty) refreshInvTransform(); return this.derivedTransformInv; } public String getName() { return this.name; } public Vector3 getPosition() { return this.position; } public void setPosition(Vector3 p) { this.position = p; this.transform.setTranslate(p); refreshDerivedTransform(); this.inverseDirty = true; }Getter/setter46Scene Graph SceneNode VI.public void setPosition(float x, float y, float z) { this.position.set(x, y, z); this.transform.setTranslate(this.position); refreshDerivedTransform(); this.inverseDirty = true; } public Quaternion getOrientation() { return this.orientation; } public void setOrientation(Quaternion q) { this.orientation = q; this.transform.setRotate(q); refreshDerivedTransform(); this.inverseDirty = true; } public void setOrientation(float yaw, float pitch, float roll) { this.orientation.set(yaw, pitch, roll); this.transform.setRotate(this.orientation); refreshDerivedTransform(); this.inverseDirty = true; }Transformation setter functions.47Scene Graph SceneNode VII.public SceneNode getChild(int index) { if (this.children.size()