Merge remote-tracking branch 'origin/master' into in-pass-shadows

in-pass-shadows
Kirill Vainer 7 years ago
commit 59c85d58c8
  1. 35
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  2. 189
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  3. 12
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  4. 2
      jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md
  5. 4
      jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.vert
  6. 128
      jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java
  7. 147
      jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java
  8. 5
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java
  9. 4
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java
  10. 58
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java
  11. 72
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  12. 27
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  13. 4
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java

@ -1980,7 +1980,13 @@ public final class GLRenderer implements Renderer {
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private void setupTextureParams(int unit, Texture tex) { private void setupTextureParams(int unit, Texture tex) {
Image image = tex.getImage(); 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; boolean haveMips = true;
if (image != null) { if (image != null) {
@ -2183,6 +2189,9 @@ public final class GLRenderer implements Renderer {
int target = convertTextureType(type, img.getMultiSamples(), -1); int target = convertTextureType(type, img.getMultiSamples(), -1);
bindTextureAndUnit(target, img, unit); bindTextureAndUnit(target, img, unit);
int imageSamples = img.getMultiSamples();
if (imageSamples <= 1) {
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
// Image does not have mipmaps, but they are required. // Image does not have mipmaps, but they are required.
// Generate from base level. // Generate from base level.
@ -2197,31 +2206,29 @@ public final class GLRenderer implements Renderer {
} else if (img.hasMipmaps()) { } else if (img.hasMipmaps()) {
// Image already has mipmaps, set the max level based on the // Image already has mipmaps, set the max level based on the
// number of mipmaps we have. // number of mipmaps we have.
if (caps.contains(Caps.OpenGL20)) { gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
}
} else { } else {
// Image does not have mipmaps and they are not required. // Image does not have mipmaps and they are not required.
// Specify that that the texture has no mipmaps. // Specify that that the texture has no mipmaps.
if (caps.contains(Caps.OpenGL20)) { gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, 0);
gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, 0); }
} else {
// 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()) { if (img.getFormat().isDepthFormat()) {
img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples)); img.setMultiSamples(Math.min(limits.get(Limits.DepthTextureSamples), imageSamples));
} else { } else {
img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples)); img.setMultiSamples(Math.min(limits.get(Limits.ColorTextureSamples), imageSamples));
} }
}
// Check if graphics card doesn't support multisample textures scaleToPot = false;
if (!caps.contains(Caps.TextureMultisample)) {
if (img.getMultiSamples() > 1) {
throw new RendererException("Multisample textures are not supported by the video hardware");
}
} }
// Check if graphics card doesn't support depth textures // Check if graphics card doesn't support depth textures

