Skip to content

Commit 8ff17a8

Browse files
authored
Merge pull request #2518 from richardTingle/screenshotTests-toon-billboard-fog-scattering
Add new screenshot tests for bill boarding, cartoons, fog and scattering
2 parents 8751505 + 4186389 commit 8ff17a8

11 files changed

Lines changed: 601 additions & 0 deletions

.github/workflows/screenshot-test-comment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,7 @@ jobs:
113113
**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".
114114
115115
See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information
116+
117+
Contact @richardTingle (aka richtea) for guidance if required
116118
edit-mode: replace
117119
comment-id: ${{ steps.existingCommentId.outputs.comment-id }}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (c) 2025 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package org.jmonkeyengine.screenshottests.model.shape;
33+
34+
import com.jme3.app.Application;
35+
import com.jme3.app.SimpleApplication;
36+
import com.jme3.app.state.BaseAppState;
37+
import com.jme3.material.Material;
38+
import com.jme3.math.ColorRGBA;
39+
import com.jme3.math.Vector3f;
40+
import com.jme3.scene.Geometry;
41+
import com.jme3.scene.Mesh;
42+
import com.jme3.scene.Node;
43+
import com.jme3.scene.control.BillboardControl;
44+
import com.jme3.scene.debug.Arrow;
45+
import com.jme3.scene.debug.Grid;
46+
import com.jme3.scene.shape.Quad;
47+
import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
48+
import org.junit.jupiter.api.TestInfo;
49+
import org.junit.jupiter.params.ParameterizedTest;
50+
import org.junit.jupiter.params.provider.Arguments;
51+
import org.junit.jupiter.params.provider.MethodSource;
52+
53+
import java.util.stream.Stream;
54+
55+
/**
56+
* Screenshot test for the Billboard test.
57+
*
58+
* <p>This test creates three different billboard alignments (Screen, Camera, AxialY)
59+
* with different colored quads. Each billboard is positioned at a different x-coordinate
60+
* and has a blue Z-axis arrow attached to it. Screenshots are taken from three different angles:
61+
* front, above, and right.
62+
*
63+
* @author Richard Tingle (screenshot test adaptation)
64+
*/
65+
@SuppressWarnings("OptionalGetWithoutIsPresent")
66+
public class TestBillboard extends ScreenshotTestBase {
67+
68+
private static Stream<Arguments> testParameters() {
69+
return Stream.of(
70+
Arguments.of("fromFront", new Vector3f(0, 1, 15)),
71+
Arguments.of("fromAbove", new Vector3f(0, 15, 6)),
72+
Arguments.of("fromRight", new Vector3f(-15, 10, 5))
73+
);
74+
}
75+
76+
/**
77+
* A billboard test with the specified camera parameters.
78+
*
79+
* @param cameraPosition The position of the camera
80+
*/
81+
@ParameterizedTest(name = "{0}")
82+
@MethodSource("testParameters")
83+
public void testBillboard(String testName, Vector3f cameraPosition, TestInfo testInfo) {
84+
String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
85+
86+
screenshotTest(new BaseAppState() {
87+
@Override
88+
protected void initialize(Application app) {
89+
SimpleApplication simpleApplication = (SimpleApplication) app;
90+
Node rootNode = simpleApplication.getRootNode();
91+
92+
// Set up the camera
93+
simpleApplication.getCamera().setLocation(cameraPosition);
94+
simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
95+
96+
// Set background color
97+
simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.DarkGray);
98+
99+
// Create grid
100+
Geometry grid = makeShape(simpleApplication, "DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray);
101+
grid.center().move(0, 0, 0);
102+
rootNode.attachChild(grid);
103+
104+
// Create billboards with different alignments
105+
Node node = createBillboard(simpleApplication, BillboardControl.Alignment.Screen, ColorRGBA.Red);
106+
node.setLocalTranslation(-6f, 0, 0);
107+
rootNode.attachChild(node);
108+
109+
node = createBillboard(simpleApplication, BillboardControl.Alignment.Camera, ColorRGBA.Green);
110+
node.setLocalTranslation(-2f, 0, 0);
111+
rootNode.attachChild(node);
112+
113+
node = createBillboard(simpleApplication, BillboardControl.Alignment.AxialY, ColorRGBA.Blue);
114+
node.setLocalTranslation(2f, 0, 0);
115+
rootNode.attachChild(node);
116+
}
117+
118+
@Override
119+
protected void cleanup(Application app) {}
120+
121+
@Override
122+
protected void onEnable() {}
123+
124+
@Override
125+
protected void onDisable() {}
126+
127+
private Node createBillboard(SimpleApplication app, BillboardControl.Alignment alignment, ColorRGBA color) {
128+
Node node = new Node("Parent");
129+
Quad quad = new Quad(2, 2);
130+
Geometry g = makeShape(app, alignment.name(), quad, color);
131+
BillboardControl bc = new BillboardControl();
132+
bc.setAlignment(alignment);
133+
g.addControl(bc);
134+
node.attachChild(g);
135+
node.attachChild(makeShape(app, "ZAxis", new Arrow(Vector3f.UNIT_Z), ColorRGBA.Blue));
136+
return node;
137+
}
138+
139+
private Geometry makeShape(SimpleApplication app, String name, Mesh shape, ColorRGBA color) {
140+
Geometry geo = new Geometry(name, shape);
141+
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
142+
mat.setColor("Color", color);
143+
geo.setMaterial(mat);
144+
return geo;
145+
}
146+
})
147+
.setBaseImageFileName(imageName)
148+
.setFramesToTakeScreenshotsOn(1)
149+
.run();
150+
}
151+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright (c) 2025 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package org.jmonkeyengine.screenshottests.post;
33+
34+
import com.jme3.app.Application;
35+
import com.jme3.app.SimpleApplication;
36+
import com.jme3.app.state.BaseAppState;
37+
import com.jme3.light.DirectionalLight;
38+
import com.jme3.material.Material;
39+
import com.jme3.math.ColorRGBA;
40+
import com.jme3.math.FastMath;
41+
import com.jme3.math.Vector3f;
42+
import com.jme3.post.FilterPostProcessor;
43+
import com.jme3.post.filters.CartoonEdgeFilter;
44+
import com.jme3.renderer.Caps;
45+
import com.jme3.scene.Geometry;
46+
import com.jme3.scene.Node;
47+
import com.jme3.scene.Spatial;
48+
import com.jme3.scene.Spatial.CullHint;
49+
import com.jme3.texture.Texture;
50+
import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
51+
import org.junit.jupiter.api.Test;
52+
53+
/**
54+
* Screenshot test for the CartoonEdge filter.
55+
*
56+
* <p>This test creates a scene with a monkey head model that has a cartoon/cel-shaded effect
57+
* applied to it. The CartoonEdgeFilter is used to create yellow outlines around the edges
58+
* of the model, and a toon shader is applied to the model's material to create the cel-shaded look.
59+
*
60+
* @author Richard Tingle (screenshot test adaptation)
61+
*/
62+
public class TestCartoonEdge extends ScreenshotTestBase {
63+
64+
/**
65+
* This test creates a scene with a cartoon-shaded monkey head model.
66+
*/
67+
@Test
68+
public void testCartoonEdge() {
69+
screenshotTest(new BaseAppState() {
70+
@Override
71+
protected void initialize(Application app) {
72+
SimpleApplication simpleApplication = (SimpleApplication) app;
73+
Node rootNode = simpleApplication.getRootNode();
74+
75+
simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.Gray);
76+
77+
simpleApplication.getCamera().setLocation(new Vector3f(-1, 2, -5));
78+
simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
79+
simpleApplication.getCamera().setFrustumFar(300);
80+
81+
rootNode.setCullHint(CullHint.Never);
82+
83+
setupLighting(rootNode);
84+
85+
setupModel(simpleApplication, rootNode);
86+
87+
setupFilters(simpleApplication);
88+
}
89+
90+
private void setupFilters(SimpleApplication app) {
91+
if (app.getRenderer().getCaps().contains(Caps.GLSL100)) {
92+
FilterPostProcessor fpp = new FilterPostProcessor(app.getAssetManager());
93+
94+
CartoonEdgeFilter toon = new CartoonEdgeFilter();
95+
toon.setEdgeColor(ColorRGBA.Yellow);
96+
fpp.addFilter(toon);
97+
app.getViewPort().addProcessor(fpp);
98+
}
99+
}
100+
101+
private void setupLighting(Node rootNode) {
102+
DirectionalLight dl = new DirectionalLight();
103+
dl.setDirection(new Vector3f(-1, -1, 1).normalizeLocal());
104+
dl.setColor(new ColorRGBA(2, 2, 2, 1));
105+
rootNode.addLight(dl);
106+
}
107+
108+
private void setupModel(SimpleApplication app, Node rootNode) {
109+
Spatial model = app.getAssetManager().loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml");
110+
makeToonish(app, model);
111+
model.rotate(0, FastMath.PI, 0);
112+
rootNode.attachChild(model);
113+
}
114+
115+
private void makeToonish(SimpleApplication app, Spatial spatial) {
116+
if (spatial instanceof Node) {
117+
Node n = (Node) spatial;
118+
for (Spatial child : n.getChildren()) {
119+
makeToonish(app, child);
120+
}
121+
} else if (spatial instanceof Geometry) {
122+
Geometry g = (Geometry) spatial;
123+
Material m = g.getMaterial();
124+
if (m.getMaterialDef().getMaterialParam("UseMaterialColors") != null) {
125+
Texture t = app.getAssetManager().loadTexture("Textures/ColorRamp/toon.png");
126+
m.setTexture("ColorRamp", t);
127+
m.setBoolean("UseMaterialColors", true);
128+
m.setColor("Specular", ColorRGBA.Black);
129+
m.setColor("Diffuse", ColorRGBA.White);
130+
m.setBoolean("VertexLighting", true);
131+
}
132+
}
133+
}
134+
135+
@Override
136+
protected void cleanup(Application app) {
137+
}
138+
139+
@Override
140+
protected void onEnable() {
141+
}
142+
143+
@Override
144+
protected void onDisable() {
145+
}
146+
})
147+
.setFramesToTakeScreenshotsOn(1)
148+
.run();
149+
}
150+
}

0 commit comments

Comments
 (0)