/******************************************************************************* * Copyright (c) 2007 VTT Technical Research Centre of Finland and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.proconf.g3d.scenegraph; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.vecmath.AxisAngle4d; import javax.vecmath.Quat4d; import org.simantics.g2d.stubs.anim.Interpolator; import org.simantics.animation.curve.SlerpCurve; import org.simantics.animation.curve.TCBCurve; import org.simantics.proconf.g3d.Resources; import org.simantics.proconf.g3d.animation.Animatable; import org.simantics.proconf.g3d.animation.Animation; import org.simantics.proconf.g3d.animation.ChanneledColorInterpolator; import org.simantics.proconf.g3d.animation.ChanneledPositionInterpolator; import org.simantics.proconf.g3d.animation.ConstantInterpolator; import org.simantics.proconf.g3d.animation.ScalarInterpolator; import org.simantics.proconf.g3d.animation.SlerpInterpolator; import org.simantics.proconf.g3d.animation.TCBInterpolator; import org.simantics.proconf.g3d.base.AppearanceTools; import org.simantics.proconf.g3d.base.G3DTools; import org.simantics.proconf.g3d.base.GeometryProvider; import org.simantics.proconf.g3d.base.GeometryProviderRegistry; import org.simantics.proconf.g3d.base.ThreeDimensionalEditorBase; import org.simantics.proconf.g3d.stubs.Appearance; import org.simantics.proconf.g3d.stubs.Color; import org.simantics.proconf.g3d.stubs.G3DModel; import org.simantics.proconf.g3d.stubs.G3DNode; import org.simantics.proconf.g3d.stubs.Orientation; import org.simantics.utils.ErrorLogger; import com.jme.bounding.BoundingBox; import com.jme.bounding.CollisionTreeManager; import com.jme.intersection.PickResults; import com.jme.math.Ray; import com.jme.renderer.ColorRGBA; import com.jme.renderer.Renderer; import com.jme.scene.Geometry; import com.jme.scene.Node; import com.jme.scene.SharedMesh; import com.jme.scene.TriMesh; import com.jme.scene.state.AlphaState; import com.jme.scene.state.MaterialState; import com.jme.scene.state.RenderState; import com.jme.scene.state.WireframeState; import com.jme.scene.state.ZBufferState; import org.simantics.db.Builtins; import org.simantics.db.ContextGraph; import org.simantics.db.Graph; import org.simantics.db.Resource; import org.simantics.layer0.utils.IEntity; public class ShapeNode extends AbstractGraphicsNode implements Animatable, IGeometryNode{ public static final int NORMAL = 0; public static final int TRANSPARENT = 1; public static final int SELECTED_EDGE = 2; public static final int HIGHLIGHTED_EDGE = 3; private boolean highlighted = false; protected Geometry mesh = null; protected Geometry lines = null; protected Geometry[] geometry = null; private boolean visible[] = new boolean[4]; private Node body; private Node transparent; private Node edge; private MaterialState selectedEdgeState; private MaterialState highlightedEdgeState; private Collection renderStates; private boolean isTransparent; public ShapeNode(ThreeDimensionalEditorBase editor,IGraphicsNode parent, Graph graph, Resource shapeResource) { super(editor,parent, graph, shapeResource); for (int i = 0; i < visible.length; i++) visible[i] = false; body = new Node(); body.setName(id); transparent = new Node() { private static final long serialVersionUID = 1L; @Override public void calculatePick(Ray ray, PickResults results) { } }; // transparent.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); edge = new Node(){ private static final long serialVersionUID = 1L; @Override public void calculatePick(Ray ray, PickResults results) { } }; transparent.setIsCollidable(false); edge.setIsCollidable(false); MaterialState ms = editor.getRenderingComponent().getDisplaySystem().getRenderer().createMaterialState(); ms.setDiffuse(new ColorRGBA(0.0f, 0.75f, 0.0f,0.3f)); ms.setEmissive(new ColorRGBA(0f, 0f, 0f,0.3f)); ms.setSpecular(new ColorRGBA(0.5f, 0.5f, 0.5f,0.3f)); ms.setAmbient(new ColorRGBA(0.0f, 0.75f, 0.0f,0.3f)); ms.setShininess(128.f); ms.setMaterialFace(MaterialState.MF_FRONT_AND_BACK); transparent.setRenderState(ms); AlphaState as = editor.getRenderingComponent().getDisplaySystem().getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA); as.setSrcFunction(AlphaState.DB_SRC_ALPHA); transparent.setRenderState(as); ms = editor.getRenderingComponent().getDisplaySystem().getRenderer().createMaterialState(); ms.setDiffuse(new ColorRGBA(1.f, 1.f, 1.f, 1.f)); ms.setEmissive(new ColorRGBA(1.f, 1.f, 1.f, 1.f)); ms.setSpecular(new ColorRGBA(1.f, 1.f, 1.f, 1.f)); ms.setAmbient(new ColorRGBA(1.f, 1.f, 1.f, 1.f)); ms.setShininess(128.f); selectedEdgeState = ms; ms = editor.getRenderingComponent().getDisplaySystem().getRenderer().createMaterialState(); ms.setDiffuse(new ColorRGBA(1.f, 0.f, 1.f, 1.f)); ms.setEmissive(new ColorRGBA(1.f, 0.f, 1.f, 1.f)); ms.setSpecular(new ColorRGBA(1.f, 0.f, 1.f, 1.f)); ms.setAmbient(new ColorRGBA(1.f, 0.f, 1.f, 1.f)); ms.setShininess(128.f); highlightedEdgeState = ms; } /** * This method is used to get implementation specific geometry. * Arrays first element is a mesh, second contains edges. * @return */ public Geometry[] getGeometry(Graph graph, boolean update) { G3DNode shape = getG3DNode(graph); final GeometryProvider provider = GeometryProviderRegistry.getGeometryProvider(shape); if (!update) { return provider.getGeometryFromResource(shape, false); } else { if (geometry == null) { geometry = provider.getGeometryFromResource(shape, false); } else { provider.reconstructGeometry(shape, false, geometry); } return geometry; } } /** * Updates shapes and it's ancestors geometry */ public void updateGeometry(Graph graph) { updateTransform(graph); // cleanAnimation(); //System.out.println("ShapeNode.updateGeometry() " + name); if (geometry == null) { Geometry g[] = getGeometry(graph,true); if (g != null) { mesh = g[0]; //TODO : uid mesh.setName(id); mesh.setModelBound(new BoundingBox()); if (g.length > 1) { lines = g[1]; } else { lines = null; } body.attachChild(mesh); transparent.detachAllChildren(); SharedMesh m = new SharedMesh("",(TriMesh)mesh); m.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); m.getBatch(0).setCastsShadows(false); transparent.attachChild(m); if (lines == null) { WireframeState ws = editor.getRenderingComponent().getDisplaySystem().getRenderer().createWireframeState(); edge.attachChild(new SharedMesh("",(TriMesh)mesh)); edge.setRenderState(ws); } else { ZBufferState zs = editor.getRenderingComponent().getDisplaySystem().getRenderer().createZBufferState(); zs.setFunction(ZBufferState.CF_ALWAYS); AlphaState as = editor.getRenderingComponent().getDisplaySystem().getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA); as.setSrcFunction(AlphaState.DB_SRC_ALPHA); lines.setRenderState(zs); lines.setRenderState(as); lines.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); edge.attachChild(lines); } } } if (geometry != null) { getGeometry(graph,true); G3DNode shape = getG3DNode(graph); if (renderStates == null) updateAppearance(shape); if (isVisible()) { getGroup().attachChild(body); } else { body.removeFromParent(); } if (isTransparentVisible()) { getGroup().attachChild(transparent); //setVisible(TRANSPARENT, true); } else { transparent.removeFromParent(); } if (isSelectedVisible() || isHighlightedVisible()) { getGroup().attachChild(edge); //setVisible(SELECTED_EDGE, true); } else { edge.removeFromParent(); //setVisible(SELECTED_EDGE,false); } mesh.updateModelBound(); CollisionTreeManager.getInstance().updateCollisionTree(mesh); //mesh.updateCollisionTree(); } } protected void updateAppearance(IEntity shape) { Collection appearanceResource; if ((appearanceResource = shape.getRelatedObjects(Resources.g3dResource.HasAppearance)) != null && appearanceResource.size() > 0) { renderStates = AppearanceTools.getAppearance(new Appearance(shape.getGraph(),appearanceResource.iterator().next().getResource()), editor.getRenderingComponent().getDisplaySystem().getRenderer()); } else { renderStates = getMaterial(); } isTransparent = false; for (RenderState s : renderStates) { if (s instanceof AlphaState) isTransparent = true; } setAppearance(); } protected void setAppearance() { if (mesh == null || renderStates == null) { return; } for (RenderState s : renderStates) mesh.setRenderState(s); if (isTransparent) mesh.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); else mesh.setRenderQueueMode(Renderer.QUEUE_OPAQUE); } public void setSelected(boolean selected) { if (this.selected == selected) return; this.selected = selected; if (selected) { setSelectedVisible(true); setTransparentVisible(true); } else { setSelectedVisible(false); setTransparentVisible(false); } } public boolean isSelected() { return selected; } public boolean isHighlighted() { return highlighted; } public void setHighlighted(boolean highlighted) { if (this.highlighted == highlighted) return; this.highlighted = highlighted; if (highlighted) { setHighlightedVisible(true); } else { setHighlightedVisible(false); } } public boolean isVisible(int shape) { return visible[shape]; } public void setVisible(int shape, boolean visible) { if (this.visible[shape] == visible) return; this.visible[shape] = visible; if (mesh == null) { return; } if (this.visible[NORMAL]){ getGroup().attachChild(body); } else { body.removeFromParent(); } if (this.visible[TRANSPARENT]) { getGroup().attachChild(transparent); } else { transparent.removeFromParent(); } if (this.visible[SELECTED_EDGE] || this.visible[HIGHLIGHTED_EDGE]) { if (this.visible[HIGHLIGHTED_EDGE]) edge.setRenderState(highlightedEdgeState); else edge.setRenderState(selectedEdgeState); getGroup().attachChild(edge); edge.updateRenderState(); } else { edge.removeFromParent(); } } public boolean isVisible() { return isVisible(NORMAL); } public void setVisible(boolean visible) { setVisible(NORMAL, visible); } public boolean isSelectedVisible() { return isVisible(SELECTED_EDGE); } public void setSelectedVisible(boolean visible) { setVisible(SELECTED_EDGE, visible); } public boolean isHighlightedVisible() { return isVisible(HIGHLIGHTED_EDGE); } public void setHighlightedVisible(boolean visible) { setVisible(HIGHLIGHTED_EDGE, visible); } public boolean isTransparentVisible() { return isVisible(TRANSPARENT); } public void setTransparentVisible(boolean visible) { setVisible(TRANSPARENT, visible); } public void setPickable(boolean pickable) { body.setIsCollidable(pickable); } public Collection getMaterial() { List states = new ArrayList(); MaterialState ms = editor.getRenderingComponent().getDisplaySystem().getRenderer().createMaterialState(); ms.setEmissive(new ColorRGBA(0.f,0.f,0.f,0.f)); ms.setSpecular(new ColorRGBA(1.f,1.f,1.f,1.f)); ms.setDiffuse(new ColorRGBA(0.75f,0.f,0.f,0.f)); ms.setAmbient(new ColorRGBA(0.75f,0.f,0.f,0.f)); ms.setEnabled(true); ms.setShininess(128.f); states.add(ms); return states; } private Animation animation; private static int preCalcSteps = 9; private Geometry[] preCalc = null; private int currentPreCalc = 0; public void animate(double delta,double frameTime) { if (animation != null) animation.interpolate(delta); if (preCalc != null) { int newPreCalc = (int)Math.round(delta*(preCalc.length-1)); if (currentPreCalc != newPreCalc) { preCalc[currentPreCalc].removeFromParent(); currentPreCalc = newPreCalc; body.attachChild(preCalc[currentPreCalc]); } } } private void cleanAnimation() { this.animation = null; if (preCalc != null) { for (Geometry g : preCalc) { if (g != null) { g.removeFromParent(); g.clearBuffers(); } } preCalc = null; } } /** * Sets shape's animation * TODO : multiple animations at the same time! (must check common animatable properties) * TODO : initial values (material, ..) (requires changes in the ontology) * TODO : messy code, refactor! * TODO : calculate number of required pre-calculated geometries * @param animation */ public boolean setAnimation(Graph g, Resource res) { ContextGraph graph; if (g instanceof ContextGraph) { graph = (ContextGraph)g; } else { graph = new ContextGraph(g); graph.setContext(shapeResource); } cleanAnimation(); if (res == null) { if (isVisible()) body.attachChild(mesh); return false; } org.simantics.g2d.stubs.anim.Animation animation = new org.simantics.g2d.stubs.anim.Animation(graph,res); G3DNode shape = getG3DNode(graph); G3DNode modelResource = G3DTools.getModelFromResource(graph,shape.getResource()); assert (modelResource != null); G3DModel model = new G3DModel(graph,modelResource.getResource()); Collection animations = model.getAnimation(); boolean found = false; for (org.simantics.g2d.stubs.anim.Animation a : animations) { if (a.getResource().equals(animation.getResource())) { found = true; break; } } if (!found) { ErrorLogger.getDefault().logWarning("Shape " + shape.getResource() + " cannot handle animation " + animation.getResource() + " because it isn't model's animation", null); return false; } Collection interpolators = animation.getInterpolator(); List handled = new ArrayList(); List precalculated = new ArrayList(); for (org.simantics.g2d.stubs.anim.Interpolator i : interpolators) { IEntity target = i.getTarget(); if (G3DTools.hasProperty(graph,shape.getResource(),target.getResource())) handled.add(i); else if (G3DTools.hasSubProperty(graph,shape.getResource(),target.getResource())) { precalculated.add(i); } } if (handled.size() == 0 && precalculated.size() == 0) { ErrorLogger.getDefault().logWarning("Shape " + shape.getResource() + " cannot handle animation " + animation.getResource() + " since it doesn't change any of shape's properties", null); return false; } this.animation = new Animation(); org.simantics.g2d.stubs.anim.Interpolator[] pos = new org.simantics.g2d.stubs.anim.Interpolator[3]; org.simantics.g2d.stubs.anim.Interpolator[] ambient = new org.simantics.g2d.stubs.anim.Interpolator[3]; org.simantics.g2d.stubs.anim.Interpolator[] diffuse = new org.simantics.g2d.stubs.anim.Interpolator[3]; org.simantics.g2d.stubs.anim.Interpolator[] specular = new org.simantics.g2d.stubs.anim.Interpolator[3]; org.simantics.g2d.stubs.anim.Interpolator[] emissive = new org.simantics.g2d.stubs.anim.Interpolator[3]; Builtins builtins = graph.getBuiltins(); for (org.simantics.g2d.stubs.anim.Interpolator i : handled) { IEntity target = i.getTarget(); //if (target.isInstanceOf(Resources.g3dResource.LocalOrientation)) { if (target.isInstanceOf(Resources.g3dResource.Orientation) && target.getRelatedObjects(Resources.g3dResource.LocalOrientationOf).size() == 1) { SlerpInterpolator si = new SlerpInterpolator((SlerpCurve)Resources.curveBuilder.loadInterpolator(i)); si.setTarget(transform); this.animation.addInterpolator(si); } else if (target.isInstanceOf(builtins.Double)) { Resource targetResource = target.getResource(); Collection p = target.getRelatedObjects(builtins.PropertyOf); if (p.size() == 1) { IEntity parent = p.iterator().next(); //if (parent.isInstanceOf(Resources.g3dResource.LocalPosition)) { if (parent.isInstanceOf(Resources.g3dResource.Position) && parent.getRelatedObjects(Resources.g3dResource.LocalPositionOf).size() == 1) { if (parent.getSingleRelatedObject(Resources.g3dResource.HasX).getResource().equals(targetResource)) { pos[0] = i; } else if (parent.getSingleRelatedObject(Resources.g3dResource.HasY).getResource().equals(targetResource)) { pos[1] = i; } else if (parent.getSingleRelatedObject(Resources.g3dResource.HasZ).getResource().equals(targetResource)) { pos[2] = i; } else { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target (Position ?)" + target.getResource(), null); } } else if (parent.isInstanceOf(Resources.g3dResource.Color)) { org.simantics.g2d.stubs.anim.Interpolator[] color = null; if (parent.isInstanceOf(Resources.g3dResource.Color) && parent.getRelatedObjects(Resources.g3dResource.AmbientColorOf).size() > 0) { color = ambient; } else if (parent.isInstanceOf(Resources.g3dResource.Color)&& parent.getRelatedObjects(Resources.g3dResource.DiffuseColorOf).size() > 0) { color = diffuse; } else if (parent.isInstanceOf(Resources.g3dResource.Color) && parent.getRelatedObjects(Resources.g3dResource.SpecularColorOf).size() > 0) { color = specular; } else if (parent.isInstanceOf(Resources.g3dResource.Color) && parent.getRelatedObjects(Resources.g3dResource.EmissiveColorOf).size() > 0) { color = emissive; } else { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target (Color)" + target.getResource() + " unknown color type", null); } if (color != null) { if (parent.getSingleRelatedObject(Resources.g3dResource.HasRed).getResource().equals(targetResource)) { color[0] = i; } else if (parent.getSingleRelatedObject(Resources.g3dResource.HasGreen).getResource().equals(targetResource)) { color[1] = i; } else if (parent.getSingleRelatedObject(Resources.g3dResource.HasBlue).getResource().equals(targetResource)) { color[2] = i; } else { ErrorLogger.getDefault().logWarning( "Cannot map animation interpolator " + i.getResource() + " to target (Color ?)" + target.getResource(), null); } } } else if (parent.isInstanceOf(Resources.g3dResource.Material)) { // TODO : transparency or shininess } else { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target" + target.getResource() + " adding it to precalculated interpolators", null); precalculated.add(i); } } else { if (p.size() == 0) { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target (Double)" + target.getResource() + " since it is not a part of a property", null); } else { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target (Double)" + target.getResource() + " since it acts as a property to more than one entity", null); } } } else { ErrorLogger.getDefault().logWarning("Cannot map animation interpolator " + i.getResource() + " to target" + target.getResource(), null); } } if (pos[0] != null || pos[1] != null || pos[2] != null) { ScalarInterpolator xIp; ScalarInterpolator yIp; ScalarInterpolator zIp; if (pos[0] != null) { xIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(pos[0])); } else { xIp = new ConstantInterpolator(shape.getLocalPosition().getX()[0]); } if (pos[1] != null) { yIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(pos[1])); } else { yIp = new ConstantInterpolator(shape.getLocalPosition().getY()[0]); } if (pos[2] != null) { zIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(pos[2])); } else { zIp = new ConstantInterpolator(shape.getLocalPosition().getZ()[0]); } ChanneledPositionInterpolator ip = new ChanneledPositionInterpolator(xIp,yIp,zIp); ip.setTarget(transform); this.animation.addInterpolator(ip); } addColorInterpolator(shape, ambient, ChanneledColorInterpolator.AMBIENT); addColorInterpolator(shape, diffuse, ChanneledColorInterpolator.DIFFUSE); addColorInterpolator(shape, emissive, ChanneledColorInterpolator.EMISSIVE); addColorInterpolator(shape, specular, ChanneledColorInterpolator.SPECULAR); if (precalculated.size() == 0) { preCalc = null; } else { preCalc = new Geometry[preCalcSteps+1]; for (int i = 0; i <= preCalcSteps; i++) { double delta = ((double)i / (double)preCalcSteps); // TODO : copy-paste from CSGAnimatorView // FIXME : does not update transformations (since ContextGraph does not support queries for context dependent values) for (Interpolator ip : precalculated) { if (ip.isInstanceOf(Resources.animationResource.ScalarInterpolator)) { // TODO : creating curve each time when time is set is slow. // Curve should be cached TCBCurve c = (TCBCurve)Resources.curveBuilder.loadInterpolator(ip); double out = c.evaluate(delta); //Double d = DoubleFactory.create(ip.getTarget()); //d.setValue(new double[]{out}); IEntity d = ip.getTarget(); d.toProperty().setDoubleArray(new double[]{out}); } else if (ip.isInstanceOf(Resources.animationResource.SlerpInterpolator)) { // TODO : creating curve each time when time is set is slow. // Curve should be cached SlerpCurve c = (SlerpCurve)Resources.curveBuilder.loadInterpolator(ip); Quat4d out = c.evaluate(delta); Orientation r = new Orientation(ip.getTarget()); AxisAngle4d aa = new AxisAngle4d(); aa.set(out); G3DTools.setOrientation(r, aa); } } preCalc[i] = getGeometry(graph,false)[0]; preCalc[i].setIsCollidable(false); AppearanceTools.copyMaterial(mesh, preCalc[i]); } // We'll have to remove original (non-animated) shape from the node mesh.removeFromParent(); body.attachChild(preCalc[0]); } return true; } private void addColorInterpolator(G3DNode shape, org.simantics.g2d.stubs.anim.Interpolator[] color, int type) { if (color[0] != null || color[1] != null || color[2] != null) { ScalarInterpolator xIp; ScalarInterpolator yIp; ScalarInterpolator zIp; Color col = null; Collection appearanceResource = shape.getRelatedObjects(Resources.g3dResource.HasAppearance); if (appearanceResource.size() == 0) { ErrorLogger.getDefault().logWarning("Cannot create interpolator for color because shape " + shape.getResource() + " has no appearance", null); } Appearance a = new Appearance(shape.getGraph(),appearanceResource.iterator().next().getResource()); switch (type) { case ChanneledColorInterpolator.AMBIENT: col = a.getMaterial().getAmbientColor(); break; case ChanneledColorInterpolator.DIFFUSE: col = a.getMaterial().getDiffuseColor(); break; case ChanneledColorInterpolator.EMISSIVE: col = a.getMaterial().getEmissiveColor(); break; case ChanneledColorInterpolator.SPECULAR: col = a.getMaterial().getSpecularColor(); break; default: ErrorLogger.defaultLogError("Unknown color type", null); return; } if (color[0] != null) { xIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(color[0]));//CurveUtils.loadCurve(color[0].getResource())); } else { xIp = new ConstantInterpolator(col.getRed()[0]); } if (color[1] != null) { yIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(color[1]));//CurveUtils.loadCurve(color[1].getResource())); } else { yIp = new ConstantInterpolator(col.getGreen()[0]); } if (color[1] != null) { zIp = new TCBInterpolator((TCBCurve)Resources.curveBuilder.loadInterpolator(color[2]));//CurveUtils.loadCurve(color[2].getResource())); } else { zIp = new ConstantInterpolator(col.getBlue()[0]); } ChanneledColorInterpolator ip = new ChanneledColorInterpolator(xIp,yIp,zIp); ip.setType(type); ip.setTarget(mesh.getRenderState(RenderState.RS_MATERIAL)); this.animation.addInterpolator(ip); } } public boolean setRandomAnimation(Graph graph) { return false; } public void dispose() { // mesh.clearBuffers(); // mesh.clearBatches(); // lines.clearBuffers(); // lines.clearBatches(); if (mesh != null) { mesh.removeFromParent(); mesh.dispose(); mesh = null; } if (lines != null) { lines.removeFromParent(); lines.dispose(); lines = null; } super.dispose(); } }