From dff1c33d3e093a6ef7816a3870c0e8f4ae840592 Mon Sep 17 00:00:00 2001 From: Richard Tingle <6330028+richardTingle@users.noreply.github.com> Date: Sat, 26 Apr 2025 10:24:53 +0100 Subject: [PATCH] Create additional screenshot tests and add an auto message on screenshot test failure This adds screenshot tests for: TestIssue2076 TestMotionPath TestSimpleBumps This also: Automatically posts a comment if a screenshot test fails to help inform users what to do --- .github/workflows/screenshot-test-comment.yml | 117 +++++++++++ jme3-screenshot-tests/README.md | 5 +- .../animation/TestIssue2076.java | 143 +++++++++++++ .../animation/TestMotionPath.java | 188 ++++++++++++++++++ .../material/TestSimpleBumps.java | 125 ++++++++++++ ...imation.TestIssue2076.testIssue2076_f1.png | Bin 0 -> 16716 bytes ...tion.TestMotionPath.testMotionPath_f10.png | Bin 0 -> 8043 bytes ...tion.TestMotionPath.testMotionPath_f60.png | Bin 0 -> 9741 bytes ...al.TestSimpleBumps.testSimpleBumps_f10.png | Bin 0 -> 4169 bytes ...al.TestSimpleBumps.testSimpleBumps_f60.png | Bin 0 -> 4200 bytes 10 files changed, 576 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/screenshot-test-comment.yml create mode 100644 jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java create mode 100644 jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java create mode 100644 jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java create mode 100644 jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png create mode 100644 jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png create mode 100644 jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png create mode 100644 jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png create mode 100644 jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png diff --git a/.github/workflows/screenshot-test-comment.yml b/.github/workflows/screenshot-test-comment.yml new file mode 100644 index 0000000000..0d9642f07e --- /dev/null +++ b/.github/workflows/screenshot-test-comment.yml @@ -0,0 +1,117 @@ +name: Screenshot Test PR Comment + +# This workflow is designed to safely comment on PRs from forks +# It uses pull_request_target which has higher permissions than pull_request +# Security note: This workflow does NOT check out or execute code from the PR +# It only monitors the status of the ScreenshotTests job and posts comments +# (If this commenting was done in the main worflow it would not have the permissions +# to create a comment) + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + monitor-screenshot-tests: + name: Monitor Screenshot Tests and Comment + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + pull-requests: write + contents: read + steps: + - name: Wait for GitHub to register the workflow run + run: sleep 15 + + - name: Wait for Screenshot Tests to complete + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + check-name: 'Run Screenshot Tests' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success,skipped,failure + - name: Check Screenshot Tests status + id: check-status + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const ref = '${{ github.event.pull_request.head.sha }}'; + + // Get workflow runs for the PR + const runs = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + head_sha: ref + }); + + // Find the ScreenshotTests job + let screenshotTestRun = null; + for (const run of runs.data.workflow_runs) { + if (run.name === 'Build jMonkeyEngine') { + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: run.id + }); + + for (const job of jobs.data.jobs) { + if (job.name === 'Run Screenshot Tests') { + screenshotTestRun = job; + break; + } + } + + if (screenshotTestRun) break; + } + } + + if (!screenshotTestRun) { + console.log('Screenshot test job not found'); + return; + } + + // Check if the job failed + if (screenshotTestRun.conclusion === 'failure') { + core.setOutput('failed', 'true'); + } else { + core.setOutput('failed', 'false'); + } + - name: Find Existing Comment + uses: peter-evans/find-comment@v3 + id: existingCommentId + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Screenshot tests have failed. + + - name: Comment on PR if tests fail + if: steps.check-status.outputs.failed == 'true' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + 🖼️ **Screenshot tests have failed.** + + The purpose of these tests is to ensure that changes introduced in this PR don't break visual features. They are visual unit tests. + + 📄 **Where to find the report:** + - Go to the (failed run) > Summary > Artifacts > screenshot-test-report + - Download the zip and open jme3-screenshot-tests/build/reports/ScreenshotDiffReport.html + + ⚠️ **If you didn't expect to change anything visual:** + Fix your changes so the screenshot tests pass. + + ✅ **If you did mean to change things:** + Review the replacement images in jme3-screenshot-tests/build/changed-images to make sure they really are improvements and then replace and commit the replacement images at jme3-screenshot-tests/src/test/resources. + + ✨ **If you are creating entirely new tests:** + Find the new images in jme3-screenshot-tests/build/changed-images and commit the new images at jme3-screenshot-tests/src/test/resources. + + **Note;** it is very important that the committed reference images are created on the build pipeline, locally created images are not reliable. Similarly tests will fail locally but you can look at the report to check they are "visually similar". + + See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information + edit-mode: replace + comment-id: ${{ steps.existingCommentId.outputs.comment-id }} diff --git a/jme3-screenshot-tests/README.md b/jme3-screenshot-tests/README.md index 3945ac20cb..9215123bb9 100644 --- a/jme3-screenshot-tests/README.md +++ b/jme3-screenshot-tests/README.md @@ -1,7 +1,8 @@ # jme3-screenshot-tests -This module contains tests that compare screenshots of the JME3 test applications to reference images. The tests are run using -the following command: +This module contains tests that compare screenshots of the JME3 test applications to reference images. Think of these like visual unit tests + +The tests are run using the following command: ``` ./gradlew :jme3-screenshot-test:screenshotTest diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java new file mode 100644 index 0000000000..50435b1218 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 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 org.jmonkeyengine.screenshottests.animation; + +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.AmbientLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for JMonkeyEngine issue #2076: software skinning requires vertex + * normals. + * + *

