diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java index 51a5aeb638..4c8d9225e7 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java @@ -17,6 +17,7 @@ import com.jme3.math.Spline.SplineType; import com.jme3.math.Vector3f; import com.jme3.math.Vector4f; +import com.jme3.math.interpolations.impl.LinearVectorInterpolation; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.mesh.IndexBuffer; import com.jme3.scene.plugins.blender.BlenderContext; @@ -849,7 +850,10 @@ public Vector3f getValueAlongCurve(float alongRatio) { float edgeLength = vertices[i].distance(vertices[i - 1]); if (length + edgeLength > probeLength) { float ratioAlongEdge = (probeLength - length) / edgeLength; - return FastMath.interpolateLinear(ratioAlongEdge, vertices[i - 1], vertices[i]); + + Vector3f res = new Vector3f(); + new LinearVectorInterpolation(vertices[i - 1], vertices[i]).interpolate(ratioAlongEdge, res); + return res; } else if (length + edgeLength == probeLength) { return vertices[i]; } diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java index 38e22ea305..ce139365e7 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java @@ -35,6 +35,8 @@ import com.jme3.math.Quaternion; import com.jme3.math.Transform; import com.jme3.math.Vector3f; +import com.jme3.math.interpolations.impl.LinearQuaternionInterpolation; +import com.jme3.math.interpolations.impl.LinearVectorInterpolation; /** * A convenience class to easily setup a spatial keyframed animation @@ -429,14 +431,22 @@ private void interpolate(Object[] keyFrames, Type type) { //interpolationg depending on the transform type switch (type) { case Translation: - translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + // TODO: not very flexible, one should consider allowing to set Interpolations + Vector3f res = new Vector3f(); + new LinearVectorInterpolation((Vector3f) keyFrames[i], (Vector3f) keyFrames[key]).interpolate(val, res); + translations[j] = res; break; case Rotation: + // TODO: not very flexible, one should consider allowing to set Interpolations Quaternion rot = new Quaternion(); - rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val); + new LinearQuaternionInterpolation(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation).interpolate(val, rot); + rotations[j] = rot; break; case Scale: - scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + // TODO: not very flexible, one should consider allowing to set Interpolations + Vector3f scale = new Vector3f(); + new LinearVectorInterpolation((Vector3f) keyFrames[i], (Vector3f) keyFrames[key]).interpolate(val, scale); + scales[j] = scale; break; } } diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index ca3467781a..ea7f2fca8f 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -45,6 +45,8 @@ import com.jme3.math.FastMath; import com.jme3.math.Matrix3f; import com.jme3.math.Vector3f; +import com.jme3.math.interpolations.api.Interpolation; +import com.jme3.math.interpolations.impl.LinearFloatInterpolation; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; @@ -101,8 +103,21 @@ public class ParticleEmitter extends Geometry { private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f); private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f); + + /** + * Please use an instance of {@link com.jme3.math.interpolations.api.Interpolation} + */ + @Deprecated private float startSize = 0.2f; + + /** + * Please use an instance of {@link com.jme3.math.interpolations.api.Interpolation} + */ + @Deprecated private float endSize = 2f; + + private Interpolation sizeInterpolation; + private boolean worldSpace = true; //variable that helps with computations private transient Vector3f temp = new Vector3f(); @@ -196,6 +211,8 @@ public ParticleEmitter clone(boolean cloneMaterial) { public ParticleEmitter(String name, Type type, int numParticles) { super(name); + + this.updateSizeInterpolationForStartAndEnd(); setBatchHint(BatchHint.Never); // ignore world transform, unless user sets inLocalSpace this.setIgnoreTransform(true); @@ -237,6 +254,7 @@ public ParticleEmitter(String name, Type type, int numParticles) { */ public ParticleEmitter() { super(); + this.updateSizeInterpolationForStartAndEnd(); setBatchHint(BatchHint.Never); } @@ -551,8 +569,12 @@ public void setEndColor(ColorRGBA endColor) { * * @return the end size of the particles spawned. * - * @see ParticleEmitter#setEndSize(float) + * @see ParticleEmitter#setEndSize(float) + * + * @Deprecated Please use {ParticleEmitter#getInterpolation} to properly get the interpolation of this particle- + * systems particle size. */ + @Deprecated public float getEndSize() { return endSize; } @@ -566,9 +588,15 @@ public float getEndSize() { * to its end of life. * * @param endSize the end size of the particles spawned. + * + * @Deprecated Please use {ParticleEmitter#setInterpolation} to properly set the interpolation of this particle- + * systems particle size. */ + @Deprecated public void setEndSize(float endSize) { this.endSize = endSize; + + this.updateSizeInterpolationForStartAndEnd(); } /** @@ -753,8 +781,12 @@ public void setStartColor(ColorRGBA startColor) { * * @return the start color of the particles spawned. * - * @see ParticleEmitter#setStartSize(float) + * @see ParticleEmitter#setStartSize(float) + * + * @Deprecated Please use {ParticleEmitter#getInterpolation} to properly get the interpolation of this particle- + * systems particle size. */ + @Deprecated public float getStartSize() { return startSize; } @@ -767,9 +799,15 @@ public float getStartSize() { * to its end of life. * * @param startSize the start size of the particles spawned. + * + * @Deprecated Please use {ParticleEmitter#setInterpolation} to properly set the interpolation of this particle- + * systems particle size. */ + @Deprecated public void setStartSize(float startSize) { this.startSize = startSize; + + this.updateSizeInterpolationForStartAndEnd(); } /** @@ -954,7 +992,7 @@ private void swap(int idx1, int idx2) { particles[idx2] = p1; } - private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){ + /* Test */ public void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){ // applying gravity p.velocity.x -= gravity.x * tpf; p.velocity.y -= gravity.y * tpf; @@ -965,7 +1003,7 @@ private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){ // affecting color, size and angle float b = (p.startlife - p.life) / p.startlife; p.color.interpolateLocal(startColor, endColor, b); - p.size = FastMath.interpolateLinear(b, startSize, endSize); + p.size = this.getSizeInterpolation().interpolate(b); p.angle += p.rotateSpeed * tpf; // Computing bounding volume @@ -1069,6 +1107,35 @@ public boolean isEnabled() { return enabled; } + + /** + * Gives the {@link Interpolation} that is used to increase the size of this particle + * @param interpolation The new interpolation used for the particles of this system + */ + public void setSizeInterpolation(Interpolation interpolation) { + this.sizeInterpolation = interpolation; + } + + /* + * Gets the {@link Interpolation} that is used to increase the size of this particle + * + * @return The used Interpolator + */ + public Interpolation getSizeInterpolation() { + return this.sizeInterpolation; + } + + /* + * Used to compute a sizeInterpolation when the old {@link setStartSize} and {@link setEndSize} methods are used. + * + * @deprecated Should be removed together with {@link setStartSize} and family + */ + @Deprecated + private void updateSizeInterpolationForStartAndEnd() { + this.sizeInterpolation = new LinearFloatInterpolation(this.startSize, this.endSize); + } + + /** * Callback from Control.update(), do not use. * @param tpf diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapText.java b/jme3-core/src/main/java/com/jme3/font/BitmapText.java index 913bfe13a4..ee42d7e9ac 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapText.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapText.java @@ -423,7 +423,7 @@ public void render(RenderManager rm, ColorRGBA color) { mat.setTexture("ColorMap", page.getTexture()); //ColorRGBA original = getColor(mat, "Color"); //mat.setColor("Color", color); - mat.render(page, rm); + page.render(rm); //if( original == null ) { // mat.clearParam("Color"); diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index d636858c7c..059f8081c8 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -38,6 +38,8 @@ import com.jme3.input.controls.*; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; +import com.jme3.math.interpolations.api.Interpolation; +import com.jme3.math.interpolations.impl.LinearFloatInterpolation; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; @@ -99,7 +101,6 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control { protected Vector3f temp = new Vector3f(0, 0, 0); protected boolean invertYaxis = false; protected boolean invertXaxis = false; - /** * @deprecated use {@link CameraInput#CHASECAM_DOWN} */ @@ -436,7 +437,7 @@ protected void updateCamera(float tpf) { //computing lerp factor trailingLerpFactor = Math.min(trailingLerpFactor + tpf * tpf * trailingSensitivity, 1); //computing rotation by linear interpolation - rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation); + rotation = new LinearFloatInterpolation(rotation, targetRotation).interpolate(trailingLerpFactor); //if the rotation is near the target rotation we're good, that's over if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) { @@ -449,7 +450,7 @@ protected void updateCamera(float tpf) { if (chasing) { distance = temp.set(targetLocation).subtractLocal(cam.getLocation()).length(); distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * chasingSensitivity * 0.05f), 1); - distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance); + distance = new LinearFloatInterpolation(distance, targetDistance).interpolate(distanceLerpFactor); if (targetDistance + 0.01f >= distance && targetDistance - 0.01f <= distance) { distanceLerpFactor = 0; chasing = false; @@ -459,7 +460,7 @@ protected void updateCamera(float tpf) { //linear interpolation of the distance while zooming if (zooming) { distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * zoomSensitivity), 1); - distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance); + distance = new LinearFloatInterpolation(distance, targetDistance).interpolate(distanceLerpFactor); if (targetDistance + 0.1f >= distance && targetDistance - 0.1f <= distance) { zooming = false; distanceLerpFactor = 0; @@ -469,7 +470,7 @@ protected void updateCamera(float tpf) { //linear interpolation of the rotation while rotating horizontally if (rotating) { rotationLerpFactor = Math.min(rotationLerpFactor + tpf * tpf * rotationSensitivity, 1); - rotation = FastMath.interpolateLinear(rotationLerpFactor, rotation, targetRotation); + rotation = new LinearFloatInterpolation(rotation, targetRotation).interpolate(rotationLerpFactor); if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) { rotating = false; rotationLerpFactor = 0; @@ -479,7 +480,7 @@ protected void updateCamera(float tpf) { //linear interpolation of the rotation while rotating vertically if (vRotating) { vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1); - vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation); + vRotation = new LinearFloatInterpolation(vRotation, targetVRotation).interpolate(vRotationLerpFactor); if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) { vRotating = false; vRotationLerpFactor = 0; diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java index 677c17d558..91bf78a561 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParam.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -129,7 +129,7 @@ public void setValue(Object value) { this.value = value; } - void apply(Renderer r, Technique technique) { + /* TODO */ public void apply(Renderer r, Technique technique) { technique.updateUniformParam(getPrefixedName(), getVarType(), getValue()); } diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 2aabbd6676..0dbb2afb2c 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -45,6 +45,10 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.RendererException; +import com.jme3.renderer.geometryrenderers.GeometryRenderer; +import com.jme3.renderer.geometryrenderers.MultiPassGeometryRenderer; +import com.jme3.renderer.geometryrenderers.NoLightGeometryRenderer; +import com.jme3.renderer.geometryrenderers.SinglePassGeometryRenderer; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; @@ -79,7 +83,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { private static final Logger logger = Logger.getLogger(Material.class.getName()); private static final RenderState additiveLight = new RenderState(); private static final RenderState depthOnly = new RenderState(); - private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1); static { depthOnly.setDepthTest(true); @@ -95,7 +98,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { private MaterialDef def; private ListMap paramValues = new ListMap(); private Technique technique; - private HashMap techniques = new HashMap(); + private Map techniques = new HashMap(); private int nextTexUnit = 0; private RenderState additionalState = null; private RenderState mergedRenderState = new RenderState(); @@ -306,22 +309,6 @@ public int contentHashCode() { return hash; } - /** - * Returns the currently active technique. - *

- * The technique is selected automatically by the {@link RenderManager} - * based on system capabilities. Users may select their own - * technique by using - * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }. - * - * @return the currently active technique. - * - * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) - */ - public Technique getActiveTechnique() { - return technique; - } - /** * Check if the transparent value marker is set on this material. * @return True if the transparent value marker is set on this material. @@ -372,24 +359,6 @@ public void setReceivesShadows(boolean receivesShadows) { this.receivesShadows = receivesShadows; } - /** - * Acquire the additional {@link RenderState render state} to apply - * for this material. - * - *

The first call to this method will create an additional render - * state which can be modified by the user to apply any render - * states in addition to the ones used by the renderer. Only render - * states which are modified in the additional render state will be applied. - * - * @return The additional render state. - */ - public RenderState getAdditionalRenderState() { - if (additionalState == null) { - additionalState = RenderState.ADDITIONAL.clone(); - } - return additionalState; - } - /** * Get the material definition (j3md file info) that this * material is implementing. @@ -695,541 +664,6 @@ public void setVector4(String name, Vector4f value) { setParam(name, VarType.Vector4, value); } - private ColorRGBA getAmbientColor(LightList lightList, boolean removeLights) { - ambientLightColor.set(0, 0, 0, 1); - for (int j = 0; j < lightList.size(); j++) { - Light l = lightList.get(j); - if (l instanceof AmbientLight) { - ambientLightColor.addLocal(l.getColor()); - if(removeLights){ - lightList.remove(l); - } - } - } - ambientLightColor.a = 1.0f; - return ambientLightColor; - } - - private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { - Mesh mesh = geom.getMesh(); - int lodLevel = geom.getLodLevel(); - if (geom instanceof InstancedGeometry) { - InstancedGeometry instGeom = (InstancedGeometry) geom; - int numInstances = instGeom.getActualNumInstances(); - if (numInstances == 0) { - return; - } - if (renderer.getCaps().contains(Caps.MeshInstancing)) { - renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData()); - } else { - throw new RendererException("Mesh instancing is not supported by the video hardware"); - } - } else { - renderer.renderMesh(mesh, lodLevel, 1, null); - } - } - - /** - * Uploads the lights in the light list as two uniform arrays.

* - *

- * uniform vec4 g_LightColor[numLights];
// - * g_LightColor.rgb is the diffuse/specular color of the light.
// - * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// - * 2 = Spot.

- * uniform vec4 g_LightPosition[numLights];
// - * g_LightPosition.xyz is the position of the light (for point lights)
- * // or the direction of the light (for directional lights).
// - * g_LightPosition.w is the inverse radius (1/r) of the light (for - * attenuation)

- */ - protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) { - if (numLights == 0) { // this shader does not do lighting, ignore. - return 0; - } - - Uniform lightData = shader.getUniform("g_LightData"); - lightData.setVector4Length(numLights * 3);//8 lights * max 3 - Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - - - if (startIndex != 0) { - // apply additive blending for 2nd and future passes - rm.getRenderer().applyRenderState(additiveLight); - ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); - }else{ - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList,true)); - } - - int lightDataIndex = 0; - TempVars vars = TempVars.get(); - Vector4f tmpVec = vars.vect4f1; - int curIndex; - int endIndex = numLights + startIndex; - for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { - - - Light l = lightList.get(curIndex); - if(l.getType() == Light.Type.Ambient){ - endIndex++; - continue; - } - ColorRGBA color = l.getColor(); - //Color - lightData.setVector4InArray(color.getRed(), - color.getGreen(), - color.getBlue(), - l.getType().getId(), - lightDataIndex); - lightDataIndex++; - - switch (l.getType()) { - case Directional: - DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); - //Data directly sent in view space to avoid a matrix mult for each pixel - tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); -// tmpVec.divideLocal(tmpVec.w); -// tmpVec.normalizeLocal(); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); - lightDataIndex++; - //PADDING - lightData.setVector4InArray(0,0,0,0, lightDataIndex); - lightDataIndex++; - break; - case Point: - PointLight pl = (PointLight) l; - Vector3f pos = pl.getPosition(); - float invRadius = pl.getInvRadius(); - tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); - //tmpVec.divideLocal(tmpVec.w); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); - lightDataIndex++; - //PADDING - lightData.setVector4InArray(0,0,0,0, lightDataIndex); - lightDataIndex++; - break; - case Spot: - SpotLight sl = (SpotLight) l; - Vector3f pos2 = sl.getPosition(); - Vector3f dir2 = sl.getDirection(); - float invRange = sl.getInvSpotRange(); - float spotAngleCos = sl.getPackedAngleCos(); - tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); - // tmpVec.divideLocal(tmpVec.w); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); - lightDataIndex++; - - //We transform the spot direction in view space here to save 5 varying later in the lighting shader - //one vec4 less and a vec4 that becomes a vec3 - //the downside is that spotAngleCos decoding happens now in the frag shader. - tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); - tmpVec.normalizeLocal(); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); - lightDataIndex++; - break; - default: - throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); - } - } - vars.release(); - //Padding of unsued buffer space - while(lightDataIndex < numLights * 3) { - lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); - lightDataIndex++; - } - return curIndex; - } - - protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) { - - Renderer r = rm.getRenderer(); - Uniform lightDir = shader.getUniform("g_LightDirection"); - Uniform lightColor = shader.getUniform("g_LightColor"); - Uniform lightPos = shader.getUniform("g_LightPosition"); - Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - boolean isFirstLight = true; - boolean isSecondLight = false; - - for (int i = 0; i < lightList.size(); i++) { - Light l = lightList.get(i); - if (l instanceof AmbientLight) { - continue; - } - - if (isFirstLight) { - // set ambient color for first light only - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false)); - isFirstLight = false; - isSecondLight = true; - } else if (isSecondLight) { - ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); - // apply additive blending for 2nd and future lights - r.applyRenderState(additiveLight); - isSecondLight = false; - } - - TempVars vars = TempVars.get(); - Quaternion tmpLightDirection = vars.quat1; - Quaternion tmpLightPosition = vars.quat2; - ColorRGBA tmpLightColor = vars.color; - Vector4f tmpVec = vars.vect4f1; - - ColorRGBA color = l.getColor(); - tmpLightColor.set(color); - tmpLightColor.a = l.getType().getId(); - lightColor.setValue(VarType.Vector4, tmpLightColor); - - switch (l.getType()) { - case Directional: - DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); - //FIXME : there is an inconstency here due to backward - //compatibility of the lighting shader. - //The directional light direction is passed in the - //LightPosition uniform. The lighting shader needs to be - //reworked though in order to fix this. - tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - tmpLightDirection.set(0, 0, 0, 0); - lightDir.setValue(VarType.Vector4, tmpLightDirection); - break; - case Point: - PointLight pl = (PointLight) l; - Vector3f pos = pl.getPosition(); - float invRadius = pl.getInvRadius(); - - tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - tmpLightDirection.set(0, 0, 0, 0); - lightDir.setValue(VarType.Vector4, tmpLightDirection); - break; - case Spot: - SpotLight sl = (SpotLight) l; - Vector3f pos2 = sl.getPosition(); - Vector3f dir2 = sl.getDirection(); - float invRange = sl.getInvSpotRange(); - float spotAngleCos = sl.getPackedAngleCos(); - - tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - - //We transform the spot direction in view space here to save 5 varying later in the lighting shader - //one vec4 less and a vec4 that becomes a vec3 - //the downside is that spotAngleCos decoding happens now in the frag shader. - tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); - rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); - tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); - - lightDir.setValue(VarType.Vector4, tmpLightDirection); - - break; - default: - throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); - } - vars.release(); - r.setShader(shader); - renderMeshFromGeometry(r, g); - } - - if (isFirstLight) { - // Either there are no lights at all, or only ambient lights. - // Render a dummy "normal light" so we can see the ambient color. - ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false)); - lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); - lightPos.setValue(VarType.Vector4, nullDirLight); - r.setShader(shader); - renderMeshFromGeometry(r, g); - } - } - - /** - * Select the technique to use for rendering this material. - *

- * If name is "Default", then one of the - * {@link MaterialDef#getDefaultTechniques() default techniques} - * on the material will be selected. Otherwise, the named technique - * will be found in the material definition. - *

- * Any candidate technique for selection (either default or named) - * must be verified to be compatible with the system, for that, the - * renderManager is queried for capabilities. - * - * @param name The name of the technique to select, pass "Default" to - * select one of the default techniques. - * @param renderManager The {@link RenderManager render manager} - * to query for capabilities. - * - * @throws IllegalArgumentException If "Default" is passed and no default - * techniques are available on the material definition, or if a name - * is passed but there's no technique by that name. - * @throws UnsupportedOperationException If no candidate technique supports - * the system capabilities. - */ - public void selectTechnique(String name, RenderManager renderManager) { - // check if already created - Technique tech = techniques.get(name); - // When choosing technique, we choose one that - // supports all the caps. - EnumSet rendererCaps = renderManager.getRenderer().getCaps(); - if (tech == null) { - - if (name.equals("Default")) { - List techDefs = def.getDefaultTechniques(); - if (techDefs == null || techDefs.isEmpty()) { - throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'"); - } - - TechniqueDef lastTech = null; - for (TechniqueDef techDef : techDefs) { - if (rendererCaps.containsAll(techDef.getRequiredCaps())) { - // use the first one that supports all the caps - tech = new Technique(this, techDef); - techniques.put(name, tech); - if(tech.getDef().getLightMode() == renderManager.getPreferredLightMode() || - tech.getDef().getLightMode() == LightMode.Disable){ - break; - } - } - lastTech = techDef; - } - if (tech == null) { - throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n" - + " is supported by the video hardware. The caps " - + lastTech.getRequiredCaps() + " are required."); - } - - } else { - // create "special" technique instance - TechniqueDef techDef = def.getTechniqueDef(name); - if (techDef == null) { - throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name); - } - - if (!rendererCaps.containsAll(techDef.getRequiredCaps())) { - throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n" - + "requires caps " + techDef.getRequiredCaps() + " which are not " - + "supported by the video renderer"); - } - - tech = new Technique(this, techDef); - techniques.put(name, tech); - } - } else if (technique == tech) { - // attempting to switch to an already - // active technique. - return; - } - - technique = tech; - tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager); - - // shader was changed - sortingId = -1; - } - - private void autoSelectTechnique(RenderManager rm) { - if (technique == null) { - selectTechnique("Default", rm); - } else { - technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm); - } - } - - /** - * Preloads this material for the given render manager. - *

- * Preloading the material can ensure that when the material is first - * used for rendering, there won't be any delay since the material has - * been already been setup for rendering. - * - * @param rm The render manager to preload for - */ - public void preload(RenderManager rm) { - autoSelectTechnique(rm); - - Renderer r = rm.getRenderer(); - TechniqueDef techDef = technique.getDef(); - - Collection params = paramValues.values(); - for (MatParam param : params) { - param.apply(r, technique); - } - - r.setShader(technique.getShader()); - } - - private void clearUniformsSetByCurrent(Shader shader) { - ListMap uniforms = shader.getUniformMap(); - int size = uniforms.size(); - for (int i = 0; i < size; i++) { - Uniform u = uniforms.getValue(i); - u.clearSetByCurrentMaterial(); - } - } - - private void resetUniformsNotSetByCurrent(Shader shader) { - ListMap uniforms = shader.getUniformMap(); - int size = uniforms.size(); - for (int i = 0; i < size; i++) { - Uniform u = uniforms.getValue(i); - if (!u.isSetByCurrentMaterial()) { - if (u.getName().charAt(0) != 'g') { - // Don't reset world globals! - // The benefits gained from this are very minimal - // and cause lots of matrix -> FloatBuffer conversions. - u.clearValue(); - } - } - } - } - - /** - * Called by {@link RenderManager} to render the geometry by - * using this material. - *

- * The material is rendered as follows: - *

    - *
  • Determine which technique to use to render the material - - * either what the user selected via - * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) - * Material.selectTechnique()}, - * or the first default technique that the renderer supports - * (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering capabilities})
      - *
    • If the technique has been changed since the last frame, then it is notified via - * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, java.util.EnumSet) - * Technique.makeCurrent()}. - * If the technique wants to use a shader to render the model, it should load it at this part - - * the shader should have all the proper defines as declared in the technique definition, - * including those that are bound to material parameters. - * The technique can re-use the shader from the last frame if - * no changes to the defines occurred.
    - *
  • Set the {@link RenderState} to use for rendering. The render states are - * applied in this order (later RenderStates override earlier RenderStates):
      - *
    1. {@link TechniqueDef#getRenderState() Technique Definition's RenderState} - * - i.e. specific renderstate that is required for the shader.
    2. - *
    3. {@link #getAdditionalRenderState() Material Instance Additional RenderState} - * - i.e. ad-hoc renderstate set per model
    4. - *
    5. {@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} - * - i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or - * post-processing filter.
    - *
  • If the technique {@link TechniqueDef#isUsingShaders() uses a shader}, then the uniforms of the shader must be updated.
      - *
    • Uniforms bound to material parameters are updated based on the current material parameter values.
    • - *
    • Uniforms bound to world parameters are updated from the RenderManager. - * Internally {@link UniformBindingManager} is used for this task.
    • - *
    • Uniforms bound to textures will cause the texture to be uploaded as necessary. - * The uniform is set to the texture unit where the texture is bound.
    - *
  • If the technique uses a shader, the model is then rendered according - * to the lighting mode specified on the technique definition.
      - *
    • {@link LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays - * with the first 4 lights and renders the model once.
    • - *
    • {@link LightMode#MultiPass multi pass light mode} light mode renders the model multiple times, - * for the first light it is rendered opaque, on subsequent lights it is - * rendered with {@link BlendMode#AlphaAdditive alpha-additive} blending and depth writing disabled.
    • - *
    - *
  • For techniques that do not use shaders, - * fixed function OpenGL is used to render the model (see {@link GL1Renderer} interface):
      - *
    • OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is updated.
    • - *
    • The texture set on the material is uploaded and bound. - * Currently only 1 texture is supported for fixed function techniques.
    • - *
    • If the technique uses lighting, then OpenGL lighting state is updated - * based on the light list on the geometry, otherwise OpenGL lighting is disabled.
    • - *
    • The mesh is uploaded and rendered.
    • - *
    - *
- * - * @param geom The geometry to render - * @param lights Presorted and filtered light list to use for rendering - * @param rm The render manager requesting the rendering - */ - public void render(Geometry geom, LightList lights, RenderManager rm) { - autoSelectTechnique(rm); - TechniqueDef techDef = technique.getDef(); - - if (techDef.isNoRender()) return; - - Renderer r = rm.getRenderer(); - - if (rm.getForcedRenderState() != null) { - r.applyRenderState(rm.getForcedRenderState()); - } else { - if (techDef.getRenderState() != null) { - r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); - } else { - r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); - } - } - - - // update camera and world matrices - // NOTE: setWorldTransform should have been called already - - // reset unchanged uniform flag - clearUniformsSetByCurrent(technique.getShader()); - rm.updateUniformBindings(technique.getWorldBindUniforms()); - - - // setup textures and uniforms - for (int i = 0; i < paramValues.size(); i++) { - MatParam param = paramValues.getValue(i); - param.apply(r, technique); - } - - Shader shader = technique.getShader(); - - // send lighting information, if needed - switch (techDef.getLightMode()) { - case Disable: - break; - case SinglePass: - int nbRenderedLights = 0; - resetUniformsNotSetByCurrent(shader); - if (lights.size() == 0) { - nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, 0); - r.setShader(shader); - renderMeshFromGeometry(r, geom); - } else { - while (nbRenderedLights < lights.size()) { - nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights); - r.setShader(shader); - renderMeshFromGeometry(r, geom); - } - } - return; - case FixedPipeline: - throw new IllegalArgumentException("OpenGL1 is not supported"); - case MultiPass: - // NOTE: Special case! - resetUniformsNotSetByCurrent(shader); - renderMultipassLighting(shader, geom, lights, rm); - // very important, notice the return statement! - return; - } - - // upload and bind shader - // any unset uniforms will be set to 0 - resetUniformsNotSetByCurrent(shader); - r.setShader(shader); - - renderMeshFromGeometry(r, geom); - } - - /** - * Called by {@link RenderManager} to render the geometry by - * using this material. - * - * Note that this version of the render method - * does not perform light filtering. - * - * @param geom The geometry to render - * @param rm The render manager requesting the rendering - */ - public void render(Geometry geom, RenderManager rm) { - render(geom, geom.getWorldLightList(), rm); - } public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); @@ -1315,7 +749,7 @@ public void read(JmeImporter im) throws IOException { if (def.getMaterialParam(param.getName()) == null) { logger.log(Level.WARNING, "The material parameter is not defined: {0}. Ignoring..", - param.getName()); + param.getName()); } else { checkSetParam(param.getVarType(), param.getName()); paramValues.put(param.getName(), param); @@ -1353,4 +787,55 @@ public void read(JmeImporter im) throws IOException { setBoolean("SeparateTexCoord", true); } } + + /** + * Acquire the additional {@link RenderState render state} to apply + * for this material. + * + *

The first call to this method will create an additional render + * state which can be modified by the user to apply any render + * states in addition to the ones used by the renderer. Only render + * states which are modified in the additional render state will be applied. + * + * @return The additional render state. + */ + public RenderState getAdditionalRenderState() { + if (additionalState == null) { + additionalState = RenderState.ADDITIONAL.clone(); + } + return additionalState; + } + + public Map getTechiques() { + return this.techniques; + } + + /** + * Returns the currently active technique. + *

+ * The technique is selected automatically by the {@link RenderManager} + * based on system capabilities. Users may select their own + * technique by using + * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }. + * + * @return the currently active technique. + * + * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + */ + public Technique getActiveTechnique() { + return technique; + } + + /** + * Sets the technique this material will be rendered with + * + * Should only be used by the Geometry, for selecting what technique is used on a Geometry see + * {@link Geometry#selectTechnique(String, RenderManager)}. This method is for storage inside this + * data object only! + * + * @param tech + */ + public void setActiveTechnique(Technique tech) { + this.technique = tech; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 4f1a7feede..3725aab6a1 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -31,6 +31,8 @@ */ package com.jme3.math; +import com.jme3.math.interpolations.impl.*; + import java.util.Random; /** @@ -119,18 +121,13 @@ public static int nearestPowerOfTwo(int number) { * @param endValue * ending value. 100% of f * @return The interpolated value between startValue and endValue. + * + * @deprecated This method is deprecated see {@link LinearFloatInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static float interpolateLinear(float scale, float startValue, float endValue) { - if (startValue == endValue) { - return startValue; - } - if (scale <= 0f) { - return startValue; - } - if (scale >= 1f) { - return endValue; - } - return ((1f - scale) * startValue) + (scale * endValue); + return new LinearFloatInterpolation(startValue, endValue).interpolate(scale); } /** @@ -145,14 +142,16 @@ public static float interpolateLinear(float scale, float startValue, float endVa * ending value. 100% of f * @param store a vector3f to store the result * @return The interpolated value between startValue and endValue. + * + * @deprecated This method is deprecated in favor of {@link LinearVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) { if (store == null) { store = new Vector3f(); } - store.x = interpolateLinear(scale, startValue.x, endValue.x); - store.y = interpolateLinear(scale, startValue.y, endValue.y); - store.z = interpolateLinear(scale, startValue.z, endValue.z); + new LinearVectorInterpolation(startValue, endValue).interpolate(scale, store); return store; } @@ -166,8 +165,11 @@ public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vecto * Beginning value. 0% of f * @param endValue * ending value. 100% of f - * @return The interpolated value between startValue and endValue. + * + * @deprecated This method is deprecated in favor of {@link LinearVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue) { return interpolateLinear(scale, startValue, endValue, null); } @@ -242,15 +244,13 @@ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vecto * @param p2 control point 2 * @param p3 control point 3 * @return Catmull–Rom interpolation + * + * @deprecated This method is deprecated in favor of {@link CatmullRomFloatInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static float interpolateCatmullRom(float u, float T, float p0, float p1, float p2, float p3) { - float c1, c2, c3, c4; - c1 = p1; - c2 = -1.0f * T * p0 + T * p2; - c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; - c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; - - return (float) (((c4 * u + c3) * u + c2) * u + c1); + return new CatmullRomFloatInterpolation(T, p0, p1, p2, p3).interpolate(u); } /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. @@ -269,14 +269,16 @@ public static float interpolateCatmullRom(float u, float T, float p0, float p1, * @param p3 control point 3 * @param store a Vector3f to store the result * @return Catmull–Rom interpolation + * + * @deprecated This method is deprecated in favor of {@link CatmullRomVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { if (store == null) { store = new Vector3f(); } - store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x); - store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y); - store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z); + new CatmullRomVectorInterpolation(T, p0, p1, p2, p3).interpolate(u, store); return store; } @@ -296,7 +298,11 @@ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vect * @param p2 control point 2 * @param p3 control point 3 * @return Catmull–Rom interpolation + * + * @deprecated This method is deprecated in favor of {@link CatmullRomVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { return interpolateCatmullRom(u, T, p0, p1, p2, p3, null); } @@ -315,15 +321,13 @@ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vect * @param p2 control point 2 * @param p3 control point 3 * @return Bezier interpolation + * + * @deprecated This method is deprecated in favor of {@link BezierFloatInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) { - float oneMinusU = 1.0f - u; - float oneMinusU2 = oneMinusU * oneMinusU; - float u2 = u * u; - return p0 * oneMinusU2 * oneMinusU - + 3.0f * p1 * u * oneMinusU2 - + 3.0f * p2 * u2 * oneMinusU - + p3 * u2 * u; + return new BezierFloatInterpolation(p0, p1, p2, p3).interpolate(u); } /**Interpolate a spline between at least 4 control points following the Bezier equation. @@ -340,15 +344,16 @@ public static float interpolateBezier(float u, float p0, float p1, float p2, flo * @param p2 control point 2 * @param p3 control point 3 * @param store a Vector3f to store the result - * @return Bezier interpolation + * + * @deprecated This method is deprecated in favor of {@link BezierVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { if (store == null) { store = new Vector3f(); } - store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x); - store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y); - store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z); + new BezierVectorInterpolation(p0, p1, p2, p3).interpolate(u, store); return store; } @@ -366,7 +371,11 @@ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vect * @param p2 control point 2 * @param p3 control point 3 * @return Bezier interpolation + * + * @deprecated This method is deprecated in favor of {@link BezierVectorInterpolation}. + * This facade is kept for compatibility. */ + @Deprecated public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { return interpolateBezier(u, p0, p1, p2, p3, null); } @@ -416,10 +425,12 @@ public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f * @return the length of the segment */ public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + BezierVectorInterpolation interpolation = new BezierVectorInterpolation(p0, p1, p2, p3); + float delta = 0.02f, t = 0.0f, result = 0.0f; Vector3f v1 = p0.clone(), v2 = new Vector3f(); while (t <= 1.0f) { - FastMath.interpolateBezier(t, p0, p1, p2, p3, v2); + interpolation.interpolate(t, v2); result += v1.subtractLocal(v2).length(); v1.set(v2); t += delta; diff --git a/jme3-core/src/main/java/com/jme3/math/Vector2f.java b/jme3-core/src/main/java/com/jme3/math/Vector2f.java index c1c2bcba49..7ac9e28d2d 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector2f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector2f.java @@ -36,15 +36,19 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.logging.Logger; /** - * Vector2f defines a Vector for a two float value vector. + * Vector2f defines a VectorNf for a two float value vector. * * @author Mark Powell * @author Joshua Slack */ -public final class Vector2f implements Savable, Cloneable, java.io.Serializable { +public final class Vector2f implements VectorNf, Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); @@ -408,7 +412,7 @@ public Vector2f mult(float scalar, Vector2f product) { * * @param scalar * the value to divide this vectors attributes by. - * @return the result Vector. + * @return the result Vector2f. */ public Vector2f divide(float scalar) { return new Vector2f(x / scalar, y / scalar); @@ -753,4 +757,48 @@ public void rotateAroundOrigin(float angle, boolean cw) { x = newX; y = newY; } + + @Override + public int size() { + return 2; + } + + + /** + * @param index + * @return x value if index == 0, y value if index == 1 + * @throws IllegalArgumentException + * if index is not one of 0, 1 + */ + @Override + public float get(int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + } + throw new IllegalArgumentException("index must be either 0 or 1"); + } + + /** + * @param index + * which field index in this vector to set. + * @param value + * to set to one of x or y. + * @throws IllegalArgumentException + * if index is not one of 0, 1 + */ + @Override + public void setIndex(int index, float value) { + switch (index) { + case 0: + x = value; + return; + case 1: + y = value; + return; + } + throw new IllegalArgumentException("index must be either 0 or 1"); + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java index b5d2d8fc6e..946f7e67f2 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -34,6 +34,8 @@ import com.jme3.export.*; import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.logging.Logger; /* @@ -41,7 +43,7 @@ */ /** - * Vector3f defines a Vector for a three float value tuple. + * Vector3f defines a VectorNf for a three float value tuple. * Vector3f can represent any three dimensional value, such as a * vertex, a normal, etc. Utility methods are also included to aid in * mathematical calculations. @@ -49,7 +51,7 @@ * @author Mark Powell * @author Joshua Slack */ -public final class Vector3f implements Savable, Cloneable, java.io.Serializable { +public final class Vector3f implements VectorNf, Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; @@ -592,7 +594,7 @@ public Vector3f mult(Vector3f vec, Vector3f store) { * * @param scalar * the value to divide this vectors attributes by. - * @return the result Vector. + * @return the result Vector3f. */ public Vector3f divide(float scalar) { scalar = 1f/scalar; @@ -623,7 +625,7 @@ public Vector3f divideLocal(float scalar) { * * @param scalar * the value to divide this vectors attributes by. - * @return the result Vector. + * @return the result Vector3f. */ public Vector3f divide(Vector3f scalar) { return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); @@ -1079,4 +1081,13 @@ public void set(int index, float value) { throw new IllegalArgumentException("index must be either 0, 1 or 2"); } + public void setIndex(int index, float value) { + this.set(index, value); + } + + + @Override + public int size() { + return 3; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Vector4f.java b/jme3-core/src/main/java/com/jme3/math/Vector4f.java index 5db9d4a6dd..7ce6fd64de 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector4f.java @@ -33,17 +33,19 @@ import com.jme3.export.*; import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.logging.Logger; /** - * Vector4f defines a Vector for a four float value tuple. + * Vector4f defines a VectorNf for a four float value tuple. * Vector4f can represent any four dimensional value, such as a * vertex, a normal, etc. Utility methods are also included to aid in * mathematical calculations. * * @author Maarten Steur */ -public final class Vector4f implements Savable, Cloneable, java.io.Serializable { +public final class Vector4f implements VectorNf, Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; @@ -509,7 +511,7 @@ public Vector4f mult(Vector4f vec, Vector4f store) { * * @param scalar * the value to divide this vectors attributes by. - * @return the result Vector. + * @return the result Vector4f. */ public Vector4f divide(float scalar) { scalar = 1f/scalar; @@ -540,7 +542,7 @@ public Vector4f divideLocal(float scalar) { * * @param scalar * the value to divide this vectors attributes by. - * @return the result Vector. + * @return the result Vector3f. */ public Vector4f divide(Vector4f scalar) { return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w); @@ -1001,4 +1003,12 @@ public void set(int index, float value) { throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); } + public void setIndex(int index, float value) { + this.set(index, value); + } + + @Override + public int size() { + return 4; + } } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/math/VectorNf.java b/jme3-core/src/main/java/com/jme3/math/VectorNf.java new file mode 100644 index 0000000000..0826a7bf60 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/VectorNf.java @@ -0,0 +1,32 @@ +package com.jme3.math; + + +import java.util.Iterator; + +/** + * Common ancenstor of all Vector classes + * + * Specifies a generic way to access vectors of different sizes + */ +public interface VectorNf { + /* + * Returns the size of the Vector currenlty being evaluated + */ + public int size(); + + /* + * Returns the component on a certain index. + * + * Should throw an exception if the index is outside of the range of this VectorNF. Always returns a float if the + * index is between zero and VectorNf#size(); + */ + public float get(int index); + + /* + * Sets the component on a certain index. + * + * Should throw an exception if the index is outside of the range of this VectorNF. Always sets a float if the + * index is between zero and VectorNf#size(); + */ + public void setIndex(int index, float value); +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/api/Interpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/api/Interpolation.java new file mode 100644 index 0000000000..494a1f2c00 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/api/Interpolation.java @@ -0,0 +1,11 @@ +package com.jme3.math.interpolations.api; + +public interface Interpolation { + + /** Applies the Interpolation function on a certain value + * + * @param step The step in range of the interpolator + * @return The result of applying the interpolation strategy on the given step + */ + T interpolate(float step); +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/api/NonPrimitiveInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/api/NonPrimitiveInterpolation.java new file mode 100644 index 0000000000..c23c21162a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/api/NonPrimitiveInterpolation.java @@ -0,0 +1,16 @@ +package com.jme3.math.interpolations.api; + +public interface NonPrimitiveInterpolation { + /** Interpolates a non-primitive type by applying {@param step} to some interpolation function + * + * Unlike {@link Interpolation}, this function is in place. Because of type erasure during Java compilation + * it is impossible to construct a class of generic type. To circumvent this, the consumer of this api is required + * to provide an implementation of the generic type of this class, which ironically will be correctly type checked + * by the Java compiler again. E.g. it is not possible to have a NonPrimitiveInterpolation on which you + * call interpolate(step, new Matrix4f()); + * + * @param step The step used by the interpolation function + * @param res The value the resulting interpolated value should be stored in + */ + void interpolate(float step, T res); +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierFloatInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierFloatInterpolation.java new file mode 100644 index 0000000000..ef0e3e2a8f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierFloatInterpolation.java @@ -0,0 +1,39 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.interpolations.api.Interpolation; + +public class BezierFloatInterpolation implements Interpolation { + private final float p0, p1, p2, p3; + + /**Creates an Interpolation on a spline between at least 4 control points following the Bezier equation. + * here is the interpolation matrix + * m = [ -1.0 3.0 -3.0 1.0 ] + * [ 3.0 -6.0 3.0 0.0 ] + * [ -3.0 3.0 0.0 0.0 ] + * [ 1.0 0.0 0.0 0.0 ] + * where T is the curve tension + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + */ + public BezierFloatInterpolation(float p0, float p1, float p2, float p3) { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + /**Calculates the bezier interpolation of step using the state stored in this interpolator + */ + @Override + public Float interpolate(float step) { + float oneMinusValue = 1.0f - step; + float oneMinusValue2 = oneMinusValue * oneMinusValue; + float value2 = step * step; + return p0 * oneMinusValue2 * oneMinusValue + + 3.0f * p1 * step * oneMinusValue2 + + 3.0f * p2 * value2 * oneMinusValue + + p3 * value2 * step; + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierVectorInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierVectorInterpolation.java new file mode 100644 index 0000000000..ca376b0c0f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/BezierVectorInterpolation.java @@ -0,0 +1,50 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.math.VectorNf; +import com.jme3.math.interpolations.api.NonPrimitiveInterpolation; + +public class BezierVectorInterpolation implements NonPrimitiveInterpolation { + private final BezierFloatInterpolation[] inters; + + private final V p0; + private final V p1; + private final V p2; + private final V p3; + + /**Creates an Interpolation on a spline between at least 4 control points following the Bezier equation. + * here is the interpolation matrix + * m = [ -1.0 3.0 -3.0 1.0 ] + * [ 3.0 -6.0 3.0 0.0 ] + * [ -3.0 3.0 0.0 0.0 ] + * [ 1.0 0.0 0.0 0.0 ] + * where T is the curve tension + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + */ + public BezierVectorInterpolation(V p0, V p1, V p2, V p3) { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + + inters = new BezierFloatInterpolation[p0.size()]; + + for (int i = 0; i < p0.size(); i++) { + inters[i] = new BezierFloatInterpolation(p0.get(i), p1.get(i), p2.get(i), p3.get(i)); + } + + } + + /**Calculates the bezier interpolation of step using the state stored in this interpolator + */ + @Override + public void interpolate(float step, V resVector) { + for (int i = 0; i < resVector.size(); i++) { + resVector.setIndex(i, inters[i].interpolate(step)); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomFloatInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomFloatInterpolation.java new file mode 100644 index 0000000000..0527f4b025 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomFloatInterpolation.java @@ -0,0 +1,41 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.interpolations.api.Interpolation; + +public class CatmullRomFloatInterpolation implements Interpolation { + private final float lowerBound; + private final float upperBound; + private float c1, c2, c3, c4; + + + /**Creates an Interpolation on a spline between at least 4 control points following the Catmull-Rom equation. + * here is the interpolation matrix + * m = [ 0.0 1.0 0.0 0.0 ] + * [-T 0.0 T 0.0 ] + * [ 2T T-3 3-2T -T ] + * [-T 2-T T-2 T ] + * where T is the curve tension + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + */ + public CatmullRomFloatInterpolation(float T, float p0, float p1, float p2, float p3) { + this.lowerBound = p1; + this.upperBound = p2; + + c1 = p1; + c2 = -1.0f * T * p0 + T * p2; + c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; + c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; + } + + /* Calculates the CatmullRomFloatInterpolation of step using the state stored in this interpolator + * the result is a value between p1 and p2, value=0 for p1, value=1 for p2 + */ + @Override + public Float interpolate(float value) { + return (float) (((c4 * value + c3) * value + c2) * value + c1); + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomVectorInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomVectorInterpolation.java new file mode 100644 index 0000000000..6187315f0f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/CatmullRomVectorInterpolation.java @@ -0,0 +1,47 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.VectorNf; +import com.jme3.math.interpolations.api.NonPrimitiveInterpolation; + +public class CatmullRomVectorInterpolation implements NonPrimitiveInterpolation { + private final CatmullRomFloatInterpolation[] inters; + + private final V lowerBound; + private final V upperBound; + + /**Creates an Interpolation on a spline between at least 4 control points following the Catmull-Rom equation. + * here is the interpolation matrix + * m = [ 0.0 1.0 0.0 0.0 ] + * [-T 0.0 T 0.0 ] + * [ 2T T-3 3-2T -T ] + * [-T 2-T T-2 T ] + * where T is the curve tension + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + */ + public CatmullRomVectorInterpolation(float T, V p0, V p1, V p2, V p3) { + + this.lowerBound = p1; + this.upperBound = p2; + + inters = new CatmullRomFloatInterpolation[p0.size()]; + + for (int i = 0; i < p0.size(); i++) { + inters[i] = new CatmullRomFloatInterpolation(T, p0.get(i), p1.get(i), p2.get(i), p3.get(i)); + } + + } + + /* Calculates the CatmullRomFloatInterpolation of step using the state stored in this interpolator + * the result is a value between p1 and p2, value=0 for p1, value=1 for p2 + */ + @Override + public void interpolate(float value, V resVector) { + for (int i = 0; i < resVector.size(); i++) { + resVector.setIndex(i, inters[i].interpolate(value)); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearFloatInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearFloatInterpolation.java new file mode 100644 index 0000000000..011d026e64 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearFloatInterpolation.java @@ -0,0 +1,41 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.interpolations.api.Interpolation; + +public class LinearFloatInterpolation implements Interpolation { + + private final float start; + private final float end; + + /** + * Creates a Interpolation instance that applies linear interpolation between + * start and end + * + * @param start The minimum value of this interpolation + * @param end The maximum value of this interpolation + */ + public LinearFloatInterpolation(float start, float end) { + this.start = start; + this.end = end; + } + + /** + * Returns an interpolated value on range of this interpolation. + * Returns start if step <= 0.0 and end if step >= 1.0 + * The result is always within range of start, end + */ + @Override + public Float interpolate(float step) { + if (this.start == this.end) { + return this.start; + } + if (step <= 0f) { + return this.start; + } + if (step >= 1f) { + return this.end; + } + return ((1f - step) * this.start) + (step * this.end); + + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearQuaternionInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearQuaternionInterpolation.java new file mode 100644 index 0000000000..7380890570 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearQuaternionInterpolation.java @@ -0,0 +1,31 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.Quaternion; +import com.jme3.math.interpolations.api.NonPrimitiveInterpolation; + +public class LinearQuaternionInterpolation implements NonPrimitiveInterpolation { + private final Quaternion start; + private final Quaternion end; + + /** + * Creates a Interpolation instance that applies linear interpolation between + * start and end + * + * @param start The minimum value of this interpolation + * @param end The maximum value of this interpolation + */ + public LinearQuaternionInterpolation(Quaternion start, Quaternion end) { + this.start = start; + this.end = end; + } + + /** + * Returns an interpolated value on range of this interpolation. + * Returns start if step <= 0.0 and end if step >= 1.0 + * The result is always within range of start, end + */ + @Override + public void interpolate(float step, Quaternion res) { + res.slerp(this.start, this.end, step); + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearVectorInterpolation.java b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearVectorInterpolation.java new file mode 100644 index 0000000000..8e5b37e7f6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/interpolations/impl/LinearVectorInterpolation.java @@ -0,0 +1,41 @@ +package com.jme3.math.interpolations.impl; + +import com.jme3.math.VectorNf; +import com.jme3.math.interpolations.api.NonPrimitiveInterpolation; + +public class LinearVectorInterpolation implements NonPrimitiveInterpolation { + private final LinearFloatInterpolation[] inters; + private final V lowerBound; + private final V upperBound; + + /** + * Creates a Interpolation instance that applies linear interpolation between + * start and end + * + * @param start The minimum value of this interpolation + * @param end The maximum value of this interpolation + */ + public LinearVectorInterpolation(V start, V end) { + + this.lowerBound = start; + this.upperBound = end; + + inters = new LinearFloatInterpolation[start.size()]; + + for (int i = 0; i < start.size(); i++) { + inters[i] = new LinearFloatInterpolation(start.get(i), end.get(i)); + } + } + + /** + * Returns an interpolated value on range of this interpolation. + * Returns start if step <= 0.0 and end if step >= 1.0 + * The result is always within range of start, end + */ + @Override + public void interpolate(float value, V resVector) { + for (int i = 0; i < resVector.size(); i++) { + resVector.setIndex(i, inters[i].interpolate(value)); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index 7aac5a6895..e17f093e2b 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -353,6 +353,26 @@ public void setForcedMaterial(Material mat) { forcedMaterial = mat; } + /** + * Returns the material that is forced for the next render pass + * @return the forced material + */ + public Material getForcedMaterial() { + return this.forcedMaterial; + } + + public LightList getFilteredLightList() { + return this.filteredLightList; + } + + /** + * Returns the Light Filter used on the lights that will be rendered. + * @return the lightlist + */ + public LightFilter getLightFilter() { + return this.lightFilter; + } + /** * Returns the forced render state previously set with * {@link #setForcedRenderState(com.jme3.material.RenderState) }. @@ -516,53 +536,7 @@ public void updateUniformBindings(List params) { * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry g) { - if (g.isIgnoreTransform()) { - setWorldMatrix(Matrix4f.IDENTITY); - } else { - setWorldMatrix(g.getWorldMatrix()); - } - - // Perform light filtering if we have a light filter. - LightList lightList = g.getWorldLightList(); - - if (lightFilter != null) { - filteredLightList.clear(); - lightFilter.filterLights(g, filteredLightList); - lightList = filteredLightList; - } - - //if forcedTechnique we try to force it for render, - //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null - //else the geom is not rendered - if (forcedTechnique != null) { - if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) { - tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default"; - g.getMaterial().selectTechnique(forcedTechnique, this); - //saving forcedRenderState for future calls - RenderState tmpRs = forcedRenderState; - if (g.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) { - //forcing forced technique renderState - forcedRenderState = g.getMaterial().getActiveTechnique().getDef().getForcedRenderState(); - } - // use geometry's material - g.getMaterial().render(g, lightList, this); - g.getMaterial().selectTechnique(tmpTech, this); - - //restoring forcedRenderState - forcedRenderState = tmpRs; - - //Reverted this part from revision 6197 - //If forcedTechnique does not exists, and forcedMaterial is not set, the geom MUST NOT be rendered - } else if (forcedMaterial != null) { - // use forced material - forcedMaterial.render(g, lightList, this); - } - } else if (forcedMaterial != null) { - // use forced material - forcedMaterial.render(g, lightList, this); - } else { - g.getMaterial().render(g, lightList, this); - } + g.render(this); } /** @@ -604,13 +578,15 @@ public void preloadScene(Spatial scene) { preloadScene(children.get(i)); } } else if (scene instanceof Geometry) { + + // add to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - gm.getMaterial().preload(this); + gm.preload(this); Mesh mesh = gm.getMesh(); if (mesh != null) { for (VertexBuffer vb : mesh.getBufferList().getArray()) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/GeometryRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/GeometryRenderer.java new file mode 100644 index 0000000000..f6cf5190b0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/GeometryRenderer.java @@ -0,0 +1,207 @@ +package com.jme3.renderer.geometryrenderers; + +import com.jme3.light.AmbientLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.material.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.instancing.InstancedGeometry; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.system.SystemListener; +import com.jme3.util.ListMap; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +/** + * A class that contains all information to render a Geometry + */ +public abstract class GeometryRenderer { + protected final Geometry geometry; + protected final RenderManager renderManager; + + protected RenderState mergedRenderState = new RenderState(); + + public GeometryRenderer(Geometry g, RenderManager rm) { + this.geometry = g; + this.renderManager = rm; + } + + /** + * Called by {@link RenderManager} to render the geometry + *

+ * The material is rendered as follows: + *

    + *
  • Determine which technique to use to render the material - + * either what the user selected via + * {@link Geometry#selectTechnique(String, RenderManager)} + * or the first default technique that the renderer supports + * (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering capabilities})
      + *
    • If the technique has been changed since the last frame, then it is notified via + * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, java.util.EnumSet) + * Technique.makeCurrent()}. + * If the technique wants to use a shader to render the model, it should load it at this part - + * the shader should have all the proper defines as declared in the technique definition, + * including those that are bound to material parameters. + * The technique can re-use the shader from the last frame if + * no changes to the defines occurred.
    + *
  • Set the {@link RenderState} to use for rendering. The render states are + * applied in this order (later RenderStates override earlier RenderStates):
      + *
    1. {@link TechniqueDef#getRenderState() Technique Definition's RenderState} + * - i.e. specific renderstate that is required for the shader.
    2. + *
    3. {@link Material#getAdditionalRenderState() Material Instance Additional RenderState} + * - i.e. ad-hoc renderstate set per model
    4. + *
    5. {@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} + * - i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or + * post-processing filter.
    + *
  • If the technique {@link TechniqueDef#isUsingShaders() uses a shader}, then the uniforms of the shader must be updated.
      + *
    • Uniforms bound to material parameters are updated based on the current material parameter values.
    • + *
    • Uniforms bound to world parameters are updated from the RenderManager. + * Internally {@link UniformBindingManager} is used for this task.
    • + *
    • Uniforms bound to textures will cause the texture to be uploaded as necessary. + * The uniform is set to the texture unit where the texture is bound.
    + *
  • If the technique uses a shader, the model is then rendered according + * to the lighting mode specified on the technique definition.
      + *
    • {@link TechniqueDef.LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays + * with the first 4 lights and renders the model once.
    • + *
    • {@link TechniqueDef.LightMode#MultiPass multi pass light mode} light mode renders the model multiple times, + * for the first light it is rendered opaque, on subsequent lights it is + * rendered with {@link RenderState.BlendMode#AlphaAdditive alpha-additive} blending and depth writing disabled.
    • + *
    + *
  • For techniques that do not use shaders, + * fixed function OpenGL is used to render the model (see {@link GL1Renderer} interface):
      + *
    • OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is updated.
    • + *
    • The texture set on the material is uploaded and bound. + * Currently only 1 texture is supported for fixed function techniques.
    • + *
    • If the technique uses lighting, then OpenGL lighting state is updated + * based on the light list on the geometry, otherwise OpenGL lighting is disabled.
    • + *
    • The mesh is uploaded and rendered.
    • + *
    + *
+ */ + public void render() { + Technique technique = this.geometry.getMaterial().getActiveTechnique(); + Renderer renderer = this.renderManager.getRenderer(); + RenderState rs = this.geometry.getMaterial().getAdditionalRenderState(); + Shader shader = technique.getShader(); + + if (renderManager.getForcedRenderState() != null) { + renderer.applyRenderState(renderManager.getForcedRenderState()); + } else { + TechniqueDef techDef = technique.getDef(); + + if (techDef.getRenderState() != null) { + renderer.applyRenderState(techDef.getRenderState().copyMergedTo(rs, mergedRenderState)); + } else { + renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(rs, mergedRenderState)); + } + } + + + clearUniformsSetByCurrent(shader); + renderManager.updateUniformBindings(this.geometry.getMaterial().getActiveTechnique().getWorldBindUniforms()); + + ListMap paramsMap = geometry.getMaterial().getParamsMap(); + + for (int i = 0; i < paramsMap.size(); i++) { + MatParam param = paramsMap.getValue(i); + param.apply(renderer, technique); + } + + this.renderForLighting(); + } + + /** + * Method that renders this geometry specifically for the lighting chosen + * + * So, if a {@link Technique} demands MultiPassLighting, the {@link Geometry} will select a + * {@link MultiPassGeometryRenderer} to render itself, all specific code for multi pass rendering is then in + * this function, which gets called from {@link GeometryRenderer#render()} + */ + public abstract void renderForLighting(); + + /** + * Convenienece function that renders a mesh using the currently set shader + * + * If ths geomery is instanced, it will render multiple instances of this mesh on the different locations set + * by the instancing + */ + + protected void renderMeshFromGeometry() { + Mesh mesh = this.geometry.getMesh(); + int lodLevel = this.geometry.getLodLevel(); + + Renderer renderer = this.renderManager.getRenderer(); + + + if (this.geometry instanceof InstancedGeometry) { + InstancedGeometry instGeom = (InstancedGeometry) this.geometry; + int numInstances = instGeom.getActualNumInstances(); + if (numInstances == 0) { + return; + } + if (this.renderManager.getRenderer().getCaps().contains(Caps.MeshInstancing)) { + renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData()); + } else { + throw new RendererException("Mesh instancing is not supported by the video hardware"); + } + } else { + renderer.renderMesh(mesh, lodLevel, 1, null); + } + } + + protected void resetUniformsNotSetByCurrent(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + if (!u.isSetByCurrentMaterial()) { + if (u.getName().charAt(0) != 'g') { + // Don't reset world globals! + // The benefits gained from this are very minimal + // and cause lots of matrix -> FloatBuffer conversions. + u.clearValue(); + } + } + } + } + + protected void clearUniformsSetByCurrent(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + u.clearSetByCurrentMaterial(); + } + } + + protected ColorRGBA getAmbientColor(boolean removeLights) { + ColorRGBA ambientLightColor = new ColorRGBA(0f, 0f, 0f, 1f); + LightList lightList = renderManager.getFilteredLightList(); + + for (int j = 0; j < lightList.size(); j++) { + Light l = lightList.get(j); + if (l instanceof AmbientLight) { + ambientLightColor.addLocal(l.getColor()); + if(removeLights){ + lightList.remove(l); + } + } + } + ambientLightColor.a = 1.0f; + + return ambientLightColor; + } + + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/MultiPassGeometryRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/MultiPassGeometryRenderer.java new file mode 100644 index 0000000000..87bfde2ecb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/MultiPassGeometryRenderer.java @@ -0,0 +1,146 @@ +package com.jme3.renderer.geometryrenderers; + +import com.jme3.light.*; +import com.jme3.material.MatParam; +import com.jme3.material.RenderState; +import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.util.TempVars; + +import java.util.logging.Logger; + +public class MultiPassGeometryRenderer extends GeometryRenderer { + + private static final Logger logger = Logger.getLogger(MultiPassGeometryRenderer.class.getName()); + + public MultiPassGeometryRenderer(Geometry g,RenderManager rm) { + super(g, rm); + } + + public void renderForLighting() { + Technique technique = this.geometry.getMaterial().getActiveTechnique(); + Shader shader = technique.getShader(); + + resetUniformsNotSetByCurrent(shader); + renderMultipassLighting(shader); + } + + protected void renderMultipassLighting(Shader shader) { + Renderer r = renderManager.getRenderer(); + Uniform lightDir = shader.getUniform("g_LightDirection"); + Uniform lightColor = shader.getUniform("g_LightColor"); + Uniform lightPos = shader.getUniform("g_LightPosition"); + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + boolean isFirstLight = true; + boolean isSecondLight = false; + + LightList ll = this.renderManager.getFilteredLightList(); + + + for (int i = 0; i < ll.size(); i++) { + Light l = ll.get(i); + if (l instanceof AmbientLight) { + continue; + } + + if (isFirstLight) { + // set ambient color for first light only + ambientColor.setValue(VarType.Vector4, this.getAmbientColor(false)); + isFirstLight = false; + isSecondLight = true; + } else if (isSecondLight) { + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + // apply additive blending for 2nd and future lights + RenderState additiveLight = new RenderState(); + additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive); + additiveLight.setDepthWrite(false); + + r.applyRenderState(additiveLight); + isSecondLight = false; + } + + TempVars vars = TempVars.get(); + Quaternion tmpLightDirection = vars.quat1; + Quaternion tmpLightPosition = vars.quat2; + ColorRGBA tmpLightColor = vars.color; + Vector4f tmpVec = vars.vect4f1; + + ColorRGBA color = l.getColor(); + tmpLightColor.set(color); + tmpLightColor.a = l.getType().getId(); + lightColor.setValue(VarType.Vector4, tmpLightColor); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //FIXME : there is an inconstency here due to backward + //compatibility of the lighting shader. + //The directional light direction is passed in the + //LightPosition uniform. The lighting shader needs to be + //reworked though in order to fix this. + tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + + tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + + tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + + //We transform the spot direction in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happens now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); + renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); + + lightDir.setValue(VarType.Vector4, tmpLightDirection); + + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + vars.release(); + r.setShader(shader); + renderMeshFromGeometry(); + } + + if (isFirstLight) { + // Either there are no lights at all, or only ambient lights. + // Render a dummy "normal light" so we can see the ambient color. + + ambientColor.setValue(VarType.Vector4, this.getAmbientColor(false)); + lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); + lightPos.setValue(VarType.Vector4, new Quaternion(0, -1, 0, -1)); + r.setShader(shader); + renderMeshFromGeometry(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/NoLightGeometryRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/NoLightGeometryRenderer.java new file mode 100644 index 0000000000..903a5855c8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/NoLightGeometryRenderer.java @@ -0,0 +1,27 @@ +package com.jme3.renderer.geometryrenderers; + +import com.jme3.material.MatParam; +import com.jme3.material.Technique; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.Shader; +import com.jme3.util.ListMap; + +public class NoLightGeometryRenderer extends GeometryRenderer { + public NoLightGeometryRenderer(Geometry g, RenderManager rm) { + super(g, rm); + } + + @Override + public void renderForLighting() { + Technique technique = this.geometry.getMaterial().getActiveTechnique(); + Shader shader = technique.getShader(); + Renderer renderer = this.renderManager.getRenderer(); + + resetUniformsNotSetByCurrent(shader); + renderer.setShader(shader); + + renderMeshFromGeometry(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/SinglePassGeometryRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/SinglePassGeometryRenderer.java new file mode 100644 index 0000000000..ba360a0253 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/geometryrenderers/SinglePassGeometryRenderer.java @@ -0,0 +1,178 @@ +package com.jme3.renderer.geometryrenderers; + +import com.jme3.light.*; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.util.TempVars; + +import java.util.logging.Logger; + +public class SinglePassGeometryRenderer extends GeometryRenderer { + + private static final Logger logger = Logger.getLogger(SinglePassGeometryRenderer.class.getName()); + + public SinglePassGeometryRenderer(Geometry g, RenderManager rm) { + super(g, rm); + } + + @Override + public void renderForLighting() { + + int nbRenderedLights = 0; + + Shader shader = this.geometry.getMaterial().getActiveTechnique().getShader(); + Renderer renderer = this.renderManager.getRenderer(); + + LightList ll = this.renderManager.getFilteredLightList(); + + resetUniformsNotSetByCurrent(shader); + if (ll.size() == 0) { + updateLightListUniforms(this.renderManager.getSinglePassLightBatchSize(), 0); + renderer.setShader(shader); + renderMeshFromGeometry(); + } else { + while (nbRenderedLights < ll.size()) { + nbRenderedLights = updateLightListUniforms(this.renderManager.getSinglePassLightBatchSize(), nbRenderedLights); + renderer.setShader(shader); + renderMeshFromGeometry(); + } + } + + resetUniformsNotSetByCurrent(shader); + renderer.setShader(shader); + + renderMeshFromGeometry(); + + } + + /** + * Uploads the lights in the light list as two uniform arrays.

* + *

+ * uniform vec4 g_LightColor[numLights];
// + * g_LightColor.rgb is the diffuse/specular color of the light.
// + * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// + * 2 = Spot.

+ * uniform vec4 g_LightPosition[numLights];
// + * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
// + * g_LightPosition.w is the inverse radius (1/r) of the light (for + * attenuation)

+ */ + /* Test */ public int updateLightListUniforms(int numLights, int startIndex) { + + Shader shader = this.geometry.getMaterial().getActiveTechnique().getShader(); + LightList lightList = this.renderManager.getFilteredLightList(); + + if (numLights == 0) { // this shader does not do lighting, ignore. + return 0; + } + + Uniform lightData = shader.getUniform("g_LightData"); + lightData.setVector4Length(numLights * 3);//8 lights * max 3 + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + + + if (startIndex != 0) { + // apply additive blending for 2nd and future passes + RenderState additiveLight = new RenderState(); + additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive); + additiveLight.setDepthWrite(false); + + this.renderManager.getRenderer().applyRenderState(additiveLight); + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + }else{ + int oldAmountOfLights = lightList.size(); + ambientColor.setValue(VarType.Vector4, this.getAmbientColor(true)); + } + + int lightDataIndex = 0; + TempVars vars = TempVars.get(); + Vector4f tmpVec = vars.vect4f1; + int curIndex; + int endIndex = numLights + startIndex; + for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { + Light l = lightList.get(curIndex); + if(l.getType() == Light.Type.Ambient){ + endIndex++; + continue; + } + ColorRGBA color = l.getColor(); + //Color + lightData.setVector4InArray(color.getRed(), + color.getGreen(), + color.getBlue(), + l.getType().getId(), + lightDataIndex); + lightDataIndex++; + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //Data directly sent in view space to avoid a matrix mult for each pixel + tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); + this.renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); +// tmpVec.divideLocal(tmpVec.w); +// tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); + this.renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + //tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); + this.renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + // tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); + lightDataIndex++; + + //We transform the spot direction in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happens now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); + this.renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); + lightDataIndex++; + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + } + vars.release(); + //Padding of unsued buffer space + while(lightDataIndex < numLights * 3) { + lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); + lightDataIndex++; + } + return curIndex; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 4c394101ab..afba6f60e0 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -39,13 +39,21 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; -import com.jme3.material.Material; +import com.jme3.light.LightList; +import com.jme3.material.*; import com.jme3.math.Matrix4f; import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.geometryrenderers.GeometryRenderer; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.geometryrenderers.MultiPassGeometryRenderer; +import com.jme3.renderer.geometryrenderers.NoLightGeometryRenderer; +import com.jme3.renderer.geometryrenderers.SinglePassGeometryRenderer; import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.TempVars; import java.io.IOException; -import java.util.Queue; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -83,7 +91,16 @@ public class Geometry extends Spatial { * the {@link GeometryGroupNode}. */ protected int startIndex = -1; - + + /** + * The renderer that will render this geometry object + */ + private GeometryRenderer renderer; + + private Material restorableMaterial; + private String restorableTechniqueName; + private RenderState restorableRenderState; + /** * Serialization only. Do not use. */ @@ -584,4 +601,222 @@ public void read(JmeImporter im) throws IOException { } } } -} + + /** + * Preloads this material for the given render manager. + *

+ * Preloading the material can ensure that when the material is first + * used for rendering, there won't be any delay since the material has + * been already been setup for rendering. + * + * @param rm The render manager to preload for + */ + public void preload(RenderManager rm) { + + + this.autoSelectRenderer(rm); + + Renderer r = rm.getRenderer(); + + Technique technique = material.getActiveTechnique(); + TechniqueDef techDef = technique.getDef(); + + Collection params = material.getParamsMap().values(); + for (MatParam param : params) { + param.apply(r, technique); + } + + r.setShader(technique.getShader()); + } + + public void setGeometryRenderer(GeometryRenderer renderer) { + this.renderer = renderer; + } + + public GeometryRenderer getGeometryRenderer() { + return renderer; + } + + public void render(RenderManager rm) { + // Firstly lets set the world transforms + if (this.isIgnoreTransform()) { + rm.setWorldMatrix(Matrix4f.IDENTITY); + } else { + rm.setWorldMatrix(this.getWorldMatrix()); + } + + if (rm.getLightFilter() != null) { + rm.getFilteredLightList().clear(); + rm.getLightFilter().filterLights(this, rm.getFilteredLightList()); + } + + //if forcedTechnique we try to force it for render, + //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null + //else the geom is not rendered + + String tmpTech; + + //if forcedTechnique we try to force it for render, + //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null + //else the geom is not rendered + if (rm.getForcedTechnique() != null) { + if (this.getMaterial().getMaterialDef().getTechniqueDef(rm.getForcedTechnique()) != null) { + tmpTech = this.getMaterial().getActiveTechnique() != null ? this.getMaterial().getActiveTechnique().getDef().getName() : "Default"; + this.selectTechnique(rm.getForcedTechnique(), rm); + //saving forcedRenderState for future calls + RenderState tmpRs = rm.getForcedRenderState(); + if (this.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) { + //forcing forced technique renderState + rm.setForcedRenderState(this.getMaterial().getActiveTechnique().getDef().getForcedRenderState()); + } + // use geometry's material + this.getRendererForTechnique(rm).render(); + this.selectTechnique(tmpTech, rm); + + //restoring forcedRenderState + rm.setForcedRenderState(tmpRs); + + //Reverted this part from revision 6197 + //If forcedTechnique does not exists, and forcedMaterial is not set, the geom MUST NOT be rendered + } else if (rm.getForcedMaterial() != null) { + // use forced material + Material oldMaterial = this.getMaterial(); + + this.setMaterial(rm.getForcedMaterial()); + this.getRendererForTechnique(rm).render(); + + this.setMaterial(oldMaterial); + } + } else if (rm.getForcedMaterial() != null) { + // use forced material + Material oldMaterial = this.getMaterial(); + + this.setMaterial(rm.getForcedMaterial()); + this.getRendererForTechnique(rm).render(); + + this.setMaterial(oldMaterial); + } else { + this.autoSelectRenderer(rm); + this.getRendererForTechnique(rm).render(); + } + } + + public void autoSelectRenderer(RenderManager rm) { + if (this.getMaterial().getActiveTechnique() == null) { + selectTechnique("Default", rm); + } else { + this.material.getActiveTechnique().makeCurrent(this.material.getMaterialDef().getAssetManager(), false, rm.getRenderer().getCaps(), rm); + this.setGeometryRenderer(this.getRendererForTechnique(rm)); + } + } + + private GeometryRenderer getRendererForTechnique(RenderManager rm) { + + switch (material.getActiveTechnique().getDef().getLightMode()) { + case Disable: + return new NoLightGeometryRenderer(this, rm); + case SinglePass: + return new SinglePassGeometryRenderer(this, rm); + case MultiPass: + return new MultiPassGeometryRenderer(this, rm); + default: + throw new IllegalArgumentException("OpenGL1 is not supported"); + } + } + + /** + * Select the technique to use for rendering this Geometry. + *

+ * If name is "Default", then one of the + * {@link MaterialDef#getDefaultTechniques() default techniques} + * on the material will be selected. Otherwise, the named technique + * will be found in the material definition. + *

+ * Any candidate technique for selection (either default or named) + * must be verified to be compatible with the system, for that, the + * renderManager is queried for capabilities. + * + * @param name The name of the technique to select, pass "Default" to + * select one of the default techniques. + * @param renderManager The {@link RenderManager render manager} + * to query for capabilities. + * + * @throws IllegalArgumentException If "Default" is passed and no default + * techniques are available on the material definition, or if a name + * is passed but there's no technique by that name. + * @throws UnsupportedOperationException If no candidate technique supports + * the system capabilities. + */ + public void selectTechnique(String name, RenderManager renderManager) { + Map techniques = this.material.getTechiques(); + Technique tech = techniques.get(name); + MaterialDef def = this.material.getMaterialDef(); + + Technique currentlyUsedTechnique = this.material.getActiveTechnique(); + + // When choosing technique, we choose one that + // supports all the caps. + EnumSet rendererCaps = renderManager.getRenderer().getCaps(); + if (tech == null) { + + if (name.equals("Default")) { + List techDefs = def.getDefaultTechniques(); + if (techDefs == null || techDefs.isEmpty()) { + throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'"); + } + + TechniqueDef lastTech = null; + for (TechniqueDef techDef : techDefs) { + if (rendererCaps.containsAll(techDef.getRequiredCaps())) { + // use the first one that supports all the caps + tech = new Technique(this.material, techDef); + techniques.put(name, tech); + if(tech.getDef().getLightMode() == renderManager.getPreferredLightMode() || + tech.getDef().getLightMode() == TechniqueDef.LightMode.Disable){ + break; + } + } + lastTech = techDef; + } + if (tech == null) { + throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n" + + " is supported by the video hardware. The caps " + + lastTech.getRequiredCaps() + " are required."); + } + + } else { + // create "special" technique instance + TechniqueDef techDef = def.getTechniqueDef(name); + if (techDef == null) { + throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name); + } + + if (!rendererCaps.containsAll(techDef.getRequiredCaps())) { + throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n" + + "requires caps " + techDef.getRequiredCaps() + " which are not " + + "supported by the video renderer"); + } + + tech = new Technique(this.material, techDef); + techniques.put(name, tech); + } + } else if (currentlyUsedTechnique == tech) { + // attempting to switch to an already + // active technique. + if (this.getGeometryRenderer() == null) { + this.setGeometryRenderer(this.getRendererForTechnique(renderManager)); + } + + return; + } + + this.material.setActiveTechnique(tech); + tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager); + + this.setGeometryRenderer(this.getRendererForTechnique(renderManager)); + + // shader was changed + // TODO: sortingId = -1; + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties index 98168a14e1..098a69eb5a 100644 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ b/jme3-core/src/main/resources/com/jme3/system/version.properties @@ -1,11 +1,12 @@ # THIS IS AN AUTO-GENERATED FILE.. # DO NOT MODIFY! -build.date=1900-01-01 -git.revision=0 -git.branch=unknown -git.hash= -git.hash.short= -git.tag= -name.full=jMonkeyEngine 3.1.0-UNKNOWN +build.date=2016-04-10 +git.revision=5499 +git.branch=master +git.hash=8e0bda264fdecc31b8b009d84dabbaacc46c345b +git.hash.short=8e0bda2 +git.tag=null +name.full=jMonkeyEngine 3.1-5499 +version.full=3.1-5499 version.number=3.1.0 version.tag=SNAPSHOT \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/ShaderCharacterizationTest.java b/jme3-core/src/test/java/com/jme3/ShaderCharacterizationTest.java new file mode 100644 index 0000000000..18e5aea037 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/ShaderCharacterizationTest.java @@ -0,0 +1,56 @@ +package com.jme3; + +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsSame.theInstance; + +public class ShaderCharacterizationTest { + private Shader shader; + + @Before + public void init() { + this.shader = new Shader(); + + // When calling this.shader.getUniform() this has to be called first + // we can therefore call it a precondition to getUniform + this.shader.initialize(); + } + + @Test(expected = java.lang.AssertionError.class) + public void getUniformFirstCharacterizationTest() { + // Lets first see what happens when we call the function with an empty string + // this produces an AssertionError, we cannot call this method with a random string + this.shader.getUniform("blaat"); + } + + @Test + public void getUniformSecondCharacterizationTest() { + // Okay, from the sourcecode we have noted that if you give a string not starting with + // m_ or g_, it will assert + + // we expect that if we give the function a uniform that does not exist, that it will create one, + // therefore we will assert that the length of the uniform map will increase + + int numOfUniforms = this.shader.getUniformMap().size(); + this.shader.getUniform("m_thisOneIsObviouslyNew"); + + assertThat(this.shader.getUniformMap().size(), is(numOfUniforms + 1)); + + // We can therefore assume this to be true + } + + @Test + public void getUniformThirdCharacterizationTest() { + // If we then call getUniform with a key we know that exist, we can assume to get the same item back we put into + // that map + Uniform toBeCompared = this.shader.getUniform("m_thisOneIsObviouslyNew"); + + assertThat(this.shader.getUniform("m_thisOneIsObviouslyNew"), is(theInstance(toBeCompared))); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/effect/ParticleEmitterTest.java b/jme3-core/src/test/java/com/jme3/effect/ParticleEmitterTest.java new file mode 100644 index 0000000000..191acf567d --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/effect/ParticleEmitterTest.java @@ -0,0 +1,45 @@ +package com.jme3.effect; + +import com.jme3.math.Vector3f; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertThat; + +public class ParticleEmitterTest { + private ParticleEmitter pe; + private Particle p; + + @Before + public void init () { + pe = new ParticleEmitter(); + p = new Particle(); + + p.startlife = 10.0f; + p.life = 10.0f; + } + + @Test + public void testUpdateParticle() { + // we are only interested in the size parameter of our particle + // the rest we will not touch + + pe.updateParticle(p, 0.0f, new Vector3f(), new Vector3f()); + // at the very start of the anim, the particle should be its start size + assertThat(p.size, is(0.2f)); + // when some time has passed the particle should have a different size + p.life -= 2.0f; + pe.updateParticle(p, 2.0f, new Vector3f(), new Vector3f()); + assertThat(p.size, is(0.56f)); + // updating start-size should change the value + pe.setStartSize(0.7f); + // please note that there is no change in life here! + pe.updateParticle(p, 0.0f, new Vector3f(), new Vector3f()); + assertThat(p.size, is(0.96000004f)); + // same goes for endSize + pe.setEndSize(3.0f); + pe.updateParticle(p, 0.0f, new Vector3f(), new Vector3f()); + assertThat(p.size, is(1.1600001f)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/material/MaterialUpdateLightListUniformsCharacterizationTest.java b/jme3-core/src/test/java/com/jme3/material/MaterialUpdateLightListUniformsCharacterizationTest.java new file mode 100644 index 0000000000..c3725385f8 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/material/MaterialUpdateLightListUniformsCharacterizationTest.java @@ -0,0 +1,94 @@ +package com.jme3.material; + +import com.jme3.light.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.geometryrenderers.MultiPassGeometryRenderer; +import com.jme3.renderer.geometryrenderers.SinglePassGeometryRenderer; +import com.jme3.renderer.opengl.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.shader.Shader; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.*; + +public class MaterialUpdateLightListUniformsCharacterizationTest { + + @Test + public void testUpdateLightListUniforms() { + // We found this method in the detection of SOLID violations, what we want to do is refactor the switch/case + + // We want to know how this method works, therefore we will try to list calls on mock objects and see how data + // flows between the various parameters feeded to this material + + Material mat = new Material(); + + Shader shader = new Shader(); // this cannot be mocked as it is final + Geometry geom = mock(Geometry.class, withSettings().verboseLogging()); + + RenderManager rm = mock(RenderManager.class, withSettings().verboseLogging()); + + + SinglePassGeometryRenderer geometryRenderer = new SinglePassGeometryRenderer(geom, rm); + + // We found that if you give a RenderManager with no Renderer, you'll get a NullPointer, so we have to mock that method + Renderer r = mock(Renderer.class, withSettings().verboseLogging()); + when(rm.getRenderer()).thenReturn(r); + // We later found out that there is a deep dependency on RenderManager's CurrentCamera's ViewMatrix + Camera currentCamera = mock(Camera.class, withSettings().verboseLogging()); + when(rm.getCurrentCamera()).thenReturn(currentCamera); + when(currentCamera.getViewMatrix()).thenReturn(new Matrix4f()); + + + LightList ll = new LightList(mock(Spatial.class, withSettings().verboseLogging()));// this cannot be mocked as it is final + + // We apparently need a mapping between LightType and a concrete instance + // This is problematic to us, as this means there is a relationship between a magic type enum and a class + // That enum is inflexible + + // The different types of lights are distinct enough to not be the same class, to test them all we will + // mock them individually + AmbientLight al = mock(AmbientLight.class, withSettings().verboseLogging()); + when(al.getColor()).thenReturn(new ColorRGBA(0,0,0,1.0f)); // A light has to return its color + when(al.getType()).thenReturn(Light.Type.Ambient); // A light has to return its type, this is part of the problem + ll.add(al); + + DirectionalLight dl = mock(DirectionalLight.class, withSettings().verboseLogging()); + when(dl.getColor()).thenReturn(new ColorRGBA(0,0,0,1.0f)); + when(dl.getType()).thenReturn(Light.Type.Directional); + when(dl.getDirection()).thenReturn(new Vector3f(0.0f, 0.0f, 0.0f)); // A directional light needs a direction + ll.add(dl); + + PointLight pl = mock(PointLight.class, withSettings().verboseLogging()); + when(pl.getColor()).thenReturn(new ColorRGBA(0,0,0,1.0f)); + when(pl.getType()).thenReturn(Light.Type.Point); + when(pl.getPosition()).thenReturn(new Vector3f(0.0f, 0.0f, 0.0f)); // A point light needs a position + ll.add(pl); + + SpotLight sl = mock(SpotLight.class, withSettings().verboseLogging()); + when(sl.getColor()).thenReturn(new ColorRGBA(0,0,0,1.0f)); + when(sl.getType()).thenReturn(Light.Type.Spot); + when(sl.getPosition()).thenReturn(new Vector3f(0.0f, 0.0f, 0.0f)); // A spot light needs a position + when(sl.getDirection()).thenReturn(new Vector3f(0.0f, 0.0f, 0.0f)); // A spot light also needs a direction + ll.add(sl); + + // With the other created characterization test we found out that shaders initialize is to be called + shader.initialize(); + + // This method changes its shader parameter + System.out.println(shader.getUniformMap()); + + + System.out.println(shader.getUniformMap()); + + //geometryRenderer.updateLightListUniforms(6, 0); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java index a74390d422..863f0718e1 100644 --- a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java +++ b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java @@ -31,8 +31,12 @@ */ package com.jme3.math; +import org.hamcrest.CoreMatchers; import org.junit.Test; +import static org.junit.Assert.assertEquals; + + /** * Verifies that algorithms in {@link FastMath} are working correctly. * @@ -56,4 +60,172 @@ public void testNearestPowerOfTwo() { assert nextPowerOf2 == nearestPowerOfTwoSlow(i); } } + + @Test + public void testLinearInterpolate() { + // Some good weather situations + assertEquals(60.0f, FastMath.interpolateLinear(0.1f, 50.0f, 150.0f), 0.001f); + assertEquals(100.0f, FastMath.interpolateLinear(0.5f, 50.0f, 150.0f), 0.001f); + assertEquals(70.0f, FastMath.interpolateLinear(0.2f, 50.0f, 150.0f), 0.001f); + + // Some bad weather situations + // On the border of the range + assertEquals(50.0f, FastMath.interpolateLinear(0.0f, 50.0f, 150.0f), 0.001f); + // Other border + assertEquals(150.0f, FastMath.interpolateLinear(1.0f, 50.0f, 150.0f), 0.001f); + // Way below the lower bound + assertEquals(50.0f, FastMath.interpolateLinear(-100.0f, 50.0f, 150.0f), 0.001f); + // Way above the higher bound + assertEquals(150.0f, FastMath.interpolateLinear(200.0f, 50.0f, 150.0f), 0.001f); + } + + @Test + public void testVectorLinearInterpolate() { + //Vectors to be tested + Vector3f v1s = new Vector3f(10.0f, 30.0f, 25.0f); + Vector3f v1e = new Vector3f(15.0f, 42.0f, 27.5f); + Vector3f v1eneg = new Vector3f(-15.0f, -42.0f, -27.5f); + + //Resultvectors + Vector3f v1r1 = new Vector3f(10.5f, 31.2f, 25.25f); + Vector3f v1r2 = new Vector3f(12.5f, 36.0f, 26.25f); + Vector3f v1r3 = new Vector3f(15.0f, 42.0f, 27.5f); + Vector3f v1rneg = new Vector3f(-2.5f, -6.0f, -1.25f); + + //Test different scales with correct vectors + assertEquals(v1r1, FastMath.interpolateLinear(0.1f, v1s, v1e)); + assertEquals(v1r2, FastMath.interpolateLinear(0.5f, v1s, v1e)); + assertEquals(v1r3, FastMath.interpolateLinear(1.0f, v1s, v1e)); + + //Test negative end vector which is also smaller than the start vector + assertEquals(v1rneg, FastMath.interpolateLinear(0.5f, v1s, v1eneg)); + + //Test out of bounds scales + assertEquals(v1s, FastMath.interpolateLinear(-0.5f, v1s, v1e)); + assertEquals(v1e, FastMath.interpolateLinear(1.5f, v1s, v1e)); + } + + @Test + public void interpolateCatmullRom() { + float[] expected = new float[]{5.147461f, 5.3921876f, 5.7001953f, 6.0375f, 6.370117f, 6.6640625f, 6.8853517f}; + + for (int i = 0; i < 7; i++) { + float value = ((float) i + 1) / 8; + + assertEquals(expected[i], FastMath.interpolateCatmullRom(value, 0.1f, 0f, 5f, 7f, 9f), 0.001f); + } + + //Test boundaries + assertEquals(5.0f, FastMath.interpolateCatmullRom(0.0f, 0.1f, 0f, 5f, 7f, 9f), 0.001f); + assertEquals(7.0f, FastMath.interpolateCatmullRom(1.0f, 0.1f, 0f, 5f, 7f, 9f), 0.001f); + } + + @Test + public void interpolateVectorCatmullRom() { + Vector3f[] expected = new Vector3f[]{ + new Vector3f(1.1719726f, 3.875293f , 0.31552735f), + new Vector3f(1.5320313f, 6.899219f , -0.19140625f), + new Vector3f(2.0151367f, 11.006349f, -0.91357434f), + new Vector3f(2.5562499f, 15.631251f, -1.7437499f), + new Vector3f(3.0903318f, 20.208498f, -2.574707f), + new Vector3f(3.5523434f, 24.172657f, -3.2992187f), + new Vector3f(3.8772454f, 26.958302f, -3.810058f) + }; + + Vector3f p0 = new Vector3f(-0.5f, -1.0f, -5.0f); + Vector3f p1 = new Vector3f(1.0f, 2.5f, 0.5f); + Vector3f p2 = new Vector3f(4.0f, 28.0f, -4.0f); + Vector3f p3 = new Vector3f(1.0f, 1.0f, 1.0f); + + for (int i = 0; i < 7; i++) { + float value = ((float) i + 1) / 8; + + Vector3f currentResult = FastMath.interpolateCatmullRom(value, 0.1f, p0, p1, p2, p3); + + assertEquals(expected[i].getX(), currentResult.getX(), 0.001f); + assertEquals(expected[i].getY(), currentResult.getY(), 0.001f); + assertEquals(expected[i].getZ(), currentResult.getZ(), 0.001f); + } + + //Test boundaries + Vector3f lowerBoundaryResult = FastMath.interpolateCatmullRom(0.0f, 0.1f, p0, p1, p2, p3); + assertEquals(p1.getX(), lowerBoundaryResult.getX(), 0.001f); + assertEquals(p1.getY(), lowerBoundaryResult.getY(), 0.001f); + assertEquals(p1.getZ(), lowerBoundaryResult.getZ(), 0.001f); + + Vector3f higherBoundaryResult = FastMath.interpolateCatmullRom(1.0f, 0.1f, p0, p1, p2, p3); + assertEquals(p2.getX(), higherBoundaryResult.getX(), 0.001f); + assertEquals(p2.getY(), higherBoundaryResult.getY(), 0.001f); + assertEquals(p2.getZ(), higherBoundaryResult.getZ(), 0.001f); + } + + @Test + public void interpolateBezier() { + float[] expected = new float[]{1.7402344f, 3.234375f, 4.517578f, 5.625f, 6.591797f, 7.453125f, 8.244141f}; + + for (int i = 0; i < 7; i++) { + float value = ((float) i + 1) / 8; + + assertEquals(expected[i], FastMath.interpolateBezier(value, 0f, 5f, 7f, 9f), 0.001f); + } + + assertEquals(0.0f, FastMath.interpolateBezier(0.0f, 0f, 5f, 7f, 9f), 0.001f); + assertEquals(9.0f, FastMath.interpolateBezier(1.0f, 0f, 5f, 7f, 9f), 0.001f); + } + + @Test + public void testGetBezierP1toP2Length() { + Vector3f p0 = new Vector3f(10f, 10f, 10f); + Vector3f p1 = new Vector3f(12f, 14f, 11f); + Vector3f p2 = new Vector3f(15f, 15f, 17f); + Vector3f p3 = new Vector3f(17f, 20f, 19f); + + assertEquals(FastMath.getBezierP1toP2Length(p0, p1, p2, p3), 15.394257f, 0.001f); + + p0 = new Vector3f(12f, 10f, 15f); + p1 = new Vector3f(15f, 13f, 17f); + p2 = new Vector3f(16f, 17f, 19f); + p3 = new Vector3f(16f, 20f, 19f); + + assertEquals(FastMath.getBezierP1toP2Length(p0, p1, p2, p3), 11.833087f, 0.001f); + } + + @Test + public void interpolateVectorBezier() { + Vector3f[] expected = new Vector3f[]{ + new Vector3f(0.11816406f, 1.1982422f , -3.368164f), + new Vector3f(0.7890625f , 4.5859375f , -2.4453125f), + new Vector3f(1.4248047f , 8.290039f , -2.0029297f), + new Vector3f(1.9375f , 11.4375f , -1.8125f), + new Vector3f(2.2392578f , 13.155273f , -1.6455078f), + new Vector3f(2.2421875f , 12.5703125f , -1.2734375f), + new Vector3f(1.8583984f , 8.80957f , -0.46777344f) + }; + + Vector3f p0 = new Vector3f(-0.5f, -1.0f, -5.0f); + Vector3f p1 = new Vector3f(1.0f, 2.5f, 0.5f); + Vector3f p2 = new Vector3f(4.0f, 28.0f, -4.0f); + Vector3f p3 = new Vector3f(1.0f, 1.0f, 1.0f); + + for (int i = 0; i < 7; i++) { + float value = ((float) i + 1) / 8; + + Vector3f currentResult = FastMath.interpolateBezier(value, p0, p1, p2, p3); + + assertEquals(expected[i].getX(), currentResult.getX(), 0.001f); + assertEquals(expected[i].getY(), currentResult.getY(), 0.001f); + assertEquals(expected[i].getZ(), currentResult.getZ(), 0.001f); + } + + //Test boundaries + Vector3f lowerBoundaryResult = FastMath.interpolateBezier(0.0f, p0, p1, p2, p3); + assertEquals(p0.getX(), lowerBoundaryResult.getX(), 0.001f); + assertEquals(p0.getY(), lowerBoundaryResult.getY(), 0.001f); + assertEquals(p0.getZ(), lowerBoundaryResult.getZ(), 0.001f); + + Vector3f higherBoundaryResult = FastMath.interpolateBezier(1.0f, p0, p1, p2, p3); + assertEquals(p3.getX(), higherBoundaryResult.getX(), 0.001f); + assertEquals(p3.getY(), higherBoundaryResult.getY(), 0.001f); + assertEquals(p3.getZ(), higherBoundaryResult.getZ(), 0.001f); + } } diff --git a/jme3-core/src/test/java/com/jme3/renderer/RendererRegressionTest.java b/jme3-core/src/test/java/com/jme3/renderer/RendererRegressionTest.java new file mode 100644 index 0000000000..c1280421c5 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/renderer/RendererRegressionTest.java @@ -0,0 +1,115 @@ +package com.jme3.renderer; + +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class RendererRegressionTest { + List blockedBoxes = Arrays.asList(new Box(0, 460, 200, 20)); + + @Test + public void testConeVSFustrum() throws IOException { + assertTrue(isSameImage("imageData/TestConeVSFrustum-pre.png", "imageData/TestConeVSFrustum-post.png", blockedBoxes)); + } + + @Test + public void testEnviromentMapping() throws IOException { + assertTrue(isSameImage("imageData/TestEnviromentMapping-pre.png", "imageData/TestEnviromentMapping-post.png", blockedBoxes)); + } + + @Test + public void testManyLights() throws IOException { + assertTrue(isSameImage("imageData/TestManyLights-pre.png", "imageData/TestManyLights-post.png", blockedBoxes)); + } + + @Test + public void testParallax() throws IOException { + assertTrue(isSameImage("imageData/TestParallax-pre.png", "imageData/TestParallax-post.png", blockedBoxes)); + } + + @Test + public void testPointLightShadows() throws IOException { + assertTrue(isSameImage("imageData/TestPointLightShadows-pre.png", "imageData/TestPointLightShadows-post.png", blockedBoxes)); + } + + @Test + public void testShaderNodes() throws IOException { + assertTrue(isSameImage("imageData/TestShaderNodes-pre.png", "imageData/TestShaderNodes-post.png", blockedBoxes)); + } + + @Test + public void testSpotlightTerrain() throws IOException { + assertTrue(isSameImage("imageData/TestSpotlightTerrain-pre.png", "imageData/TestSpotlightTerrain-post.png", blockedBoxes)); + } + + @Test + public void testTangentGen() throws IOException { + assertTrue(isSameImage("imageData/TestTangentGen-pre.png", "imageData/TestTangentGen-post.png", blockedBoxes)); + } + + @Test + public void testUnshadedModel() throws IOException { + assertTrue(isSameImage("imageData/TestUnshadedModel-pre.png", "imageData/TestUnshadedModel-post.png", blockedBoxes)); + } + + @Test + public void testManyLightsSingle() throws IOException { + assertTrue(isSameImage("imageData/TestManyLightsSingle-pre.png", "imageData/TestManyLightsSingle-post.png", blockedBoxes)); + } + + private boolean isSameImage(String preImgName, String postImgName, List blockedRegions) throws IOException { + BufferedImage preImg = ImageIO.read(getClass().getClassLoader().getResource(preImgName)); + BufferedImage postImg = ImageIO.read(getClass().getClassLoader().getResource(postImgName)); + + for (Box b : blockedRegions) { + blockRegion(preImg, b); + blockRegion(postImg, b); + } + + if (preImg.getWidth() != postImg.getWidth() || preImg.getHeight() != postImg.getHeight()) return false; + + for (int x = 0; x < preImg.getWidth(); x++) { + for (int y = 0; y < preImg.getHeight(); y++) { + if (preImg.getRGB(x, y) != postImg.getRGB(x, y)) { + return false; + } + } + } + + return true; + } + + private void blockRegion(BufferedImage img, Box blockedRegion) { + for (int x = 0; x < blockedRegion.width; x++){ + for (int y = 0; y < blockedRegion.height; y++){ + // set this pixel to black + img.setRGB(x + blockedRegion.x, y + blockedRegion.y, 0); + } + } + } + + private class Box { + int x; + int y; + + int width; + int height; + + public Box(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + } + + +} diff --git a/jme3-core/src/test/resources/imageData/TestConeVSFrustum-post.png b/jme3-core/src/test/resources/imageData/TestConeVSFrustum-post.png new file mode 100644 index 0000000000..ed042cb57b Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestConeVSFrustum-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestConeVSFrustum-pre.png b/jme3-core/src/test/resources/imageData/TestConeVSFrustum-pre.png new file mode 100644 index 0000000000..0efbeeb8c5 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestConeVSFrustum-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestEnviromentMapping-post.png b/jme3-core/src/test/resources/imageData/TestEnviromentMapping-post.png new file mode 100644 index 0000000000..b8a68acdaf Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestEnviromentMapping-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestEnviromentMapping-pre.png b/jme3-core/src/test/resources/imageData/TestEnviromentMapping-pre.png new file mode 100644 index 0000000000..412b880b7e Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestEnviromentMapping-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestManyLights-post.png b/jme3-core/src/test/resources/imageData/TestManyLights-post.png new file mode 100644 index 0000000000..eb88ec514c Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestManyLights-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestManyLights-pre.png b/jme3-core/src/test/resources/imageData/TestManyLights-pre.png new file mode 100644 index 0000000000..c9789b4406 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestManyLights-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestManyLightsSingle-post.png b/jme3-core/src/test/resources/imageData/TestManyLightsSingle-post.png new file mode 100644 index 0000000000..986e826b98 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestManyLightsSingle-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestManyLightsSingle-pre.png b/jme3-core/src/test/resources/imageData/TestManyLightsSingle-pre.png new file mode 100644 index 0000000000..91bfd60e6a Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestManyLightsSingle-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestParallax-post.png b/jme3-core/src/test/resources/imageData/TestParallax-post.png new file mode 100644 index 0000000000..9a50fdf89b Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestParallax-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestParallax-pre.png b/jme3-core/src/test/resources/imageData/TestParallax-pre.png new file mode 100644 index 0000000000..0438e67f8e Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestParallax-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestPointLightShadows-post.png b/jme3-core/src/test/resources/imageData/TestPointLightShadows-post.png new file mode 100644 index 0000000000..0499864e6c Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestPointLightShadows-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestPointLightShadows-pre.png b/jme3-core/src/test/resources/imageData/TestPointLightShadows-pre.png new file mode 100644 index 0000000000..177fccf943 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestPointLightShadows-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestShaderNodes-post.png b/jme3-core/src/test/resources/imageData/TestShaderNodes-post.png new file mode 100644 index 0000000000..57120a5fd6 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestShaderNodes-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestShaderNodes-pre.png b/jme3-core/src/test/resources/imageData/TestShaderNodes-pre.png new file mode 100644 index 0000000000..f476994357 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestShaderNodes-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-post.png b/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-post.png new file mode 100644 index 0000000000..f29ca48fb2 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-pre.png b/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-pre.png new file mode 100644 index 0000000000..dd52528bdd Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestSpotlightTerrain-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestTangentGen-post.png b/jme3-core/src/test/resources/imageData/TestTangentGen-post.png new file mode 100644 index 0000000000..93d9b2c632 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestTangentGen-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestTangentGen-pre.png b/jme3-core/src/test/resources/imageData/TestTangentGen-pre.png new file mode 100644 index 0000000000..109a542880 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestTangentGen-pre.png differ diff --git a/jme3-core/src/test/resources/imageData/TestUnshadedModel-post.png b/jme3-core/src/test/resources/imageData/TestUnshadedModel-post.png new file mode 100644 index 0000000000..2fe122907c Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestUnshadedModel-post.png differ diff --git a/jme3-core/src/test/resources/imageData/TestUnshadedModel-pre.png b/jme3-core/src/test/resources/imageData/TestUnshadedModel-pre.png new file mode 100644 index 0000000000..3a2c265b38 Binary files /dev/null and b/jme3-core/src/test/resources/imageData/TestUnshadedModel-pre.png differ diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java index 31238cdcaf..0ffae538a0 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java @@ -166,10 +166,10 @@ private void makeSoftParticleEmitter(Spatial scene, boolean enabled) { enabledSoftParticles = enabled; if( processor.getNumSamples()>1){ - emitter.getMaterial().selectTechnique("SoftParticles15", renderManager); + emitter.selectTechnique("SoftParticles15", renderManager); emitter.getMaterial().setInt("NumSamplesDepth", processor.getNumSamples()); }else{ - emitter.getMaterial().selectTechnique("SoftParticles", renderManager); + emitter.selectTechnique("SoftParticles", renderManager); } emitter.getMaterial().setTexture("DepthTexture", processor.getDepthTexture()); emitter.setQueueBucket(RenderQueue.Bucket.Translucent); @@ -177,7 +177,7 @@ private void makeSoftParticleEmitter(Spatial scene, boolean enabled) { logger.log(Level.FINE, "Made particle Emitter {0} soft.", emitter.getName()); } else { emitter.getMaterial().clearParam("DepthTexture"); - emitter.getMaterial().selectTechnique("Default", renderManager); + emitter.selectTechnique("Default", renderManager); // emitter.setQueueBucket(RenderQueue.Bucket.Transparent); logger.log(Level.FINE, "Particle Emitter {0} is not soft anymore.", emitter.getName()); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java index 005634bcd1..e9a59cd5d7 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java +++ b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java @@ -109,7 +109,7 @@ public void simpleInitApp() { LightNode ln = new LightNode("l", light); n.attachChild(ln); ln.setLocalTranslation(p.getPosition()); - int rand = FastMath.nextRandomInt(0, 3); + int rand = nb % 4; switch (rand) { case 0: light.setColor(ColorRGBA.Red); diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java index db8a21c978..60dacb60cc 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java @@ -27,17 +27,19 @@ public void simpleInitApp() { Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); - mat.selectTechnique("Default", renderManager); + + cube_tex.setMaterial(mat); + + cube_tex.selectTechnique("Default", renderManager); Technique t = mat.getActiveTechnique(); for (Shader.ShaderSource shaderSource : t.getShader().getSources()) { System.out.println(shaderSource.getSource()); } - + mat.setColor("Color", ColorRGBA.Yellow); mat.setTexture("ColorMap", tex); - cube_tex.setMaterial(mat); rootNode.attachChild(cube_tex); } } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java index 9dd8310207..2b3c5bce18 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java @@ -531,7 +531,9 @@ public void render() { material.setTexture("ColorMap", texture); mesh.updateCounts(); - material.render(meshGeometry, renderManager); + + meshGeometry.setMaterial(material); + meshGeometry.render(renderManager); renderManager.setForcedRenderState(null); } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java index ab0fc6659e..3b661b6a57 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java @@ -307,7 +307,9 @@ public void renderImage(RenderImage image, int x, int y, int w, int h, rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); - textureColorMaterial.render(quadGeom, rm); + + quadGeom.setMaterial(textureColorMaterial); + quadGeom.render(rm); //System.out.format("renderImage2(%s, %d, %d, %d, %d, %d, %d, %d, %d, %s, %f, %d, %d)\n", texture.getKey().toString(), // x, y, w, h, srcX, srcY, srcW, srcH, @@ -334,7 +336,8 @@ public void renderImage(RenderImage image, int x, int y, int width, int height, rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); - textureColorMaterial.render(quadGeom, rm); + quadGeom.setMaterial(textureColorMaterial); + quadGeom.render(rm); //System.out.format("renderImage1(%s, %d, %d, %d, %d, %s, %f)\n", jmeImage.getTexture().getKey().toString(), x, y, width, height, color.toString(), imageScale); } @@ -354,7 +357,9 @@ public void renderQuad(int x, int y, int width, int height, Color color) { rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); - colorMaterial.render(quadGeom, rm); + + quadGeom.setMaterial(colorMaterial); + quadGeom.render(rm); } //System.out.format("renderQuad1(%d, %d, %d, %d, %s)\n", x, y, width, height, color.toString()); @@ -381,7 +386,9 @@ public void renderQuad(int x, int y, int width, int height, rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); - vertexColorMaterial.render(quadGeom, rm); + + quadGeom.setMaterial(vertexColorMaterial); + quadGeom.render(rm); //System.out.format("renderQuad2(%d, %d, %d, %d, %s, %s, %s, %s)\n", x, y, width, height, topLeft.toString(), // topRight.toString(), diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java index d266dcf958..79279c42cc 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java @@ -33,6 +33,7 @@ import com.jme3.asset.AssetManager; import com.jme3.math.FastMath; +import com.jme3.math.interpolations.impl.LinearFloatInterpolation; import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.plugins.fbx.obj.FbxObject; @@ -119,8 +120,10 @@ public float getValueAtTime(long time) { long prevToNextDelta = keyTime2 - keyTime1; long prevToCurrentDelta = time - keyTime1; float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta; - - return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2); + + // TODO: Because of the new Interpolation classes, it is now possible to support + // other interpolation modes easily and extensible, consider implementing. + return new LinearFloatInterpolation(keyValue1, keyValue2).interpolate(lerpAmount); } @Override