diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java index 4f2dcfc87b..27375469e4 100644 --- a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -112,13 +112,39 @@ public static void generate(Spatial s){ for (Spatial child : n.getChildren()) { generate(child); } - } else if (s instanceof Geometry){ - Geometry g = (Geometry)s; - MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh()); - if(!genTangSpaceDefault(context)){ - logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); + + } else if (s instanceof Geometry) { + Geometry g = (Geometry) s; + Mesh mesh = g.getMesh(); + + Mesh.Mode mode = mesh.getMode(); + boolean hasTriangles; + switch (mode) { + case Points: + case Lines: + case LineStrip: + case LineLoop: + hasTriangles = false; // skip this mesh + break; + + case Triangles: + case TriangleFan: + case TriangleStrip: + hasTriangles = true; + break; + + default: + String message = "Tangent generation isn't implemented for mode=" + mode; + throw new UnsupportedOperationException(message); + } + + if (hasTriangles) { + MikkTSpaceImpl context = new MikkTSpaceImpl(mesh); + if (!genTangSpaceDefault(context)) { + logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); + } + TangentUtils.generateBindPoseTangentsIfNecessary(mesh); } - TangentUtils.generateBindPoseTangentsIfNecessary(g.getMesh()); } } diff --git a/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java b/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java new file mode 100644 index 0000000000..87283c1c9a --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/TestIssue1919.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023 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.util; + +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import java.nio.FloatBuffer; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies how MikktspaceTangentGenerator handles various mesh modes. This was + * issue #1919 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue1919 { + /** + * The number of axes in a vector. + */ + private static final int numAxes = 3; + + /** + * Tests a Hybrid-mode mesh. + */ + @Test(expected = UnsupportedOperationException.class) + public void testHybrid() { + Geometry testGeometry = createGeometry(Mesh.Mode.Hybrid); + MikktspaceTangentGenerator.generate(testGeometry); + } + + /** + * Tests a LineLoop-mode mesh. + */ + @Test + public void testLineLoop() { + Geometry testGeometry = createGeometry(Mesh.Mode.LineLoop); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); /// skipped this mesh + } + + /** + * Tests a LineStrip-mode mesh. + */ + @Test + public void testLineStrip() { + Geometry testGeometry = createGeometry(Mesh.Mode.LineStrip); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); /// skipped this mesh + } + + /** + * Tests a Lines-mode mesh. + */ + @Test + public void testLines() { + Geometry testGeometry = createGeometry(Mesh.Mode.Lines); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); // skipped this mesh + } + + /** + * Tests a Patch-mode mesh. + */ + @Test(expected = UnsupportedOperationException.class) + public void testPatch() { + Geometry testGeometry = createGeometry(Mesh.Mode.Patch); + MikktspaceTangentGenerator.generate(testGeometry); + } + + /** + * Tests a Points-mode mesh. + */ + @Test + public void testPoints() { + Geometry testGeometry = createGeometry(Mesh.Mode.Points); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNull(tangents); // skipped this mesh + } + + /** + * Tests a Triangles-mode mesh. + */ + @Test + public void testTriangles() { + Geometry testGeometry = createGeometry(Mesh.Mode.Triangles); + MikktspaceTangentGenerator.generate(testGeometry); + + Mesh mesh = testGeometry.getMesh(); + VertexBuffer tangents = mesh.getBuffer(VertexBuffer.Type.Tangent); + Assert.assertNotNull(tangents); // generated tangents + } + + /** + * Generates a geometry in the X-Z plane with the specified mesh mode. + * + * @param mode the desired mode (not null) + * @return a new geometry + */ + private Geometry createGeometry(Mesh.Mode mode) { + FloatBuffer normals = BufferUtils.createFloatBuffer( + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f, + 0f, 1f, 0f + ); + float uvDiameter = 5f; + FloatBuffer uvs = BufferUtils.createFloatBuffer( + uvDiameter, uvDiameter, + 0f, 0f, + uvDiameter, 0f, + uvDiameter, uvDiameter, + 0f, uvDiameter, + 0f, 0f + ); + float posRadius = 500f; + FloatBuffer positions = BufferUtils.createFloatBuffer( + +posRadius, 0f, +posRadius, + -posRadius, 0f, -posRadius, + -posRadius, 0f, +posRadius, + +posRadius, 0f, +posRadius, + +posRadius, 0f, -posRadius, + -posRadius, 0f, -posRadius + ); + Mesh mesh = new Mesh(); + mesh.setMode(mode); + mesh.setBuffer(VertexBuffer.Type.Normal, numAxes, normals); + mesh.setBuffer(VertexBuffer.Type.Position, numAxes, positions); + mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); + mesh.updateBound(); + + Geometry result = new Geometry("testGeometry" + mode, mesh); + return result; + } +}