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

in-pass-shadows
Kirill Vainer 7 years ago
commit 59c85d58c8
  1. 63
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  2. 231
      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")
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,46 @@ 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 {
// 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

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

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

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

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

@ -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.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;

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

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

@ -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,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

Loading…
Cancel
Save