If the issue is resolved, 2 copies of the Jaime model will be rendered in the screenshot. + * + *

If the issue is present, then the application will immediately crash, + * typically with a {@code NullPointerException}. + * + * @author Stephen Gold (original test) + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestIssue2076 extends ScreenshotTestBase { + + /** + * This test creates a scene with two Jaime models, one using the old animation system + * and one using the new animation system, both with software skinning and no vertex normals. + */ + @Test + public void testIssue2076() { + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Add ambient light + AmbientLight ambientLight = new AmbientLight(); + ambientLight.setColor(new ColorRGBA(1f, 1f, 1f, 1f)); + rootNode.addLight(ambientLight); + + /* + * The original Jaime model was chosen for testing because it includes + * tangent buffers (needed to trigger issue #2076) and uses the old + * animation system (so it can be easily used to test both systems). + */ + String assetPath = "Models/Jaime/Jaime.j3o"; + + // Test old animation system + Node oldJaime = (Node) assetManager.loadModel(assetPath); + rootNode.attachChild(oldJaime); + oldJaime.setLocalTranslation(-1f, 0f, 0f); + + // Enable software skinning + SkeletonControl skeletonControl = oldJaime.getControl(SkeletonControl.class); + skeletonControl.setHardwareSkinningPreferred(false); + + // Remove its vertex normals + Geometry oldGeometry = (Geometry) oldJaime.getChild(0); + Mesh oldMesh = oldGeometry.getMesh(); + oldMesh.clearBuffer(VertexBuffer.Type.Normal); + oldMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + + // Test new animation system + Node newJaime = (Node) assetManager.loadModel(assetPath); + AnimMigrationUtils.migrate(newJaime); + rootNode.attachChild(newJaime); + newJaime.setLocalTranslation(1f, 0f, 0f); + + // Enable software skinning + SkinningControl skinningControl = newJaime.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(false); + + // Remove its vertex normals + Geometry newGeometry = (Geometry) newJaime.getChild(0); + Mesh newMesh = newGeometry.getMesh(); + newMesh.clearBuffer(VertexBuffer.Type.Normal); + newMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + + // Position the camera to see both models + simpleApplication.getCamera().setLocation(new Vector3f(0f, 0f, 5f)); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + + @Override + public void update(float tpf) { + super.update(tpf); + } + }).run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java new file mode 100644 index 0000000000..28a5e042d2 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024 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 org.jmonkeyengine.screenshottests.animation; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the MotionPath functionality. + * + *

This test creates a teapot model that follows a predefined path with several waypoints. + * The animation is automatically started and screenshots are taken at frames 10 and 60 + * to capture the teapot at different positions along the path. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestMotionPath extends ScreenshotTestBase { + + /** + * This test creates a scene with a teapot following a motion path. + */ + @Test + public void testMotionPath() { + screenshotTest(new BaseAppState() { + private Spatial teapot; + private MotionPath path; + private MotionEvent motionControl; + private BitmapText wayPointsText; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + Node guiNode = simpleApplication.getGuiNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Set camera position + app.getCamera().setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f)); + + // Create the scene + createScene(rootNode, assetManager); + + // Create the motion path + path = new MotionPath(); + path.addWayPoint(new Vector3f(10, 3, 0)); + path.addWayPoint(new Vector3f(10, 3, 10)); + path.addWayPoint(new Vector3f(-40, 3, 10)); + path.addWayPoint(new Vector3f(-40, 3, 0)); + path.addWayPoint(new Vector3f(-40, 8, 0)); + path.addWayPoint(new Vector3f(10, 8, 0)); + path.addWayPoint(new Vector3f(10, 8, 10)); + path.addWayPoint(new Vector3f(15, 8, 10)); + path.enableDebugShape(assetManager, rootNode); + + // Create the motion event + motionControl = new MotionEvent(teapot, path); + motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation); + motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y)); + motionControl.setInitialDuration(10f); + motionControl.setSpeed(2f); + + // Create text for waypoint notifications + wayPointsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + wayPointsText.setSize(wayPointsText.getFont().getCharSet().getRenderedSize()); + guiNode.attachChild(wayPointsText); + + // Add listener for waypoint events + path.addListener(new MotionPathListener() { + @Override + public void onWayPointReach(MotionEvent control, int wayPointIndex) { + if (path.getNbWayPoints() == wayPointIndex + 1) { + wayPointsText.setText(control.getSpatial().getName() + " Finished!!! "); + } else { + wayPointsText.setText(control.getSpatial().getName() + " Reached way point " + wayPointIndex); + } + wayPointsText.setLocalTranslation( + (app.getCamera().getWidth() - wayPointsText.getLineWidth()) / 2, + app.getCamera().getHeight(), + 0); + } + }); + + // note that the ChaseCamera is self-initialising, so just creating this object attaches it + new ChaseCamera(getApplication().getCamera(), teapot); + + // Start the animation automatically + motionControl.play(); + } + + private void createScene(Node rootNode, AssetManager assetManager) { + // Create materials + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 1f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.DarkGray); + mat.setColor("Specular", ColorRGBA.White.mult(0.6f)); + + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Black); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Black); + + // Create teapot + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setName("Teapot"); + teapot.setLocalScale(3); + teapot.setMaterial(mat); + rootNode.attachChild(teapot); + + // Create ground + Geometry soil = new Geometry("soil", new Box(50, 1, 50)); + soil.setLocalTranslation(0, -1, 0); + soil.setMaterial(matSoil); + rootNode.attachChild(soil); + + // Add light + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(0, -1, 0).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + }) + .setFramesToTakeScreenshotsOn(10, 60) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java new file mode 100644 index 0000000000..0e4e53df54 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 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 org.jmonkeyengine.screenshottests.material; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the SimpleBumps material test. + * + *

This test creates a quad with a bump map material and a point light that orbits around it. + * The light's position is represented by a small red sphere. Screenshots are taken at frames 10 and 60 + * to capture the light at different positions in its orbit. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestSimpleBumps extends ScreenshotTestBase { + + /** + * This test creates a scene with a bump-mapped quad and an orbiting light. + */ + @Test + public void testSimpleBumps() { + screenshotTest(new BaseAppState() { + private float angle; + private PointLight pl; + private Spatial lightMdl; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Create quad with bump map material + Quad quadMesh = new Quad(1, 1); + Geometry sphere = new Geometry("Rock Ball", quadMesh); + Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m"); + sphere.setMaterial(mat); + MikktspaceTangentGenerator.generate(sphere); + rootNode.attachChild(sphere); + + // Create light representation + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + // Create point light + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + + @Override + public void update(float tpf) { + super.update(tpf); + + angle += tpf * 2f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + }) + .setFramesToTakeScreenshotsOn(10, 60) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png new file mode 100644 index 0000000000000000000000000000000000000000..f4cbc42002b079d76e02e2df02ff35cf03138910 GIT binary patch literal 16716 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 E0KmlX>i_@% literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png new file mode 100644 index 0000000000000000000000000000000000000000..cca8ae963f24fa936fb47c08b67b5b4ba84247ec GIT binary patch literal 9741 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 literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png new file mode 100644 index 0000000000000000000000000000000000000000..4608681de38c5dc8d020c7f63ecc1f9c7a5c1ea7 GIT binary patch literal 4169 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 K!-M`ifBy|;u8X7q literal 0 HcmV?d00001