Explorar el Código

Merge remote-tracking branch 'origin/master' into opengles2-fixes

Kirill Vainer hace 8 años
padre
commit
7326c45ee8

+ 41 - 29
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -1980,7 +1980,13 @@ public final class GLRenderer implements Renderer {
     @SuppressWarnings("fallthrough")
     private void setupTextureParams(int unit, Texture tex) {
         Image image = tex.getImage();
-        int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1);
+        int samples = image != null ? image.getMultiSamples() : 1;
+        int target = convertTextureType(tex.getType(), samples, -1);
+
+        if (samples > 1) {
+            bindTextureOnly(target, image, unit);
+            return;
+        }
 
         boolean haveMips = true;
         if (image != null) {
@@ -2183,45 +2189,51 @@ public final class GLRenderer implements Renderer {
         int target = convertTextureType(type, img.getMultiSamples(), -1);
         bindTextureAndUnit(target, img, unit);
 
-        if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
-            // Image does not have mipmaps, but they are required.
-            // Generate from base level.
+        int imageSamples = img.getMultiSamples();
 
-            if (!caps.contains(Caps.FrameBuffer) && gl2 != null) {
-                gl2.glTexParameteri(target, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE);
-                img.setMipmapsGenerated(true);
+        if (imageSamples <= 1) {
+            if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
+                // Image does not have mipmaps, but they are required.
+                // Generate from base level.
+
+                if (!caps.contains(Caps.FrameBuffer) && gl2 != null) {
+                    gl2.glTexParameteri(target, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE);
+                    img.setMipmapsGenerated(true);
+                } else {
+                    // For OpenGL3 and up.
+                    // We'll generate mipmaps via glGenerateMipmapEXT (see below)
+                }
+            } else if (img.hasMipmaps()) {
+                // Image already has mipmaps, set the max level based on the 
+                // number of mipmaps we have.
+                gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
             } else {
-                // For OpenGL3 and up.
-                // We'll generate mipmaps via glGenerateMipmapEXT (see below)
-            }
-        } else if (img.hasMipmaps()) {
-            // Image already has mipmaps, set the max level based on the 
-            // number of mipmaps we have.
-            if (caps.contains(Caps.OpenGL20)) {
-                gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
-            }
+                // Image does not have mipmaps and they are not required.
+                // Specify that that the texture has no mipmaps.
+                gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, 0);
+            }
+(??)        } else if (img.hasMipmaps()) {
+(??)            // Image already has mipmaps, set the max level based on the 
+(??)            // number of mipmaps we have.
+(??)            gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
         } else {
-            // Image does not have mipmaps and they are not required.
-            // Specify that that the texture has no mipmaps.
-            if (caps.contains(Caps.OpenGL20)) {
-                gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, 0);
+            // Check if graphics card doesn't support multisample textures
+            if (!caps.contains(Caps.TextureMultisample)) {
+                throw new RendererException("Multisample textures are not supported by the video hardware");
+            }
+(??)
+
+            if (img.isGeneratedMipmapsRequired() || img.hasMipmaps()) {
+                throw new RendererException("Multisample textures do not support mipmaps");
             }
-        }
 
-        int imageSamples = img.getMultiSamples();
-        if (imageSamples > 1) {
             if (img.getFormat().isDepthFormat()) {
                 img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples));
             } else {
                 img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples));
             }
-        }
 
-        // Check if graphics card doesn't support multisample textures
-        if (!caps.contains(Caps.TextureMultisample)) {
-            if (img.getMultiSamples() > 1) {
-                throw new RendererException("Multisample textures are not supported by the video hardware");
-            }
+            scaleToPot = false;
         }
 
         // Check if graphics card doesn't support depth textures

+ 102 - 129
jme3-core/src/main/java/com/jme3/scene/BatchNode.java

@@ -62,9 +62,9 @@ import com.jme3.util.clone.JmeCloneable;
  * Sub geoms can be removed but it may be slower than the normal spatial removing
  * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
  * To integrate them in the batch you have to call the batch() method again on the batchNode.
- *
- * TODO normal or tangents or both looks a bit weird
+ * <p>
  * TODO more automagic (batch when needed in the updateLogicalState)