@ -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 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. * 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. * To integrate them in the batch you have to call the batch() method again on the batchNode.
* * <p>
* TODO normal or tangents or both looks a bit weird
* TODO more automagic (batch when needed in the updateLogicalState) * TODO more automagic (batch when needed in the updateLogicalState)
*
* @author Nehon * @author Nehon
*/ */
public class BatchNode extends GeometryGroupNode { public class BatchNode extends GeometryGroupNode {
@ -133,35 +133,44 @@ public class BatchNode extends GeometryGroupNode {
Mesh origMesh = bg.getMesh(); Mesh origMesh = bg.getMesh();
VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position); VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer posBuf = (FloatBuffer) pvb.getData();
VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); 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); VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); 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); 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); if (nvb != null) {
FloatBuffer tanBuf = (FloatBuffer) tvb.getData(); nvb.updateData(normBuf);
VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent); }
FloatBuffer otanBuf = (FloatBuffer) otvb.getData(); if (tvb != null) {
doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat);
tvb.updateData(tanBuf); 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(); batch.geometry.updateModelBound();
} }
} }
private FloatBuffer getFloatBuffer(VertexBuffer vb) {
if (vb == null) {
return null;
}
return (FloatBuffer) vb.getData();
}
/** /**
* Batch this batchNode * 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 * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
@ -257,6 +266,7 @@ public class BatchNode extends GeometryGroupNode {
/** /**
* recursively visit the subgraph and unbatch geometries * recursively visit the subgraph and unbatch geometries
*
* @param s * @param s
*/ */
private void unbatchSubGraph(Spatial 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 * 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 * 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 * @return the material that is used for the first batch of this BatchNode
*
* @see #setMaterial(com.jme3.material.Material) * @see #setMaterial(com.jme3.material.Material)
*/ */
public Material getMaterial() { public Material getMaterial() {
@ -428,14 +437,6 @@ public class BatchNode extends GeometryGroupNode {
+ " primitive types: " + mode + " != " + listMode); + " primitive types: " + mode + " != " + listMode);
} }
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; 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) { 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;
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) {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
Vector3f pos = vars.vect1; Vector3f pos = vars.vect1;
Vector3f norm = vars.vect2; Vector3f norm = vars.vect2;
@ -588,61 +543,77 @@ public class BatchNode extends GeometryGroupNode {
int offset = start * 3; int offset = start * 3;
int tanOffset = start * 4; int tanOffset = start * 4;
bindBufPos.rewind(); bindBufPos.rewind();
bindBufNorm.rewind();
bindBufTangents.rewind();
bindBufPos.get(tmpFloat, 0, length); bindBufPos.get(tmpFloat, 0, length);
if (bindBufNorm != null) {
bindBufNorm.rewind();
bindBufNorm.get(tmpFloatN, 0, length); bindBufNorm.get(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bindBufTangents.rewind();
bindBufTangents.get(tmpFloatT, 0, tanLength); bindBufTangents.get(tmpFloatT, 0, tanLength);
}
int index = 0; int index = 0;
int tanIndex = 0; int tanIndex = 0;
while (index < length) { int index1, index2, tanIndex1, tanIndex2;
pos.x = tmpFloat[index];
norm.x = tmpFloatN[index++];
pos.y = tmpFloat[index];
norm.y = tmpFloatN[index++];
pos.z = tmpFloat[index];
norm.z = tmpFloatN[index];
tan.x = tmpFloatT[tanIndex++]; while (index < length) {
tan.y = tmpFloatT[tanIndex++]; index1 = index + 1;
tan.z = tmpFloatT[tanIndex++]; index2 = index + 2;
pos.x = tmpFloat[index];
pos.y = tmpFloat[index1];
pos.z = tmpFloat[index2];
transform.mult(pos, pos); transform.mult(pos, pos);
transform.multNormal(norm, norm);
transform.multNormal(tan, tan);
index -= 2;
tanIndex -= 3;
tmpFloat[index] = pos.x; tmpFloat[index] = pos.x;
tmpFloatN[index++] = norm.x; tmpFloat[index1] = pos.y;
tmpFloat[index] = pos.y; tmpFloat[index2] = pos.z;
tmpFloatN[index++] = norm.y;
tmpFloat[index] = pos.z;
tmpFloatN[index++] = norm.z;
tmpFloatT[tanIndex++] = tan.x; if (bindBufNorm != null) {
tmpFloatT[tanIndex++] = tan.y; norm.x = tmpFloatN[index];
tmpFloatT[tanIndex++] = tan.z; norm.y = tmpFloatN[index1];
norm.z = tmpFloatN[index2];
transform.multNormal(norm, norm);
tmpFloatN[index] = norm.x;
tmpFloatN[index1] = norm.y;
tmpFloatN[index2] = norm.z;
}
index += 3;
//Skipping 4th element of tangent buffer (handedness) if (bindBufTangents != null) {
tanIndex++; 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(); vars.release();
bufPos.position(offset);
//using bulk put as it's faster //using bulk put as it's faster
bufPos.position(offset);
bufPos.put(tmpFloat, 0, length); bufPos.put(tmpFloat, 0, length);
if (bindBufNorm != null) {
bufNorm.position(offset); bufNorm.position(offset);
//using bulk put as it's faster
bufNorm.put(tmpFloatN, 0, length); bufNorm.put(tmpFloatN, 0, length);
}
if (bindBufTangents != null) {
bufTangents.position(tanOffset); bufTangents.position(tanOffset);
//using bulk put as it's faster
bufTangents.put(tmpFloatT, 0, tanLength); bufTangents.put(tmpFloatT, 0, tanLength);
} }
}
private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
@ -653,11 +624,11 @@ public class BatchNode extends GeometryGroupNode {
offset *= componentSize; offset *= componentSize;
for (int i = 0; i < inBuf.limit() / componentSize; i++) { 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.y = inBuf.get(i * componentSize + 1);
pos.z = inBuf.get(i * componentSize + 2); 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 + 1, pos.y);
outBuf.put(offset + i * componentSize + 2, pos.z); outBuf.put(offset + i * componentSize + 2, pos.z);
} }
@ -667,6 +638,7 @@ public class BatchNode extends GeometryGroupNode {
protected class Batch implements JmeCloneable { protected class Batch implements JmeCloneable {
/** /**
* update the batchesByGeom map for this batch with the given List of geometries * update the batchesByGeom map for this batch with the given List of geometries
*
* @param list * @param list
*/ */
void updateGeomList(List<Geometry> list) { void updateGeomList(List<Geometry> list) {
@ -676,6 +648,7 @@ public class BatchNode extends GeometryGroupNode {
} }
} }
} }
Geometry geometry; Geometry geometry;
public final Geometry getGeometry() { public final Geometry getGeometry() {

@ -1013,6 +1013,18 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
BoundingVolume worldBound, BoundingVolume worldBound,
CollisionResults results){ 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) { if (getVertexCount() == 0) {
return 0; return 0;
} }

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

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

@ -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();
}
}

@ -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);
}
}

