From 8a0168760f416dfc555433ee24216f84db07f83c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 10 May 2007 20:35:27 +0000 Subject: [PATCH] Much work to reconfigure shaders automatically based on the model's current JME state set. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@237 ed5b42cb-e716-0410-a449-f6a68f950b19 --- rsrc/media/jme/skin.vert | 39 +- .../com/threerings/jme/model/ModelMesh.java | 74 +++- .../com/threerings/jme/model/SkinMesh.java | 137 ++++++- .../com/threerings/jme/util/ShaderConfig.java | 338 ++++++++++++++++++ 4 files changed, 532 insertions(+), 56 deletions(-) create mode 100644 src/java/com/threerings/jme/util/ShaderConfig.java diff --git a/rsrc/media/jme/skin.vert b/rsrc/media/jme/skin.vert index dadf7957..79323b43 100644 --- a/rsrc/media/jme/skin.vert +++ b/rsrc/media/jme/skin.vert @@ -47,37 +47,30 @@ void main () // add up the vertex as transformed by each bone and scaled by each weight #if BONES_PER_VERTEX == 1 - vec4 skinVertex = boneTransforms[int(boneIndices)] * gl_Vertex * boneWeights; - vec4 skinNormal = boneTransforms[int(boneIndices)] * normal4 * boneWeights; + vec4 modelVertex = boneTransforms[int(boneIndices)] * gl_Vertex * boneWeights; + vec4 modelNormal = boneTransforms[int(boneIndices)] * normal4 * boneWeights; #else - vec4 skinVertex = boneTransforms[int(boneIndices[0])] * gl_Vertex * boneWeights[0]; - vec4 skinNormal = boneTransforms[int(boneIndices[0])] * normal4 * boneWeights[0]; + vec4 modelVertex = boneTransforms[int(boneIndices[0])] * gl_Vertex * boneWeights[0]; + vec4 modelNormal = boneTransforms[int(boneIndices[0])] * normal4 * boneWeights[0]; for (int ii = 1; ii < BONES_PER_VERTEX; ii++) { - skinVertex += boneTransforms[int(boneIndices[ii])] * gl_Vertex * boneWeights[ii]; - skinNormal += boneTransforms[int(boneIndices[ii])] * normal4 * boneWeights[ii]; + modelVertex += boneTransforms[int(boneIndices[ii])] * gl_Vertex * boneWeights[ii]; + modelNormal += boneTransforms[int(boneIndices[ii])] * normal4 * boneWeights[ii]; } #endif - // copy the texture coordinates from attribute to varying - gl_TexCoord[0] = gl_MultiTexCoord0; - gl_TexCoord[1] = gl_MultiTexCoord1; - // apply the standard transformation - gl_Position = gl_ModelViewProjectionMatrix * skinVertex; + gl_Position = gl_ModelViewProjectionMatrix * modelVertex; + + // transform the vertex and normal into eye space + vec3 eyeVertex = vec3(gl_ModelViewMatrix * modelVertex); + vec3 eyeNormal = normalize(vec3(gl_ModelViewMatrixInverseTranspose * modelNormal)); // eye space 'z' is the standard fog coordinate - gl_FogFragCoord = -dot(gl_ModelViewMatrixTranspose[2], skinVertex); + gl_FogFragCoord = -eyeVertex.z; - // transform the normal into eye space - vec3 eyeNormal = normalize(vec3(gl_ModelViewMatrixInverseTranspose * skinNormal)); + // set gl_FrontColor based on vertex, normal and light parameters + SET_FRONT_COLOR - // set gl_FrontColor based on normal and light parameters - gl_FrontColor.rgb = - gl_FrontLightProduct[0].ambient.rgb + - gl_FrontLightProduct[0].diffuse.rgb * - max(dot(eyeNormal, gl_LightSource[0].position.xyz), 0.0) + - gl_FrontLightProduct[1].ambient.rgb + - gl_FrontLightProduct[1].diffuse.rgb * - max(dot(eyeNormal, gl_LightSource[1].position.xyz), 0.0); - gl_FrontColor.a = gl_FrontMaterial.diffuse.a; + // set the varying texture coordinates + SET_TEX_COORDS } diff --git a/src/java/com/threerings/jme/model/ModelMesh.java b/src/java/com/threerings/jme/model/ModelMesh.java index 59d85561..ed7e8165 100644 --- a/src/java/com/threerings/jme/model/ModelMesh.java +++ b/src/java/com/threerings/jme/model/ModelMesh.java @@ -470,11 +470,19 @@ public class ModelMesh extends TriMesh protected void setupBatchList () { batchList = new ArrayList(1); - TriangleBatch batch = new ModelBatch(); + TriangleBatch batch = createModelBatch(); batch.setParentGeom(this); batchList.add(batch); } + /** + * Creates a batch for this mesh. + */ + protected ModelBatch createModelBatch () + { + return new ModelBatch(); + } + /** * Returns the number of textures this mesh uses (they must all share the * same texture coordinates). @@ -666,26 +674,55 @@ public class ModelMesh extends TriMesh @Override // documentation inherited public void draw (Renderer r) { - if (_translucent && isEnabled() && r.isProcessingQueue()) { - sortTriangles(r); + boolean drawing = (isEnabled() && r.isProcessingQueue()); + if (drawing) { + if (_translucent) { + sortTriangles(r); + } + preDraw(); } super.draw(r); - if (_overlays != null && isEnabled() && r.isProcessingQueue()) { - for (RenderState[] overlay : _overlays) { - for (RenderState rstate : overlay) { - int idx = rstate.getType(); - _ostates[idx] = states[idx]; - states[idx] = rstate; - } + if (_overlays != null && drawing) { + for (int ii = 0, nn = _overlays.size(); ii < nn; ii++) { + preDrawOverlay(ii); r.draw(this); - for (RenderState rstate : overlay) { - int idx = rstate.getType(); - states[idx] = _ostates[idx]; - } + postDrawOverlay(ii); } } } + /** + * Gives derived classes a chance to update states immediately before drawing. + */ + protected void preDraw () + { + } + + /** + * Updates the batch's states with those of the identified overlay. + */ + protected void preDrawOverlay (int oidx) + { + RenderState[] overlay = _overlays.get(oidx); + for (RenderState rstate : overlay) { + int idx = rstate.getType(); + _ostates[idx] = states[idx]; + states[idx] = rstate; + } + } + + /** + * Restores the batch's original states after drawing an overlay. + */ + protected void postDrawOverlay (int oidx) + { + RenderState[] overlay = _overlays.get(oidx); + for (RenderState rstate : overlay) { + int idx = rstate.getType(); + states[idx] = _ostates[idx]; + } + } + /** * Sorts the batch's triangles by their distance to the camera. */ @@ -762,6 +799,15 @@ public class ModelMesh extends TriMesh ibuf.put(_sibuf, 0, icount); } + /** + * Gives derived classes a chance to update the states immediately before drawing. + * + * @param oidx the index of the overlay being rendered, or -1 for the base layer. + */ + protected void processStates (int oidx) + { + } + /** Temporarily stores the original states. */ protected RenderState[] _ostates = new RenderState[RenderState.RS_MAX_STATE]; diff --git a/src/java/com/threerings/jme/model/SkinMesh.java b/src/java/com/threerings/jme/model/SkinMesh.java index 86b49aad..141a5abb 100644 --- a/src/java/com/threerings/jme/model/SkinMesh.java +++ b/src/java/com/threerings/jme/model/SkinMesh.java @@ -29,6 +29,7 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -62,6 +63,7 @@ import com.samskivert.util.ListUtil; import com.threerings.jme.Log; import com.threerings.jme.util.JmeUtil; import com.threerings.jme.util.ShaderCache; +import com.threerings.jme.util.ShaderConfig; /** * A triangle mesh that deforms according to a bone hierarchy. @@ -230,6 +232,36 @@ public class SkinMesh extends ModelMesh _bones = bones.toArray(new Bone[bones.size()]); } + @Override // documentation inherited + public void addOverlay (RenderState[] overlay) + { + // add a cloned state config (with same uniforms) for the overlay + super.addOverlay(overlay); + if (_sconfig == null) { + return; + } + if (_osconfigs == null) { + _osconfigs = new ArrayList(1); + } + SkinShaderConfig osconfig = (SkinShaderConfig)_sconfig.clone(); + osconfig.getState().uniforms = _sconfig.getState().uniforms; + _osconfigs.add(osconfig); + } + + @Override // documentation inherited + public void removeOverlay (RenderState[] overlay) + { + // remove the corresponding state config + int idx = (_overlays == null) ? -1 : _overlays.indexOf(overlay); + super.removeOverlay(overlay); + if (_osconfigs != null && idx >= 0) { + _osconfigs.remove(idx); + if (_osconfigs.isEmpty()) { + _osconfigs = null; + } + } + } + @Override // documentation inherited public void reconstruct ( FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors, @@ -264,13 +296,6 @@ public class SkinMesh extends ModelMesh if (sstate == null) { properties.addProperty("vertices"); properties.addProperty("normals"); - } else { - // for the shader, we must create a separate instance with different uniforms - GLSLShaderObjectsState msstate = - DisplaySystem.getDisplaySystem().getRenderer().createGLSLShaderObjectsState(); - msstate.setProgramID(sstate.getProgramID()); - msstate.attribs = sstate.attribs; - mstore.setRenderState(msstate); } properties.addProperty("displaylistid"); mstore._frames = _frames; @@ -290,6 +315,10 @@ public class SkinMesh extends ModelMesh mstore._onbuf = _onbuf; mstore._vbuf = (sstate == null) ? new float[_vbuf.length] : _vbuf; mstore._nbuf = (sstate == null) ? new float[_nbuf.length] : _nbuf; + if (_sconfig != null) { + mstore._sconfig = (SkinShaderConfig)_sconfig.clone(); + mstore.setRenderState(mstore._sconfig.getState()); + } return mstore; } @@ -379,18 +408,13 @@ public class SkinMesh extends ModelMesh if (bonesPerVertex > MAX_SHADER_BONES_PER_VERTEX) { return; } - GLSLShaderObjectsState sstate = (GLSLShaderObjectsState)getRenderState( - RenderState.RS_GLSL_SHADER_OBJECTS); - if (sstate == null) { - sstate = DisplaySystem.getDisplaySystem().getRenderer().createGLSLShaderObjectsState(); - setShaderAttributes(sstate, bonesPerVertex); - setRenderState(sstate); - } - if (!scache.configureState(sstate, "media/jme/skin.vert", null, - "MAX_BONE_COUNT " + MAX_SHADER_BONE_COUNT, "BONES_PER_VERTEX " + bonesPerVertex)) { - clearRenderState(RenderState.RS_GLSL_SHADER_OBJECTS); + _sconfig = new SkinShaderConfig(scache, bonesPerVertex); + if (_sconfig.update(getBatch(0).states)) { + setShaderAttributes(); + setRenderState(_sconfig.getState()); + } else { + _sconfig = null; _disableShaders = true; - return; } } @@ -545,6 +569,36 @@ public class SkinMesh extends ModelMesh } } + @Override // documentation inherited + protected ModelBatch createModelBatch () + { + // update the shader configs immediately before drawing + return new ModelBatch() { + protected void preDraw () { + if (_sconfig != null) { + _sconfig.update(states); + } + } + protected void preDrawOverlay (int oidx) { + super.preDrawOverlay(oidx); + if (_osconfigs != null) { + _ostates[RenderState.RS_GLSL_SHADER_OBJECTS] = + states[RenderState.RS_GLSL_SHADER_OBJECTS]; + SkinShaderConfig osconfig = _osconfigs.get(oidx); + states[RenderState.RS_GLSL_SHADER_OBJECTS] = osconfig.getState(); + _osconfigs.get(oidx).update(states); + } + } + protected void postDrawOverlay (int oidx) { + super.postDrawOverlay(oidx); + if (_osconfigs != null) { + states[RenderState.RS_GLSL_SHADER_OBJECTS] = + _ostates[RenderState.RS_GLSL_SHADER_OBJECTS]; + } + } + }; + } + @Override // documentation inherited protected void storeOriginalBuffers () { @@ -562,8 +616,9 @@ public class SkinMesh extends ModelMesh /** * Initializes the skin shader attributes (bone indices and weights) in the supplied state. */ - protected void setShaderAttributes (GLSLShaderObjectsState sstate, int bonesPerVertex) + protected void setShaderAttributes () { + int bonesPerVertex = _sconfig.getBonesPerVertex(); int size = getBatch(0).getVertexCount() * bonesPerVertex; ByteBuffer bibuf = BufferUtils.createByteBuffer(size); FloatBuffer bwbuf = BufferUtils.createFloatBuffer(size); @@ -584,10 +639,48 @@ public class SkinMesh extends ModelMesh bibuf.rewind(); bwbuf.rewind(); + GLSLShaderObjectsState sstate = _sconfig.getState(); sstate.setAttributePointer("boneIndices", bonesPerVertex, false, false, 0, bibuf); sstate.setAttributePointer("boneWeights", bonesPerVertex, false, 0, bwbuf); } + /** Tracks the configuration of a skin shader. */ + protected static class SkinShaderConfig extends ShaderConfig + { + public SkinShaderConfig (ShaderCache scache, int bonesPerVertex) + { + super(scache); + _bonesPerVertex = bonesPerVertex; + } + + public int getBonesPerVertex () + { + return _bonesPerVertex; + } + + @Override // documentation inherited + protected String getVertexShader () + { + return "media/jme/skin.vert"; + } + + @Override // documentation inherited + protected void getDefinitions (ArrayList defs) + { + super.getDefinitions(defs); + defs.add("BONES_PER_VERTEX " + _bonesPerVertex); + } + + @Override // documentation inherited + protected void getDerivedDefinitions (ArrayList ddefs) + { + super.getDerivedDefinitions(ddefs); + ddefs.add("MAX_BONE_COUNT " + MAX_SHADER_BONE_COUNT); + } + + protected int _bonesPerVertex; + } + /** A stored frame used for linear blending. */ protected static class BlendFrame { @@ -634,6 +727,12 @@ public class SkinMesh extends ModelMesh * versions. */ protected float[] _onbuf, _ovbuf, _nbuf; + /** The primary skin shader configuration. */ + protected SkinShaderConfig _sconfig; + + /** Skin shader configurations for each overlay. */ + protected ArrayList _osconfigs; + /** The frame id to store on the next update. If 0, don't store any frame * and skin the mesh as normal. If -1, a frame has been stored and thus * skinning should only take place when further frames are requested. */ diff --git a/src/java/com/threerings/jme/util/ShaderConfig.java b/src/java/com/threerings/jme/util/ShaderConfig.java new file mode 100644 index 00000000..0fe9b1d4 --- /dev/null +++ b/src/java/com/threerings/jme/util/ShaderConfig.java @@ -0,0 +1,338 @@ +// +// $Id: JmeUtil.java 229 2007-05-08 01:50:07Z andrzej $ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.jme.util; + +import java.util.ArrayList; + +import com.jme.image.Texture; +import com.jme.light.Light; +import com.jme.scene.state.GLSLShaderObjectsState; +import com.jme.scene.state.LightState; +import com.jme.scene.state.RenderState; +import com.jme.scene.state.TextureState; +import com.jme.system.DisplaySystem; + +import com.samskivert.util.StringUtil; + +/** + * Tracks the configuration of a shader, which depends on (among other things) a set of + * {@link RenderState}s. When the state set changes, the shader must be reconfigured + * (recompiled or refetched from the {@link ShaderCache}). + */ +public abstract class ShaderConfig + implements Cloneable +{ + public ShaderConfig (ShaderCache scache) + { + _scache = scache; + _state = DisplaySystem.getDisplaySystem().getRenderer().createGLSLShaderObjectsState(); + } + + /** + * Returns a reference to the render state controlled by this configuration. + */ + public GLSLShaderObjectsState getState () + { + return _state; + } + + /** + * Updates the configuration according to the provided state set. If the configuration has + * changed, the shader will be recompiled or refetched from the cache in order to reflect the + * new configuration. + * + * @return true if all went all, false if the shader could not be compiled. + */ + public boolean update (RenderState[] states) + { + // update the configurations to determine if we must reconfigure + if (!updateConfigs(states) && _state.getProgramID() > 0) { + return true; + } + + // reconfigure the shader state, generating the derived definitions only if the + // required configuration isn't in the cache + String vert = getVertexShader(), frag = getFragmentShader(); + ArrayList defs = new ArrayList(); + getDefinitions(defs); + String[] darray = defs.toArray(new String[defs.size()]), ddarray = null; + if (!_scache.isLoaded(vert, frag, darray)) { + ArrayList ddefs = new ArrayList(); + getDerivedDefinitions(ddefs); + ddarray = ddefs.toArray(new String[ddefs.size()]); + } + return _scache.configureState(_state, vert, frag, darray, ddarray); + } + + @Override // documentation inherited + public Object clone () + { + ShaderConfig other = null; + try { + other = (ShaderConfig)super.clone(); + other._state = + DisplaySystem.getDisplaySystem().getRenderer().createGLSLShaderObjectsState(); + other._state.setProgramID(_state.getProgramID()); + other._state.attribs = _state.attribs; + if (_lights != null) { + other._lights = (LightConfig[])_lights.clone(); + for (int ii = 0; ii < _lights.length; ii++) { + other._lights[ii] = (LightConfig)_lights[ii].clone(); + } + } + if (_textures != null) { + other._textures = (TextureConfig[])_textures.clone(); + for (int ii = 0; ii < _textures.length; ii++) { + other._textures[ii] = (TextureConfig)_textures[ii].clone(); + } + } + } catch (CloneNotSupportedException e) { + // will not happen + } + return other; + } + + /** + * Updates the component configurations according to the supplied state set, returning + * true if they have changed and the shader must be reconfigured. + */ + protected boolean updateConfigs (RenderState[] states) + { + // this is one place where we don't want short-circuit evaluation + boolean lchanged = updateLightConfigs((LightState)states[RenderState.RS_LIGHT]); + boolean tchanged = updateTextureConfigs((TextureState)states[RenderState.RS_TEXTURE]); + return lchanged || tchanged; + } + + /** + * Updates the light configurations, returning true if they have changed. + */ + protected boolean updateLightConfigs (LightState lstate) + { + if (lstate == null || !lstate.isEnabled()) { + LightConfig[] olights = _lights; + _lights = null; + return (olights != null); + } + int lcount = lstate.getQuantity(); + if (_lights == null || _lights.length != lcount) { + _lights = new LightConfig[lcount]; + for (int ii = 0; ii < lcount; ii++) { + _lights[ii] = new LightConfig(); + _lights[ii].update(lstate.get(ii)); + } + return true; + } + boolean changed = false; + for (int ii = 0; ii < lcount; ii++) { + changed |= _lights[ii].update(lstate.get(ii)); + } + return changed; + } + + /** + * Updates the texture configurations, returning true if they have changed. + */ + protected boolean updateTextureConfigs (TextureState tstate) + { + if (tstate == null || !tstate.isEnabled()) { + TextureConfig[] otextures = _textures; + _textures = null; + return (otextures != null); + } + int tcount = tstate.getNumberOfSetTextures(); + if (_textures == null || _textures.length != tcount) { + _textures = new TextureConfig[tcount]; + for (int ii = 0; ii < tcount; ii++) { + _textures[ii] = new TextureConfig(); + _textures[ii].update(tstate.getTexture(ii)); + } + return true; + } + boolean changed = false; + for (int ii = 0; ii < tcount; ii++) { + changed |= _textures[ii].update(tstate.getTexture(ii)); + } + return changed; + } + + /** + * Returns the resource name of the vertex shader (or null for none). + */ + protected String getVertexShader () + { + return null; + } + + /** + * Returns the resource name of the fragment shader (or null for none). + */ + protected String getFragmentShader () + { + return null; + } + + /** + * Adds the preprocessor definitions that this configuration requires for its shader to the + * supplied list. + */ + protected void getDefinitions (ArrayList defs) + { + // the distinguishing definitions are just keys + if (_lights != null) { + defs.add("LIGHTS " + StringUtil.join(_lights, "/")); + } + if (_textures != null) { + defs.add("TEXTURES " + StringUtil.join(_textures, "/")); + } + } + + /** + * Adds the derived preprocessor definitions that this configuration requires to the supplied + * list. The derived definitions are not used to distinguish between cached shaders. + */ + protected void getDerivedDefinitions (ArrayList ddefs) + { + StringBuffer buf = new StringBuffer("SET_FRONT_COLOR "); + if (_lights != null) { + buf.append("gl_FrontColor.rgb = gl_FrontLightModelProduct.sceneColor.rgb; "); + for (int ii = 0; ii < _lights.length; ii++) { + LightConfig light = _lights[ii]; + if (light.type == -1) { + continue; + } + if (light.type == Light.LT_POINT) { + buf.append("vec3 lvec" + ii + " = gl_LightSource[" + ii + + "].position.xyz - eyeVertex;"); + buf.append("float ldist" + ii + " = length(lvec" + ii + ");"); + } + buf.append("gl_FrontColor.rgb += (gl_FrontLightProduct[" + ii + "].ambient.rgb + "); + buf.append("gl_FrontLightProduct[" + ii + "].diffuse.rgb * max(dot(eyeNormal, "); + if (light.type == Light.LT_POINT) { + buf.append("normalize(lvec" + ii + ")), 0.0)) "); + buf.append("/ (gl_LightSource[" + ii + "].constantAttenuation + " + + "ldist" + ii + " * gl_LightSource[" + ii + "].linearAttenuation + " + + "ldist" + ii + " * ldist" + ii + " * gl_LightSource[" + ii + + "].quadraticAttenuation);"); + } else { + buf.append("gl_LightSource[" + ii + "].position.xyz), 0.0));"); + } + } + buf.append("gl_FrontColor.a = gl_FrontMaterial.diffuse.a;"); + } else { + buf.append("gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0);"); + } + ddefs.add(buf.toString()); + buf = new StringBuffer("SET_TEX_COORDS"); + if (_textures != null) { + for (int ii = 0; ii < _textures.length; ii++) { + TextureConfig texture = _textures[ii]; + if (texture.envMapMode == -1) { + continue; + } + buf.append(" gl_TexCoord[" + ii + "] = "); + if (texture.envMapMode == Texture.EM_SPHERE) { + buf.append("vec4(eyeNormal.xy * 0.5 + vec2(0.5, 0.5), 0.0, 1.0);"); + } else { + buf.append("gl_MultiTexCoord" + ii + ";"); + } + } + } + ddefs.add(buf.toString()); + } + + /** The configuration of a single light in a {@link LightState}. */ + protected static class LightConfig + implements Cloneable + { + /** The type of light (see {@link Light#getType}). */ + public int type = -1; + + public boolean update (Light light) + { + int otype = type; + type = (light == null) ? -1 : light.getType(); + return (otype != type); + } + + @Override // documentation inherited + public Object clone () + { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // will not happen + return null; + } + } + + @Override // documentation inherited + public String toString () + { + return Integer.toString(type); + } + } + + /** The configuration of a single texture in a {@link TextureState}. */ + protected static class TextureConfig + implements Cloneable + { + /** The environment map mode (see {@link Texture#getEnvironmentalMapMode}). */ + public int envMapMode = -1; + + public boolean update (Texture texture) + { + int omode = envMapMode; + envMapMode = (texture == null) ? -1 : texture.getEnvironmentalMapMode(); + return (omode != envMapMode); + } + + @Override // documentation inherited + public Object clone () + { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // will not happen + return null; + } + } + + @Override // documentation inherited + public String toString () + { + return Integer.toString(envMapMode); + } + } + + /** The cache used to reconfigure shaders. */ + protected ShaderCache _scache; + + /** The state object to reconfigure. */ + protected GLSLShaderObjectsState _state; + + /** The current light configurations (or null if lighting is disabled). */ + protected LightConfig[] _lights; + + /** The current texture configurations (or null if texturing is disabled). */ + protected TextureConfig[] _textures; +}