+ *
  * @author Nehon
  */
 public class BatchNode extends GeometryGroupNode {
@@ -108,7 +108,7 @@ public class BatchNode extends GeometryGroupNode {
     public void onMaterialChange(Geometry geom) {
         throw new UnsupportedOperationException(
                 "Cannot set the material of a batched geometry, "
-                + "change the material of the parent BatchNode.");
+                        + "change the material of the parent BatchNode.");
     }
 
     @Override
@@ -122,7 +122,7 @@ public class BatchNode extends GeometryGroupNode {
         setNeedsFullRebatch(true);
     }
 
-    protected Matrix4f getTransformMatrix(Geometry g){
+    protected Matrix4f getTransformMatrix(Geometry g) {
         return g.cachedWorldMat;
     }
 
@@ -133,35 +133,44 @@ public class BatchNode extends GeometryGroupNode {
             Mesh origMesh = bg.getMesh();
 
             VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
-            FloatBuffer posBuf = (FloatBuffer) pvb.getData();
             VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
-            FloatBuffer normBuf = (FloatBuffer) nvb.getData();
+            VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
 
             VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
-            FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
             VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
-            FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
+            VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent);
+
+            FloatBuffer posBuf = getFloatBuffer(pvb);
+            FloatBuffer normBuf = getFloatBuffer(nvb);
+            FloatBuffer tanBuf = getFloatBuffer(tvb);
+
+            FloatBuffer oposBuf = getFloatBuffer(opvb);
+            FloatBuffer onormBuf = getFloatBuffer(onvb);
+            FloatBuffer otanBuf = getFloatBuffer(otvb);
+
             Matrix4f transformMat = getTransformMatrix(bg);
+            doTransforms(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
 
-            if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
+            pvb.updateData(posBuf);
 
-                VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
-                FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
-                VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent);
-                FloatBuffer otanBuf = (FloatBuffer) otvb.getData();
-                doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
+            if (nvb != null) {
+                nvb.updateData(normBuf);
+            }
+            if (tvb != null) {
                 tvb.updateData(tanBuf);
-            } else {
-                doTransforms(oposBuf, onormBuf, posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
             }
-            pvb.updateData(posBuf);
-            nvb.updateData(normBuf);
-
 
             batch.geometry.updateModelBound();
         }
     }
 
+    private FloatBuffer getFloatBuffer(VertexBuffer vb) {
+        if (vb == null) {
+            return null;
+        }
+        return (FloatBuffer) vb.getData();
+    }
+
     /**
      * Batch this batchNode
      * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
@@ -234,7 +243,7 @@ public class BatchNode extends GeometryGroupNode {
         logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
 
         //init the temp arrays if something has been batched only.
-        if(matMap.size()>0){
+        if (matMap.size() > 0) {
             //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed.
             //init temp float arrays
             tmpFloat = new float[maxVertCount * 3];
@@ -257,6 +266,7 @@ public class BatchNode extends GeometryGroupNode {
 
     /**
      * recursively visit the subgraph and unbatch geometries
+     *
      * @param s
      */
     private void unbatchSubGraph(Spatial s) {
@@ -343,11 +353,10 @@ public class BatchNode extends GeometryGroupNode {
 
     /**
      * Returns the material that is used for the first batch of this BatchNode
-     *
+     * <p>
      * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
      *
      * @return the material that is used for the first batch of this BatchNode
-     *
      * @see #setMaterial(com.jme3.material.Material)
      */
     public Material getMaterial() {
@@ -428,14 +437,6 @@ public class BatchNode extends GeometryGroupNode {
                         + " primitive types: " + mode + " != " + listMode);
             }
             mode = listMode;
-            //Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material
-//            if (mode == Mesh.Mode.Lines) {
-//                if (lineWidth != 1f && listLineWidth != lineWidth) {
-//                    throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
-//                            + lineWidth + " != " + listLineWidth);
-//                }
-//                lineWidth = listLineWidth;
-//            }
             compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
         }
 
@@ -528,53 +529,7 @@ public class BatchNode extends GeometryGroupNode {
         }
     }
 
-    private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
-        TempVars vars = TempVars.get();
-        Vector3f pos = vars.vect1;
-        Vector3f norm = vars.vect2;
-
-        int length = (end - start) * 3;
-
-        // offset is given in element units
-        // convert to be in component units
-        int offset = start * 3;
-        bindBufPos.rewind();
-        bindBufNorm.rewind();
-        //bufPos.position(offset);
-        //bufNorm.position(offset);
-        bindBufPos.get(tmpFloat, 0, length);
-        bindBufNorm.get(tmpFloatN, 0, length);
-        int index = 0;
-        while (index < length) {
-            pos.x = tmpFloat[index];
-            norm.x = tmpFloatN[index++];
-            pos.y = tmpFloat[index];
-            norm.y = tmpFloatN[index++];
-            pos.z = tmpFloat[index];
-            norm.z = tmpFloatN[index];
-
-            transform.mult(pos, pos);
-            transform.multNormal(norm, norm);
-
-            index -= 2;
-            tmpFloat[index] = pos.x;
-            tmpFloatN[index++] = norm.x;
-            tmpFloat[index] = pos.y;
-            tmpFloatN[index++] = norm.y;
-            tmpFloat[index] = pos.z;
-            tmpFloatN[index++] = norm.z;
-
-        }
-        vars.release();
-        bufPos.position(offset);
-        //using bulk put as it's faster
-        bufPos.put(tmpFloat, 0, length);
-        bufNorm.position(offset);
-        //using bulk put as it's faster
-        bufNorm.put(tmpFloatN, 0, length);
-    }
-
-    private void doTransformsTangents(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents,FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
+    private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents, FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
         TempVars vars = TempVars.get();
         Vector3f pos = vars.vect1;
         Vector3f norm = vars.vect2;
@@ -588,60 +543,76 @@ public class BatchNode extends GeometryGroupNode {
         int offset = start * 3;
         int tanOffset = start * 4;
 
-
         bindBufPos.rewind();
-        bindBufNorm.rewind();
-        bindBufTangents.rewind();
         bindBufPos.get(tmpFloat, 0, length);
-        bindBufNorm.get(tmpFloatN, 0, length);
-        bindBufTangents.get(tmpFloatT, 0, tanLength);
+
+        if (bindBufNorm != null) {
+            bindBufNorm.rewind();
+            bindBufNorm.get(tmpFloatN, 0, length);
+        }
+
+        if (bindBufTangents != null) {
+            bindBufTangents.rewind();
+            bindBufTangents.get(tmpFloatT, 0, tanLength);
+        }
 
         int index = 0;
         int tanIndex = 0;
-        while (index < length) {
-            pos.x = tmpFloat[index];
-            norm.x = tmpFloatN[index++];
-            pos.y = tmpFloat[index];
-            norm.y = tmpFloatN[index++];
-            pos.z = tmpFloat[index];
-            norm.z = tmpFloatN[index];
+        int index1, index2, tanIndex1, tanIndex2;
 
-            tan.x = tmpFloatT[tanIndex++];
-            tan.y = tmpFloatT[tanIndex++];
-            tan.z = tmpFloatT[tanIndex++];
+        while (index < length) {
+            index1 = index + 1;
+            index2 = index + 2;
 
+            pos.x = tmpFloat[index];
+            pos.y = tmpFloat[index1];
+            pos.z = tmpFloat[index2];
             transform.mult(pos, pos);
-            transform.multNormal(norm, norm);
-            transform.multNormal(tan, tan);
-
-            index -= 2;
-            tanIndex -= 3;
-
             tmpFloat[index] = pos.x;
-            tmpFloatN[index++] = norm.x;
-            tmpFloat[index] = pos.y;
-            tmpFloatN[index++] = norm.y;
-            tmpFloat[index] = pos.z;
-            tmpFloatN[index++] = norm.z;
-
-            tmpFloatT[tanIndex++] = tan.x;
-            tmpFloatT[tanIndex++] = tan.y;
-            tmpFloatT[tanIndex++] = tan.z;
+            tmpFloat[index1] = pos.y;
+            tmpFloat[index2] = pos.z;
+
+            if (bindBufNorm != null) {
+                norm.x = tmpFloatN[index];
+                norm.y = tmpFloatN[index1];
+                norm.z = tmpFloatN[index2];
+                transform.multNormal(norm, norm);
+                tmpFloatN[index] = norm.x;
+                tmpFloatN[index1] = norm.y;
+                tmpFloatN[index2] = norm.z;
+            }
 
-            //Skipping 4th element of tangent buffer (handedness)
-            tanIndex++;
+            index += 3;
+
+            if (bindBufTangents != null) {
+                tanIndex1 = tanIndex + 1;
+                tanIndex2 = tanIndex + 2;
+                tan.x = tmpFloatT[tanIndex];
+                tan.y = tmpFloatT[tanIndex1];
+                tan.z = tmpFloatT[tanIndex2];
+                transform.multNormal(tan, tan);
+                tmpFloatT[tanIndex] = tan.x;
+                tmpFloatT[tanIndex1] = tan.y;
+                tmpFloatT[tanIndex2] = tan.z;
+                tanIndex += 4;
+            }
 
         }
         vars.release();
-        bufPos.position(offset);
+
         //using bulk put as it's faster
+        bufPos.position(offset);
         bufPos.put(tmpFloat, 0, length);
-        bufNorm.position(offset);
-        //using bulk put as it's faster
-        bufNorm.put(tmpFloatN, 0, length);
-        bufTangents.position(tanOffset);
-        //using bulk put as it's faster
-        bufTangents.put(tmpFloatT, 0, tanLength);
+
+        if (bindBufNorm != null) {
+            bufNorm.position(offset);
+            bufNorm.put(tmpFloatN, 0, length);
+        }
+
+        if (bindBufTangents != null) {
+            bufTangents.position(tanOffset);
+            bufTangents.put(tmpFloatT, 0, tanLength);
+        }
     }
 
     private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
@@ -653,11 +624,11 @@ public class BatchNode extends GeometryGroupNode {
         offset *= componentSize;
 
         for (int i = 0; i < inBuf.limit() / componentSize; i++) {
-            pos.x = inBuf.get(i * componentSize + 0);
+            pos.x = inBuf.get(i * componentSize);
             pos.y = inBuf.get(i * componentSize + 1);
             pos.z = inBuf.get(i * componentSize + 2);
 
-            outBuf.put(offset + i * componentSize + 0, pos.x);
+            outBuf.put(offset + i * componentSize, pos.x);
             outBuf.put(offset + i * componentSize + 1, pos.y);
             outBuf.put(offset + i * componentSize + 2, pos.z);
         }
@@ -667,6 +638,7 @@ public class BatchNode extends GeometryGroupNode {
     protected class Batch implements JmeCloneable {
         /**
          * update the batchesByGeom map for this batch with the given List of geometries
+         *
          * @param list
          */
         void updateGeomList(List<Geometry> list) {
@@ -676,6 +648,7 @@ public class BatchNode extends GeometryGroupNode {
                 }
             }
         }
+
         Geometry geometry;
 
         public final Geometry getGeometry() {
@@ -685,14 +658,14 @@ public class BatchNode extends GeometryGroupNode {
         @Override
         public Batch jmeClone() {
             try {
-                return (Batch)super.clone();
+                return (Batch) super.clone();
             } catch (CloneNotSupportedException ex) {
                 throw new AssertionError();
             }
         }
 
         @Override
-        public void cloneFields( Cloner cloner, Object original ) {
+        public void cloneFields(Cloner cloner, Object original) {
             this.geometry = cloner.clone(geometry);
         }
 
@@ -704,11 +677,11 @@ public class BatchNode extends GeometryGroupNode {
 
     @Override
     public Node clone(boolean cloneMaterials) {
-        BatchNode clone = (BatchNode)super.clone(cloneMaterials);
-        if ( batches.size() > 0) {
-            for ( Batch b : batches ) {
-                for ( int i =0; i < clone.children.size(); i++ ) {
-                    if ( clone.children.get(i).getName().equals(b.geometry.getName())) {
+        BatchNode clone = (BatchNode) super.clone(cloneMaterials);
+        if (batches.size() > 0) {
+            for (Batch b : batches) {
+                for (int i = 0; i < clone.children.size(); i++) {
+                    if (clone.children.get(i).getName().equals(b.geometry.getName())) {
                         clone.children.remove(i);
                         break;
                     }
@@ -723,10 +696,10 @@ public class BatchNode extends GeometryGroupNode {
     }
 
     /**
-     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     * Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
      */
     @Override
-    public void cloneFields( Cloner cloner, Object original ) {
+    public void cloneFields(Cloner cloner, Object original) {
         super.cloneFields(cloner, original);
 
         this.batches = cloner.clone(batches);
@@ -736,7 +709,7 @@ public class BatchNode extends GeometryGroupNode {
 
 
         HashMap<Geometry, Batch> newBatchesByGeom = new HashMap<Geometry, Batch>();
-        for( Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet() ) {
+        for (Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet()) {
             newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
         }
         this.batchesByGeom = newBatchesByGeom;
@@ -745,7 +718,7 @@ public class BatchNode extends GeometryGroupNode {
     @Override
     public int collideWith(Collidable other, CollisionResults results) {
         int total = 0;
-        for (Spatial child : children.getArray()){
+        for (Spatial child : children.getArray()) {
             if (!isBatch(child)) {
                 total += child.collideWith(other, results);
             }

+ 12 - 0
jme3-core/src/main/java/com/jme3/scene/Mesh.java

@@ -1013,6 +1013,18 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
                            BoundingVolume worldBound,
                            CollisionResults results){
 
+        switch (mode) {
+            case Points:
+            case Lines:
+            case LineStrip:
+            case LineLoop:
+                /*
+                 * Collisions can be detected only with triangles,
+                 * and there are no triangles in this mesh.
+                 */
+                return 0;
+        }
+
         if (getVertexCount() == 0) {
             return 0;
         }

+ 1 - 1
jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md

@@ -12,7 +12,7 @@ MaterialDef Sky Plane {
         WorldParameters {
             ViewMatrix
             ProjectionMatrix
-            WorldMatrix
+            WorldMatrixInverse
         }
 
         Defines {

+ 2 - 2
jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.vert

@@ -1,7 +1,7 @@
 #import "Common/ShaderLib/GLSLCompat.glsllib"
 uniform mat4 g_ViewMatrix;
 uniform mat4 g_ProjectionMatrix;
-uniform mat4 g_WorldMatrix;
+uniform mat4 g_WorldMatrixInverse;
 
 uniform vec3 m_NormalScale;
 
@@ -22,5 +22,5 @@ void main(){
     gl_Position = g_ProjectionMatrix * pos;
 
     vec4 normal = vec4(inNormal * m_NormalScale, 0.0);
-    direction = (g_WorldMatrix * normal).xyz;
+    direction = (g_WorldMatrixInverse * normal).xyz;
 }

+ 128 - 0
jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java

@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2017 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.material.Material;
+import com.jme3.material.plugins.J3MLoader;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.shape.Quad;
+import org.junit.Test;
+
+/**
+ * Verify that collideWith() doesn't reports collisions with phantom triangles.
+ * This was issue #710 at GitHub.
+ *
+ * @author Stephen Gold
+ */
+public class PhantomTrianglesTest {
+
+    AssetManager assetManager;
+
+    /**
+     * ray in the -Z direction, starting from (0.1, 0.2, 10)
+     */
+    final private Ray ray = new Ray(/* origin */new Vector3f(0.1f, 0.2f, 10f),
+            /* direction */ new Vector3f(0f, 0f, -1f));
+    Node rootNode;
+
+    /**
+     * Cast a ray at the geometries and report all collisions.
+     */
+    void castRay() {
+        CollisionResults results = new CollisionResults();
+        rootNode.collideWith(ray, results);
+        int numResults = results.size();
+        for (int resultI = 0; resultI < numResults; resultI++) {
+            CollisionResult result = results.getCollision(resultI);
+            Geometry geometry = result.getGeometry();
+            String name = geometry.getName();
+            if (name.equals("white lines")) {
+                assert false; // phantom triangle
+            }
+        }
+    }
+
+    /**
+     * Attach a red square in the XY plane with its lower left corner at (0, 0,
+     * 0). It is composed of 2 triangles.
+     */
+    void createRedSquare() {
+        Mesh quadMesh = new Quad(1f, 1f);
+        Geometry redSquare = new Geometry("red square", quadMesh);
+        Material red = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+        redSquare.setMaterial(red);
+        rootNode.attachChild(redSquare);
+    }
+
+    /**
+     * Attach a pair of parallel white lines in the z=1 plane.
+     */
+    void createWhiteLines() {
+        Mesh lineMesh = new Mesh();
+        lineMesh.setMode(Mesh.Mode.Lines);
+        float[] corners = new float[]{
+            -1f, -1f, 0f,
+            -1f, 1f, 0f,
+            1f, 1f, 0f,
+            1f, -1f, 0f
+        };
+        lineMesh.setBuffer(VertexBuffer.Type.Position, 3, corners);
+        short[] indices = new short[]{0, 1, 2, 3};
+        lineMesh.setBuffer(VertexBuffer.Type.Index, 2, indices);
+        lineMesh.updateBound();
+        Geometry whiteLines = new Geometry("white lines", lineMesh);
+        Material white = assetManager.loadMaterial("Common/Materials/WhiteColor.j3m");
+        whiteLines.setMaterial(white);
+        whiteLines.move(0f, 0f, 1f);
+        rootNode.attachChild(whiteLines);
+    }
+
+    @Test
+    public void testPhantomTriangles() {
+        assetManager = new DesktopAssetManager();
+        assetManager.registerLocator(null, ClasspathLocator.class);
+        assetManager.registerLoader(J3MLoader.class, "j3m", "j3md");
+        rootNode = new Node();
+
+        createRedSquare();
+        createWhiteLines();
+        rootNode.updateLogicalState(0.01f);
+        rootNode.updateGeometricState();
+        castRay();
+    }
+}

+ 147 - 0
jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2017 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.texture;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+/**
+ * Simple application to test sky rotation with a cube-mapped sky.
+ *
+ * Press "T" to rotate the sky and floor to the camera's left. Press "Y" to
+ * rotate the sky and floor to the camera's right. Both should appear to move by
+ * the same amount in the same direction.
+ *
+ * See issue #651 for further information.
+ *
+ * @author Stephen Gold
+ */
+public class TestSkyRotation extends SimpleApplication implements ActionListener {
+
+    /**
+     * objects visible in the scene
+     */
+    private Spatial floor, sky;
+    /**
+     * Y-axis rotation angle in radians
+     */
+    private float angle = 0f;
+
+    public static void main(String[] arguments) {
+        TestSkyRotation application = new TestSkyRotation();
+        application.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        /*
+         * Configure the camera.
+         */
+        flyCam.setEnabled(false);
+        Vector3f location = new Vector3f(-7f, 4f, 8f);
+        cam.setLocation(location);
+        Quaternion orientation;
+        orientation = new Quaternion(0.0037f, 0.944684f, -0.01067f, 0.327789f);
+        assert FastMath.approximateEquals(orientation.norm(), 1f);
+        cam.setRotation(orientation);
+        /*
+         * Attach a cube-mapped sky to the scene graph.
+         */
+        sky = SkyFactory.createSky(assetManager,
+                "Scenes/Beach/FullskiesSunset0068.dds",
+                SkyFactory.EnvMapType.CubeMap);
+        rootNode.attachChild(sky);
+        /*
+         * Attach a "floor" geometry to the scene graph.
+         */
+        Mesh floorMesh = new Box(10f, 0.1f, 10f);
+        floor = new Geometry("floor", floorMesh);
+        Material floorMaterial = new Material(assetManager,
+                "Common/MatDefs/Misc/Unshaded.j3md");
+        floorMaterial.setTexture("ColorMap",
+                assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
+        floor.setMaterial(floorMaterial);
+        rootNode.attachChild(floor);
+        /*
+         * Configure mappings and listeners for keyboard input.
+         */
+        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addListener(this, "left");
+        inputManager.addListener(this, "right");
+    }
+
+    /**
+     * Handle an input action from the user.
+     *
+     * @param name the name of the action
+     * @param ongoing true&rarr;depress key, false&rarr;release key
+     * @param ignored
+     */
+    @Override
+    public void onAction(String name, boolean ongoing, float ignored) {
+        if (!ongoing) {
+            return;
+        }
+        /*
+         * Update the Y-axis rotation angle based on which key was pressed.
+         */
+        if (name.equals("left")) {
+            angle += 0.1f; // radians
+            System.out.print("rotate floor and sky leftward ...");
+        } else if (name.equals("right")) {
+            angle -= 0.1f; // radians
+            System.out.printf("rotate floor and sky spatials rightward ...");
+        } else {
+            return;
+        }
+        /*
+         * Update the local rotations of both objects based on the angle.
+         */
+        System.out.printf(" to %.1f radians left of start%n", angle);
+        Quaternion rotation = new Quaternion();
+        rotation.fromAngleNormalAxis(angle, Vector3f.UNIT_Y);
+        floor.setLocalRotation(rotation);
+        sky.setLocalRotation(rotation);
+    }
+}

+ 3 - 2
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java

@@ -4,6 +4,7 @@ import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.jme3.asset.AssetLoadException;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.logging.Level;
@@ -56,13 +57,13 @@ public class CustomContentManager {
         }
     }
 
-    public <T> T readExtensionAndExtras(String name, JsonElement el, T input) throws AssetLoadException {
+    public <T> T readExtensionAndExtras(String name, JsonElement el, T input) throws AssetLoadException, IOException {
         T output = readExtension(name, el, input);
         output = readExtras(name, el, output);
         return output;
     }
 
-    private <T> T readExtension(String name, JsonElement el, T input) throws AssetLoadException {
+    private <T> T readExtension(String name, JsonElement el, T input) throws AssetLoadException, IOException {
         JsonElement extensions = el.getAsJsonObject().getAsJsonObject("extensions");
         if (extensions == null) {
             return input;

+ 3 - 1
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java

@@ -2,6 +2,8 @@ package com.jme3.scene.plugins.gltf;
 
 import com.google.gson.JsonElement;
 
+import java.io.IOException;
+
 /**
  * Base Interface for extension loading implementation.
  *
@@ -19,6 +21,6 @@ public interface ExtensionLoader {
      * @param input      an object containing already loaded data from the element, this is most probably a JME object
      * @return An object of the same type as input, containing the data from the input object and the eventual additional data read from the extension
      */
-    Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input);
+    Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException;
 
 }

+ 58 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java

@@ -0,0 +1,58 @@
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.util.LittleEndien;
+
+import java.io.*;
+import java.util.ArrayList;
+
+/**
+ * Created by Nehon on 12/09/2017.
+ */
+public class GlbLoader extends GltfLoader {
+
+    private static final int GLTF_MAGIC = 0x46546C67;
+    private static final int JSON_TYPE = 0x4E4F534A;
+    private static final int BIN_TYPE = 0x004E4942;
+    private ArrayList<byte[]> data = new ArrayList<>();
+
+    @Override
+    public Object load(AssetInfo assetInfo) throws IOException {
+        LittleEndien stream = new LittleEndien(new DataInputStream(assetInfo.openStream()));
+        int magic = stream.readInt();
+        int version = stream.readInt();
+        int length = stream.readInt();
+        System.err.println(magic == GLTF_MAGIC ? "gltf" : "no no no");
+        System.err.println(version);
+        System.err.println(length);
+
+        byte[] json = null;
+
+        //length is the total size, we have to remove the header size (3 integers = 12 bytes).
+        length -= 12;
+
+        while (length > 0) {
+            int chunkLength = stream.readInt();
+            int chunkType = stream.readInt();
+            if (chunkType == JSON_TYPE) {
+                json = new byte[chunkLength];
+                stream.read(json);
+                System.err.println(new String(json));
+            } else {
+                byte[] bin = new byte[chunkLength];
+                stream.read(bin);
+                data.add(bin);
+            }
+            //8 is the byte size of the 2 ints chunkLength and chunkType.
+            length -= chunkLength + 8;
+        }
+
+        return loadFromStream(assetInfo, new ByteArrayInputStream(json));
+    }
+
+    @Override
+    protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
+        return data.get(bufferIndex);
+    }
+
+}

+ 49 - 23
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -75,6 +75,11 @@ public class GltfLoader implements AssetLoader {
 
     @Override
     public Object load(AssetInfo assetInfo) throws IOException {
+        return loadFromStream(assetInfo, assetInfo.openStream());
+    }
+
+
+    protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws IOException {
         try {
             dataCache.clear();
             info = assetInfo;
@@ -87,7 +92,7 @@ public class GltfLoader implements AssetLoader {
                 defaultMat.setFloat("Roughness", 1f);
             }
 
-            docRoot = new JsonParser().parse(new JsonReader(new InputStreamReader(assetInfo.openStream()))).getAsJsonObject();
+            docRoot = new JsonParser().parse(new JsonReader(new InputStreamReader(stream))).getAsJsonObject();
 
             JsonObject asset = docRoot.getAsJsonObject().get("asset").getAsJsonObject();
             String generator = getAsString(asset, "generator");
@@ -455,7 +460,7 @@ public class GltfLoader implements AssetLoader {
         return data;
     }
 
-    public void readBuffer(Integer bufferViewIndex, int byteOffset, int count, Object store, int numComponents, VertexBuffer.Format format) throws IOException {
+    public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Object store, int numComponents, VertexBuffer.Format format) throws IOException {
 
         JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject();
         Integer bufferIndex = getAsInteger(bufferView, "buffer");
@@ -473,8 +478,17 @@ public class GltfLoader implements AssetLoader {
 
         data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data);
 
+        if (store == null) {
+            store = new byte[byteLength];
+        }
+
+        if (count == -1) {
+            count = byteLength;
+        }
+
         populateBuffer(store, data, count, byteOffset + bvByteOffset, byteStride, numComponents, format);
 
+        return store;
     }
 
     public byte[] readData(int bufferIndex) throws IOException {
@@ -489,6 +503,17 @@ public class GltfLoader implements AssetLoader {
         if (data != null) {
             return data;
         }
+        data = getBytes(bufferIndex, uri, bufferLength);
+
+        data = customContentManager.readExtensionAndExtras("buffer", buffer, data);
+
+        addToCache("buffers", bufferIndex, data, buffers.size());
+        return data;
+
+    }
+
+    protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException {
+        byte[] data;
         if (uri != null) {
             if (uri.startsWith("data:")) {
                 //base 64 embed data
@@ -505,19 +530,13 @@ public class GltfLoader implements AssetLoader {
                 input.read(data);
             }
         } else {
-            //no URI we are in a binary file so the data is in the 2nd chunk
-            //TODO handle binary GLTF (GLB)
-            throw new AssetLoadException("Binary gltf is not supported yet");
+            //no URI this should not happen in a gltf file, only in glb files.
+            throw new AssetLoadException("Buffer " + bufferIndex + " has no uri");
         }
-
-        data = customContentManager.readExtensionAndExtras("buffer", buffer, data);
-
-        addToCache("buffers", bufferIndex, data, buffers.size());
         return data;
-
     }
 
-    public Material readMaterial(int materialIndex) {
+    public Material readMaterial(int materialIndex) throws IOException {
         assertNotNull(materials, "There is no material defined yet a mesh references one");
 
         JsonObject matData = materials.get(materialIndex).getAsJsonObject();
@@ -571,7 +590,7 @@ public class GltfLoader implements AssetLoader {
         return adapter.getMaterial();
     }
 
-    public void readCameras() {
+    public void readCameras() throws IOException {
         if (cameras == null) {
             return;
         }
@@ -616,12 +635,12 @@ public class GltfLoader implements AssetLoader {
         }
     }
 
-    public Texture2D readTexture(JsonObject texture) {
+    public Texture2D readTexture(JsonObject texture) throws IOException {
         return readTexture(texture, false);
 
     }
 
-    public Texture2D readTexture(JsonObject texture, boolean flip) {
+    public Texture2D readTexture(JsonObject texture, boolean flip) throws IOException {
         if (texture == null) {
             return null;
         }
@@ -646,18 +665,24 @@ public class GltfLoader implements AssetLoader {
         return texture2d;
     }
 
-    public Texture2D readImage(int sourceIndex, boolean flip) {
+    public Texture2D readImage(int sourceIndex, boolean flip) throws IOException {
         if (images == null) {
             throw new AssetLoadException("No image defined");
         }
 
         JsonObject image = images.get(sourceIndex).getAsJsonObject();
         String uri = getAsString(image, "uri");
+        Integer bufferView = getAsInteger(image, "bufferView");
+        String mimeType = getAsString(image, "mimeType");
         Texture2D result;
         if (uri == null) {
-            //Image is embed in a buffer not supported yet
-            //TODO support images embed in a buffer
-            throw new AssetLoadException("Images embed in a buffer are not supported yet");
+            assertNotNull(bufferView, "Image " + sourceIndex + " should either have an uri or a bufferView");
+            assertNotNull(mimeType, "Image " + sourceIndex + " should have a mimeType");
+            byte[] data = (byte[]) readBuffer(bufferView, 0, -1, null, 1, VertexBuffer.Format.Byte);
+            String extension = mimeType.split("/")[1];
+            TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip);
+            result = (Texture2D) info.getManager().loadAssetFromStream(key, new ByteArrayInputStream(data));
+
         } else if (uri.startsWith("data:")) {
             //base64 encoded image
             String[] uriInfo = uri.split(",");
@@ -672,11 +697,7 @@ public class GltfLoader implements AssetLoader {
             Texture tex = info.getManager().loadTexture(key);
             result = (Texture2D) tex;
         }
-
-        result = customContentManager.readExtensionAndExtras("image", image, result);
-
         return result;
-
     }
 
     public void readAnimation(int animationIndex) throws IOException {
@@ -844,7 +865,7 @@ public class GltfLoader implements AssetLoader {
         }
     }
 
-    public Texture2D readSampler(int samplerIndex, Texture2D texture) {
+    public Texture2D readSampler(int samplerIndex, Texture2D texture) throws IOException {
         if (samplers == null) {
             throw new AssetLoadException("No samplers defined");
         }
@@ -1225,6 +1246,10 @@ public class GltfLoader implements AssetLoader {
         }
     }
 
+    private class TextureData {
+        byte[] data;
+    }
+
     private interface Populator<T> {
         T populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException;
     }
@@ -1380,5 +1405,6 @@ public class GltfLoader implements AssetLoader {
             return new SkinBuffers(data, format.getComponentSize());
         }
     }
+
 }
 

+ 24 - 3
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@@ -253,10 +253,11 @@ public class GltfUtils {
             return;
         }
         LittleEndien stream = getStream(source);
-        if (store instanceof short[]) {
+        if (store instanceof byte[]) {
+            populateByteArray((byte[]) store, stream, count, byteOffset, byteStride, numComponents, format);
+        } else if (store instanceof short[]) {
             populateShortArray((short[]) store, stream, count, byteOffset, byteStride, numComponents, format);
-        } else
-        if (store instanceof float[]) {
+        } else if (store instanceof float[]) {
             populateFloatArray((float[]) store, stream, count, byteOffset, byteStride, numComponents, format);
         } else if (store instanceof Vector3f[]) {
             populateVector3fArray((Vector3f[]) store, stream, count, byteOffset, byteStride, numComponents, format);
@@ -367,6 +368,26 @@ public class GltfUtils {
 
     }
 
+    private static void populateByteArray(byte[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
+        int componentSize = format.getComponentSize();
+        int index = byteOffset;
+        int dataLength = componentSize * numComponents;
+        int stride = Math.max(dataLength, byteStride);
+        int end = count * stride + byteOffset;
+        stream.skipBytes(byteOffset);
+        int arrayIndex = 0;
+        while (index < end) {
+            for (int i = 0; i < numComponents; i++) {
+                array[arrayIndex] = stream.readByte();
+                arrayIndex++;
+            }
+            if (dataLength < stride) {
+                stream.skipBytes(stride - dataLength);
+            }
+            index += stride;
+        }
+    }
+
     private static void populateShortArray(short[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {
         int componentSize = format.getComponentSize();
         int index = byteOffset;

+ 3 - 1
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java

@@ -3,6 +3,8 @@ package com.jme3.scene.plugins.gltf;
 import com.google.gson.JsonElement;
 import com.jme3.asset.AssetKey;
 
+import java.io.IOException;
+
 import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor;
 import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat;
 
@@ -15,7 +17,7 @@ public class PBRSpecGlossExtensionLoader implements ExtensionLoader {
     private PBRSpecGlossMaterialAdapter materialAdapter = new PBRSpecGlossMaterialAdapter();
 
     @Override
-    public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) {
+    public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
         MaterialAdapter adapter = materialAdapter;
         AssetKey key = loader.getInfo().getKey();
         //check for a custom adapter for spec/gloss pipeline