@ -4,6 +4,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.jme3.asset.AssetLoadException; import com.jme3.asset.AssetLoadException;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; 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); T output = readExtension(name, el, input);
output = readExtras(name, el, output); output = readExtras(name, el, output);
return 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"); JsonElement extensions = el.getAsJsonObject().getAsJsonObject("extensions");
if (extensions == null) { if (extensions == null) {
return input; return input;

@ -2,6 +2,8 @@ package com.jme3.scene.plugins.gltf;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import java.io.IOException;
/** /**
* Base Interface for extension loading implementation. * 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 * @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 * @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;
} }

@ -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);
}
}

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

@ -253,10 +253,11 @@ public class GltfUtils {
return; return;
} }
LittleEndien stream = getStream(source); 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); populateShortArray((short[]) store, stream, count, byteOffset, byteStride, numComponents, format);
} else } else if (store instanceof float[]) {
if (store instanceof float[]) {
populateFloatArray((float[]) store, stream, count, byteOffset, byteStride, numComponents, format); populateFloatArray((float[]) store, stream, count, byteOffset, byteStride, numComponents, format);
} else if (store instanceof Vector3f[]) { } else if (store instanceof Vector3f[]) {
populateVector3fArray((Vector3f[]) store, stream, count, byteOffset, byteStride, numComponents, format); 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 { 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 componentSize = format.getComponentSize();
int index = byteOffset; int index = byteOffset;

@ -3,6 +3,8 @@ package com.jme3.scene.plugins.gltf;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.jme3.asset.AssetKey; 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.getAsColor;
import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat;
@ -15,7 +17,7 @@ public class PBRSpecGlossExtensionLoader implements ExtensionLoader {
private PBRSpecGlossMaterialAdapter materialAdapter = new PBRSpecGlossMaterialAdapter(); private PBRSpecGlossMaterialAdapter materialAdapter = new PBRSpecGlossMaterialAdapter();
@Override @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; MaterialAdapter adapter = materialAdapter;
AssetKey key = loader.getInfo().getKey(); AssetKey key = loader.getInfo().getKey();
//check for a custom adapter for spec/gloss pipeline //check for a custom adapter for spec/gloss pipeline

Loading…
Cancel
Save