Skip to content

Commit 3fd4c97

Browse files
committedJun 24, 2021
8234920: Add SpotLight to the selection of 3D light types
Reviewed-by: kcr, arapte
1 parent 063bfe8 commit 3fd4c97

40 files changed

+1678
-439
lines changed
 
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.sun.javafx.scene;
27+
28+
import com.sun.javafx.sg.prism.NGNode;
29+
import com.sun.javafx.util.Utils;
30+
import javafx.scene.SpotLight;
31+
import javafx.scene.Node;
32+
33+
/**
34+
* Used to access internal methods of SpotLight.
35+
*/
36+
public class SpotLightHelper extends PointLightHelper {
37+
38+
private static final SpotLightHelper theInstance;
39+
private static SpotLightAccessor spotLightAccessor;
40+
41+
static {
42+
theInstance = new SpotLightHelper();
43+
Utils.forceInit(SpotLight.class);
44+
}
45+
46+
private static SpotLightHelper getInstance() {
47+
return theInstance;
48+
}
49+
50+
public static void initHelper(SpotLight spotLight) {
51+
setHelper(spotLight, getInstance());
52+
}
53+
54+
@Override
55+
protected NGNode createPeerImpl(Node node) {
56+
return spotLightAccessor.doCreatePeer(node);
57+
}
58+
59+
@Override
60+
protected void updatePeerImpl(Node node) {
61+
super.updatePeerImpl(node);
62+
spotLightAccessor.doUpdatePeer(node);
63+
}
64+
65+
public static void setSpotLightAccessor(final SpotLightAccessor newAccessor) {
66+
if (spotLightAccessor != null) {
67+
throw new IllegalStateException("Accessor already exists");
68+
}
69+
70+
spotLightAccessor = newAccessor;
71+
}
72+
73+
public interface SpotLightAccessor {
74+
NGNode doCreatePeer(Node node);
75+
void doUpdatePeer(Node node);
76+
}
77+
}

‎modules/javafx.graphics/src/main/java/com/sun/javafx/sg/prism/NGPointLight.java

+46
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
package com.sun.javafx.sg.prism;
2727

28+
import javafx.geometry.Point3D;
29+
2830
/**
2931
* The peer of the {@code PointLight} class. Holds the default values of {@code PointLight}'s
3032
* properties and updates the visuals via {@link NGNode#visualsChanged} when one of the current
@@ -41,6 +43,18 @@ public class NGPointLight extends NGLightBase {
4143
/** Max range default value */
4244
private static final float DEFAULT_MAX_RANGE = Float.POSITIVE_INFINITY;
4345

46+
/**
47+
* The direction of a {@code SpotLight} that simulates a {@code PointLight}.
48+
* Since the light radiates equally in all directions, this value is meaningless.
49+
**/
50+
private static final Point3D SIMULATED_DIRECTION = new Point3D(0, 0, 1);
51+
/** The inner angle value of a {@code SpotLight} that simulates a {@code PointLight} */
52+
private static final float SIMULATED_INNER_ANGLE = 0;
53+
/** The outer angle value of a {@code SpotLight} that simulates a {@code PointLight} */
54+
private static final float SIMULATED_OUTER_ANGLE = 180;
55+
/** The falloff value of a {@code SpotLight} that simulates a {@code PointLight} */
56+
private static final float SIMULATED_FALLOFF = 0;
57+
4458
public NGPointLight() {
4559
}
4660

@@ -60,6 +74,38 @@ public static float getDefaultMaxRange() {
6074
return DEFAULT_MAX_RANGE;
6175
}
6276

77+
public static Point3D getSimulatedDirection() {
78+
return SIMULATED_DIRECTION;
79+
}
80+
81+
public static float getSimulatedInnerAngle() {
82+
return SIMULATED_INNER_ANGLE;
83+
}
84+
85+
public static float getSimulatedOuterAngle() {
86+
return SIMULATED_OUTER_ANGLE;
87+
}
88+
89+
public static float getSimulatedFalloff() {
90+
return SIMULATED_FALLOFF;
91+
}
92+
93+
public Point3D getDirection() {
94+
return SIMULATED_DIRECTION;
95+
}
96+
97+
public float getInnerAngle() {
98+
return SIMULATED_INNER_ANGLE;
99+
}
100+
101+
public float getOuterAngle() {
102+
return SIMULATED_OUTER_ANGLE;
103+
}
104+
105+
public float getFalloff() {
106+
return SIMULATED_FALLOFF;
107+
}
108+
63109

64110
private float ca = DEFAULT_CA;
65111

‎modules/javafx.graphics/src/main/java/com/sun/javafx/sg/prism/NGShape3D.java

+81-66
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import javafx.scene.shape.DrawMode;
3232
import com.sun.javafx.geom.Vec3d;
3333
import com.sun.javafx.geom.transform.Affine3D;
34+
import com.sun.javafx.util.Utils;
3435
import com.sun.prism.Graphics;
3536
import com.sun.prism.Material;
3637
import com.sun.prism.MeshView;
@@ -115,95 +116,109 @@ private void renderMeshView(Graphics g) {
115116
}
116117

117118
// Setup lights
118-
int pointLightIdx = 0;
119+
int lightIndex = 0;
119120
if (g.getLights() == null || g.getLights()[0] == null) {
120121
// If no lights are in scene apply default light. Default light
121122
// is a single white point light at camera eye position.
122123
meshView.setAmbientLight(0.0f, 0.0f, 0.0f);
123124
Vec3d cameraPos = g.getCameraNoClone().getPositionInWorld(null);
124-
meshView.setPointLight(pointLightIdx++,
125-
(float)cameraPos.x,
126-
(float)cameraPos.y,
127-
(float)cameraPos.z,
128-
1.0f, 1.0f, 1.0f, 1.0f,
129-
NGPointLight.getDefaultCa(),
130-
NGPointLight.getDefaultLa(),
131-
NGPointLight.getDefaultQa(),
132-
NGPointLight.getDefaultMaxRange());
125+
meshView.setLight(lightIndex++,
126+
(float) cameraPos.x,
127+
(float) cameraPos.y,
128+
(float) cameraPos.z,
129+
1.0f, 1.0f, 1.0f, 1.0f,
130+
NGPointLight.getDefaultCa(),
131+
NGPointLight.getDefaultLa(),
132+
NGPointLight.getDefaultQa(),
133+
NGPointLight.getDefaultMaxRange(),
134+
(float) NGPointLight.getSimulatedDirection().getX(),
135+
(float) NGPointLight.getSimulatedDirection().getY(),
136+
(float) NGPointLight.getSimulatedDirection().getZ(),
137+
NGPointLight.getSimulatedInnerAngle(),
138+
NGPointLight.getSimulatedOuterAngle(),
139+
NGPointLight.getSimulatedFalloff());
133140
} else {
134141
float ambientRed = 0.0f;
135142
float ambientBlue = 0.0f;
136143
float ambientGreen = 0.0f;
137144

138-
for (int i = 0; i < g.getLights().length; i++) {
139-
NGLightBase lightBase = g.getLights()[i];
145+
for (NGLightBase lightBase : g.getLights()) {
140146
if (lightBase == null) {
141147
// The array of lights can have nulls
142148
break;
143-
} else if (lightBase.affects(this)) {
144-
float rL = lightBase.getColor().getRed();
145-
float gL = lightBase.getColor().getGreen();
146-
float bL = lightBase.getColor().getBlue();
147-
/* TODO: 3D
148-
* There is a limit on the number of point lights that can affect
149-
* a 3D shape. (Currently we simply select the first 3)
150-
* Thus it is important to select the most relevant lights.
151-
*
152-
* One such way would be to sort lights according to
153-
* intensity, which becomes especially relevant when lights
154-
* are attenuated. Only the most intense set of lights
155-
* would be set.
156-
* The approximate intesity a light will have on a given
157-
* shape, could be defined by:
158-
*/
159-
// // Where d is distance from point light
160-
// float attenuationFactor = 1/(c + cL * d + cQ * d * d);
161-
// float intensity = rL * 0.299f + gL * 0.587f + bL * 0.114f;
162-
// intensity *= attenuationFactor;
163-
if (lightBase instanceof NGPointLight) {
164-
NGPointLight light = (NGPointLight)lightBase;
165-
if (rL != 0.0f || gL != 0.0f || bL != 0.0f) {
166-
Affine3D lightWT = light.getWorldTransform();
167-
meshView.setPointLight(pointLightIdx++,
168-
(float)lightWT.getMxt(),
169-
(float)lightWT.getMyt(),
170-
(float)lightWT.getMzt(),
171-
rL, gL, bL, 1.0f,
172-
light.getCa(),
173-
light.getLa(),
174-
light.getQa(),
175-
light.getMaxRange());
176-
}
177-
} else if (lightBase instanceof NGAmbientLight) {
178-
// Accumulate ambient lights
179-
ambientRed += rL;
180-
ambientGreen += gL;
181-
ambientBlue += bL;
182-
}
149+
}
150+
if (!lightBase.affects(this)) {
151+
continue;
152+
}
153+
// Transparent component is ignored
154+
float rL = lightBase.getColor().getRed();
155+
float gL = lightBase.getColor().getGreen();
156+
float bL = lightBase.getColor().getBlue();
157+
// Black color is ignored
158+
if (rL == 0.0f && gL == 0.0f && bL == 0.0f) {
159+
continue;
160+
}
161+
/* TODO: 3D
162+
* There is a limit on the number of point lights that can affect
163+
* a 3D shape. (Currently we simply select the first 3)
164+
* Thus it is important to select the most relevant lights.
165+
*
166+
* One such way would be to sort lights according to
167+
* intensity, which becomes especially relevant when lights
168+
* are attenuated. Only the most intense set of lights
169+
* would be set.
170+
* The approximate intensity a light will have on a given
171+
* shape, could be defined by:
172+
*
173+
* Where d is distance from point light
174+
* float attenuationFactor = 1/(c + cL * d + cQ * d * d);
175+
* float intensity = rL * 0.299f + gL * 0.587f + bL * 0.114f;
176+
* intensity *= attenuationFactor;
177+
*/
178+
if (lightBase instanceof NGPointLight) {
179+
var light = (NGPointLight) lightBase;
180+
Affine3D lightWT = light.getWorldTransform();
181+
meshView.setLight(lightIndex++,
182+
(float) lightWT.getMxt(),
183+
(float) lightWT.getMyt(),
184+
(float) lightWT.getMzt(),
185+
rL, gL, bL, 1.0f,
186+
light.getCa(),
187+
light.getLa(),
188+
light.getQa(),
189+
light.getMaxRange(),
190+
(float) light.getDirection().getX(),
191+
(float) light.getDirection().getY(),
192+
(float) light.getDirection().getZ(),
193+
light.getInnerAngle(),
194+
light.getOuterAngle(),
195+
light.getFalloff());
196+
} else if (lightBase instanceof NGAmbientLight) {
197+
// Accumulate ambient lights
198+
ambientRed += rL;
199+
ambientGreen += gL;
200+
ambientBlue += bL;
183201
}
184202
}
185-
ambientRed = saturate(ambientRed);
186-
ambientGreen = saturate(ambientGreen);
187-
ambientBlue = saturate(ambientBlue);
203+
ambientRed = Utils.clamp(0, ambientRed, 1);
204+
ambientGreen = Utils.clamp(0, ambientGreen, 1);
205+
ambientBlue = Utils.clamp(0, ambientBlue, 1);
188206
meshView.setAmbientLight(ambientRed, ambientGreen, ambientBlue);
189207
}
190208
// TODO: 3D Required for D3D implementation of lights, which is limited to 3
191-
while (pointLightIdx < 3) {
192-
// Reset any previously set lights
193-
meshView.setPointLight(pointLightIdx++,
194-
0, 0, 0, // x y z
195-
0, 0, 0, 0, // r g b w
196-
1, 0, 0, 0); // ca la qa maxRange
209+
210+
while (lightIndex < 3) { // Reset any previously set lights
211+
meshView.setLight(lightIndex++,
212+
0, 0, 0, // x y z
213+
0, 0, 0, 0, // r g b w
214+
1, 0, 0, 0, // ca la qa maxRange
215+
0, 0, 0, // dirX Y Z
216+
0, 0, 0); // inner outer falloff
197217
}
198218

199219
meshView.render(g);
200220
}
201221

202-
// Clamp between [0, 1]
203-
private static float saturate(float value) {
204-
return value < 1.0f ? ((value < 0.0f) ? 0.0f : value) : 1.0f;
205-
}
206-
207222
public void setMesh(NGTriangleMesh triangleMesh) {
208223
this.mesh = triangleMesh;
209224
meshView = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.sun.javafx.sg.prism;
27+
28+
import com.sun.javafx.geom.Vec3d;
29+
30+
import javafx.geometry.Point3D;
31+
32+
/**
33+
* The peer of the {@code SpotLight} class. Holds the default values of {@code SpotLight}'s
34+
* properties and updates the visuals via {@link NGNode#visualsChanged} when one of the current
35+
* values changes. The peer receives its changes by {@link javafx.scene.SpotLight#doUpdatePeer} calls.
36+
*/
37+
public class NGSpotLight extends NGPointLight {
38+
39+
/** Direction default value */
40+
private static final Point3D DEFAULT_DIRECTION = new Point3D(0, 0, 1);
41+
/** Inner angle default value */
42+
private static final float DEFAULT_INNER_ANGLE = 0;
43+
/** Outer angle default value */
44+
private static final float DEFAULT_OUTER_ANGLE = 30;
45+
/** Falloff default value */
46+
private static final float DEFAULT_FALLOFF = 1;
47+
48+
public NGSpotLight() {
49+
}
50+
51+
public static Point3D getDefaultDirection() {
52+
return DEFAULT_DIRECTION;
53+
}
54+
55+
public static float getDefaultInnerAngle() {
56+
return DEFAULT_INNER_ANGLE;
57+
}
58+
59+
public static float getDefaultOuterAngle() {
60+
return DEFAULT_OUTER_ANGLE;
61+
}
62+
63+
public static float getDefaultFalloff() {
64+
return DEFAULT_FALLOFF;
65+
}
66+
67+
68+
private Point3D direction = DEFAULT_DIRECTION;
69+
private final Vec3d effectiveDir = new Vec3d();
70+
71+
@Override
72+
public Point3D getDirection() {
73+
var dir = new Vec3d(direction.getX(), direction.getY(), direction.getZ());
74+
getWorldTransform().deltaTransform(dir, effectiveDir);
75+
return new Point3D(effectiveDir.x, effectiveDir.y, effectiveDir.z);
76+
}
77+
78+
public void setDirection(Point3D direction) {
79+
if (!this.direction.equals(direction)) {
80+
this.direction = direction;
81+
visualsChanged();
82+
}
83+
}
84+
85+
86+
private float innerAngle = DEFAULT_INNER_ANGLE;
87+
88+
@Override
89+
public float getInnerAngle() {
90+
return innerAngle;
91+
}
92+
93+
public void setInnerAngle(float innerAngle) {
94+
if (this.innerAngle != innerAngle) {
95+
this.innerAngle = innerAngle;
96+
visualsChanged();
97+
}
98+
}
99+
100+
101+
private float outerAngle = DEFAULT_OUTER_ANGLE;
102+
103+
@Override
104+
public float getOuterAngle() {
105+
return outerAngle;
106+
}
107+
108+
public void setOuterAngle(float outerAngle) {
109+
if (this.outerAngle != outerAngle) {
110+
this.outerAngle = outerAngle;
111+
visualsChanged();
112+
}
113+
}
114+
115+
116+
private float falloff = DEFAULT_FALLOFF;
117+
118+
@Override
119+
public float getFalloff() {
120+
return falloff;
121+
}
122+
123+
public void setFalloff(float falloff) {
124+
if (this.falloff != falloff) {
125+
this.falloff = falloff;
126+
visualsChanged();
127+
}
128+
}
129+
}

‎modules/javafx.graphics/src/main/java/com/sun/prism/MeshView.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ public interface MeshView extends GraphicsResource {
4646

4747
public void setAmbientLight(float r, float g, float b);
4848

49-
public void setPointLight(int index,
50-
float x, float y, float z,
51-
float r, float g, float b, float w,
52-
float ca, float la, float qa, float maxRange);
49+
public void setLight(int index, float x, float y, float z, float r, float g, float b, float w,
50+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
51+
float innerAngle, float outerAngle, float falloff);
5352

5453
public void render(Graphics g);
5554

‎modules/javafx.graphics/src/main/java/com/sun/prism/d3d/D3DContext.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,9 @@ private static native void nSetWireframe(long pContext, long nativeMeshView,
473473
boolean wireframe);
474474
private static native void nSetAmbientLight(long pContext, long nativeMeshView,
475475
float r, float g, float b);
476-
private static native void nSetPointLight(long pContext, long nativeMeshView,
477-
int index, float x, float y, float z, float r, float g, float b, float w,
478-
float ca, float la, float qa, float maxRange);
476+
private static native void nSetLight(long pContext, long nativeMeshView,
477+
int index, float x, float y, float z, float r, float g, float b, float w, float ca, float la, float qa,
478+
float maxRange, float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff);
479479
private static native void nRenderMeshView(long pContext, long nativeMeshView);
480480
private static native int nDrawIndexedQuads(long pContext,
481481
float coords[], byte colors[], int numVertices);
@@ -610,9 +610,11 @@ void setAmbientLight(long nativeMeshView, float r, float g, float b) {
610610
nSetAmbientLight(pContext, nativeMeshView, r, g, b);
611611
}
612612

613-
void setPointLight(long nativeMeshView, int index, float x, float y, float z,
614-
float r, float g, float b, float w, float ca, float la, float qa, float maxRange) {
615-
nSetPointLight(pContext, nativeMeshView, index, x, y, z, r, g, b, w, ca, la, qa, maxRange);
613+
void setLight(long nativeMeshView, int index, float x, float y, float z, float r, float g, float b, float w,
614+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
615+
float innerAngle, float outerAngle, float falloff) {
616+
nSetLight(pContext, nativeMeshView, index, x, y, z, r, g, b, w, ca, la, qa, maxRange,
617+
dirX, dirY, dirZ, innerAngle, outerAngle, falloff);
616618
}
617619

618620
@Override

‎modules/javafx.graphics/src/main/java/com/sun/prism/d3d/D3DMeshView.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,13 @@ public void setAmbientLight(float r, float g, float b) {
8282
}
8383

8484
@Override
85-
public void setPointLight(int index, float x, float y, float z, float r, float g, float b, float w,
86-
float ca, float la, float qa, float maxRange) {
85+
public void setLight(int index, float x, float y, float z, float r, float g, float b, float w,
86+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
87+
float innerAngle, float outerAngle, float falloff) {
8788
// NOTE: We only support up to 3 point lights at the present
8889
if (index >= 0 && index <= 2) {
89-
context.setPointLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange);
90+
context.setLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange,
91+
dirX, dirY, dirZ, innerAngle, outerAngle, falloff);
9092
}
9193
}
9294

‎modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2Context.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,11 @@ void setAmbientLight(long nativeHandle, float r, float g, float b) {
449449
glContext.setAmbientLight(nativeHandle, r, g, b);
450450
}
451451

452-
void setPointLight(long nativeHandle, int index, float x, float y, float z, float r, float g, float b, float w,
453-
float ca, float la, float qa, float maxRange) {
454-
glContext.setPointLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange);
452+
void setLight(long nativeHandle, int index, float x, float y, float z, float r, float g, float b, float w,
453+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
454+
float innerAngle, float outerAngle, float falloff) {
455+
glContext.setLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange, dirX, dirY, dirZ,
456+
innerAngle, outerAngle, falloff);
455457
}
456458

457459
@Override

‎modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2Light.java

+21-8
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,31 @@ class ES2Light {
3333
float x, y, z = 0;
3434
float r, g, b, w = 1;
3535
float ca, la, qa, maxRange;
36+
float dirX, dirY, dirZ;
37+
float innerAngle, outerAngle, falloff;
3638

37-
ES2Light(float ix, float iy, float iz, float ir, float ig, float ib, float iw, float ca, float la, float qa, float maxRange) {
38-
x = ix;
39-
y = iy;
40-
z = iz;
41-
r = ir;
42-
g = ig;
43-
b = ib;
44-
w = iw;
39+
ES2Light(float x, float y, float z, float r, float g, float b, float w, float ca, float la, float qa,
40+
float maxRange, float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff) {
41+
this.x = x;
42+
this.y = y;
43+
this.z = z;
44+
this.r = r;
45+
this.g = g;
46+
this.b = b;
47+
this.w = w;
4548
this.ca = ca;
4649
this.la = la;
4750
this.qa = qa;
4851
this.maxRange = maxRange;
52+
this.dirX = dirX;
53+
this.dirY = dirY;
54+
this.dirZ = dirZ;
55+
this.innerAngle = innerAngle;
56+
this.outerAngle = outerAngle;
57+
this.falloff = falloff;
58+
}
59+
60+
boolean isPointLight() {
61+
return falloff == 0 && outerAngle == 180;
4962
}
5063
}

‎modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2MeshView.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,19 @@ float getAmbientLightBlue() {
101101
}
102102

103103
@Override
104-
public void setPointLight(int index, float x, float y, float z, float r, float g, float b, float w,
105-
float ca, float la, float qa, float maxRange) {
104+
public void setLight(int index, float x, float y, float z, float r, float g, float b, float w,
105+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
106+
float innerAngle, float outerAngle, float falloff) {
106107
// NOTE: We only support up to 3 point lights at the present
107108
if (index >= 0 && index <= 2) {
108-
lights[index] = new ES2Light(x, y, z, r, g, b, w, ca, la, qa, maxRange);
109-
context.setPointLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange);
109+
lights[index] = new ES2Light(x, y, z, r, g, b, w, ca, la, qa, maxRange, dirX, dirY, dirZ, innerAngle,
110+
outerAngle, falloff);
111+
context.setLight(nativeHandle, index, x, y, z, r, g, b, w, ca, la, qa, maxRange, dirX, dirY, dirZ,
112+
innerAngle, outerAngle, falloff);
110113
}
111114
}
112115

113-
ES2Light[] getPointLights() {
116+
ES2Light[] getLights() {
114117
return lights;
115118
}
116119

‎modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2PhongShader.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ static ES2Shader getShader(ES2MeshView meshView, ES2Context context) {
146146
}
147147

148148
int numLights = 0;
149-
for (ES2Light light : meshView.getPointLights()) {
149+
for (ES2Light light : meshView.getLights()) {
150150
if (light != null && light.w > 0) { numLights++; }
151151
}
152152

@@ -203,15 +203,36 @@ static void setShaderParamaters(ES2Shader shader, ES2MeshView meshView, ES2Conte
203203
shader.setConstant("ambientColor", meshView.getAmbientLightRed(),
204204
meshView.getAmbientLightGreen(), meshView.getAmbientLightBlue());
205205

206-
int i = 0;
207-
for (ES2Light light : meshView.getPointLights()) {
206+
for (int i = 0; i < meshView.getLights().length; i++) {
207+
ES2Light light = meshView.getLights()[i];
208208
if (light != null && light.w > 0) {
209-
shader.setConstant("lights[" + i + "].pos", light.x, light.y, light.z, light.w);
210-
shader.setConstant("lights[" + i + "].color", light.r, light.g, light.b);
211-
shader.setConstant("lights[" + i + "].attn", light.ca, light.la, light.qa);
212-
shader.setConstant("lights[" + i + "].range", light.maxRange);
213-
i++;
209+
setLightConstants(i, shader, light);
214210
}
215211
}
216212
}
213+
214+
private static void setLightConstants(int i, ES2Shader shader, ES2Light light) {
215+
shader.setConstant("lights[" + i + "].pos", light.x, light.y, light.z, light.w);
216+
shader.setConstant("lights[" + i + "].color", light.r, light.g, light.b);
217+
shader.setConstant("lights[" + i + "].attn", light.ca, light.la, light.qa);
218+
shader.setConstant("lights[" + i + "].range", light.maxRange);
219+
if (light.isPointLight()) {
220+
shader.setConstant("lights[" + i + "].dir", 0f, 0f, 1f);
221+
shader.setConstant("lights[" + i + "].cosOuter", -1f); // cos(180)
222+
shader.setConstant("lights[" + i + "].denom", 2f); // cos(0) - cos(180)
223+
shader.setConstant("lights[" + i + "].falloff", 0f);
224+
} else {
225+
float dirX = light.dirX;
226+
float dirY = light.dirY;
227+
float dirZ = light.dirZ;
228+
float length = (float) Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
229+
shader.setConstant("lights[" + i + "].dir", dirX / length, dirY / length, dirZ / length);
230+
// preparing for: I = pow((cosAngle - cosOuter) / (cosInner - cosOuter), falloff);
231+
float cosOuter = (float) Math.cos(Math.toRadians(light.outerAngle));
232+
float cosInner = (float) Math.cos(Math.toRadians(light.innerAngle));
233+
shader.setConstant("lights[" + i + "].cosOuter", cosOuter);
234+
shader.setConstant("lights[" + i + "].denom", cosInner - cosOuter);
235+
shader.setConstant("lights[" + i + "].falloff", light.falloff);
236+
}
237+
}
217238
}

‎modules/javafx.graphics/src/main/java/com/sun/prism/es2/GLContext.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ private static native void nSetWireframe(long nativeCtxInfo, long nativeMeshView
251251
boolean wireframe);
252252
private static native void nSetAmbientLight(long nativeCtxInfo, long nativeMeshViewInfo,
253253
float r, float g, float b);
254-
private static native void nSetPointLight(long nativeCtxInfo, long nativeMeshViewInfo,
255-
int index, float x, float y, float z, float r, float g, float b, float w,
256-
float ca, float la, float qa, float maxRange);
254+
private static native void nSetLight(long nativeCtxInfo, long nativeMeshViewInfo,
255+
int index, float x, float y, float z, float r, float g, float b, float w, float ca, float la, float qa,
256+
float maxRange, float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff);
257257
private static native void nRenderMeshView(long nativeCtxInfo, long nativeMeshViewInfo);
258258
private static native void nBlit(long nativeCtxInfo, int srcFBO, int dstFBO,
259259
int srcX0, int srcY0, int srcX1, int srcY1,
@@ -809,9 +809,11 @@ void setAmbientLight(long nativeMeshViewInfo, float r, float g, float b) {
809809
nSetAmbientLight(nativeCtxInfo, nativeMeshViewInfo, r, g, b);
810810
}
811811

812-
void setPointLight(long nativeMeshViewInfo, int index, float x, float y, float z, float r, float g, float b, float w,
813-
float ca, float la, float qa, float maxRange) {
814-
nSetPointLight(nativeCtxInfo, nativeMeshViewInfo, index, x, y, z, r, g, b, w, ca, la, qa, maxRange);
812+
void setLight(long nativeMeshViewInfo, int index, float x, float y, float z, float r, float g, float b, float w,
813+
float ca, float la, float qa, float maxRange, float dirX, float dirY, float dirZ,
814+
float innerAngle, float outerAngle, float falloff) {
815+
nSetLight(nativeCtxInfo, nativeMeshViewInfo, index, x, y, z, r, g, b, w, ca, la, qa, maxRange,
816+
dirX, dirY, dirZ, innerAngle, outerAngle, falloff);
815817
}
816818

817819
void renderMeshView(long nativeMeshViewInfo) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package javafx.scene;
27+
28+
import com.sun.javafx.scene.DirtyBits;
29+
import com.sun.javafx.scene.NodeHelper;
30+
import com.sun.javafx.scene.SpotLightHelper;
31+
import com.sun.javafx.sg.prism.NGNode;
32+
import com.sun.javafx.sg.prism.NGSpotLight;
33+
34+
import javafx.beans.property.DoubleProperty;
35+
import javafx.beans.property.ObjectProperty;
36+
import javafx.beans.property.SimpleObjectProperty;
37+
import javafx.geometry.Point3D;
38+
import javafx.scene.paint.Color;
39+
import javafx.scene.paint.PhongMaterial;
40+
41+
/**
42+
* A {@code SpotLight} is a {@code PointLight} that radiates light in a cone in a specific direction.
43+
* The direction is defined by the {@link #directionProperty() direction} vector property of the light. The direction
44+
* can be rotated by setting a rotation transform on the {@code SpotLight}. For example, if the direction vector is
45+
* {@code (1, 1, 1)} and the light is not rotated, it will point in the {@code (1, 1, 1)} direction, and if the light is
46+
* rotated 90 degrees on the y axis, it will point in the {@code (1, 1, -1)} direction.
47+
* <p>
48+
* In addition to the factors that control the light intensity of a {@code PointLight}, a {@code SpotLight} has a
49+
* light-cone attenuation factor, {@code spot}, that is determined by 3 properties:
50+
* <ul>
51+
* <li> {@link #innerAngleProperty() innerAngle}: the angle of the inner cone (see image below)
52+
* <li> {@link #outerAngleProperty() outerAngle}: the angle of the outer cone (see image below)
53+
* <li> {@link #falloffProperty() falloff}: the factor that controls the light's intensity drop inside the outer cone
54+
* </ul>
55+
* The valid ranges for these properties are {@code 0 <= innerAngle <= outerAngle <= 180} and {@code falloff >= 0};
56+
* values outside either of these ranges can produce unexpected results.
57+
* <p>
58+
* The angle of a point to the light is defined as the angle between its vector to the light's position and the
59+
* direction of the light. For such an angle {@code theta}, if
60+
* <ul>
61+
* <li>{@code theta < innerAngle} then {@code spot = 1}
62+
* <li>{@code theta > outerAngle} then {@code spot = 0}
63+
* <li>{@code innerAngle <= theta <= outerAngle} then
64+
*
65+
* <pre>spot = pow((cos(theta) - cos(outer)) / (cos(inner) - cos(outer)), falloff)</pre>
66+
*
67+
* which represents a drop in intensity from the inner angle to the outer angle.
68+
* </ul>
69+
* As a result, {@code 0 <= spot <= 1}. The overall intensity of the light is {@code I = lambert * atten * spot}.
70+
* <p>
71+
* <img src="doc-files/spotlight.png" alt="Image of the Spotlight">
72+
*
73+
* @since 17
74+
* @see PhongMaterial
75+
*/
76+
public class SpotLight extends PointLight {
77+
static {
78+
SpotLightHelper.setSpotLightAccessor(new SpotLightHelper.SpotLightAccessor() {
79+
@Override
80+
public NGNode doCreatePeer(Node node) {
81+
return ((SpotLight) node).doCreatePeer();
82+
}
83+
84+
@Override
85+
public void doUpdatePeer(Node node) {
86+
((SpotLight) node).doUpdatePeer();
87+
}
88+
});
89+
}
90+
91+
{
92+
// To initialize the class helper at the beginning of each constructor of this class
93+
SpotLightHelper.initHelper(this);
94+
}
95+
96+
/**
97+
* Creates a new instance of {@code SpotLight} class with a default {@code Color.WHITE} light source.
98+
*/
99+
public SpotLight() {
100+
super();
101+
}
102+
103+
/**
104+
* Creates a new instance of {@code SpotLight} class using the specified color.
105+
*
106+
* @param color the color of the light source
107+
*/
108+
public SpotLight(Color color) {
109+
super(color);
110+
}
111+
112+
113+
/**
114+
* The direction vector of the spotlight. It can be rotated by setting a rotation transform on the
115+
* {@code SpotLight}. The vector need not be normalized.
116+
*
117+
* @defaultValue {@code Point3D(0, 0, 1)}
118+
*/
119+
private ObjectProperty<Point3D> direction;
120+
121+
public final void setDirection(Point3D value) {
122+
directionProperty().set(value);
123+
}
124+
125+
private static final Point3D DEFAULT_DIRECTION = NGSpotLight.getDefaultDirection();
126+
127+
public final Point3D getDirection() {
128+
return direction == null ? DEFAULT_DIRECTION : direction.get();
129+
}
130+
131+
public final ObjectProperty<Point3D> directionProperty() {
132+
if (direction == null) {
133+
direction = new SimpleObjectProperty<>(this, "direction", DEFAULT_DIRECTION) {
134+
@Override
135+
protected void invalidated() {
136+
NodeHelper.markDirty(SpotLight.this, DirtyBits.NODE_LIGHT);
137+
}
138+
};
139+
}
140+
return direction;
141+
}
142+
143+
144+
/**
145+
* The angle of the spotlight's inner cone, in degrees. A point whose angle to the light is less than this angle is
146+
* not attenuated by the spotlight factor ({@code spot = 1}). At larger angles, the light intensity starts to drop.
147+
* See the class doc for more information.
148+
* <p>
149+
* The valid range is {@code 0 <= innerAngle <= outerAngle}; values outside of this range can produce unexpected
150+
* results.
151+
*
152+
* @defaultValue 0
153+
*/
154+
private DoubleProperty innerAngle;
155+
156+
public final void setInnerAngle(double value) {
157+
innerAngleProperty().set(value);
158+
}
159+
160+
private static final double DEFAULT_INNER_ANGLE = NGSpotLight.getDefaultInnerAngle();
161+
162+
public final double getInnerAngle() {
163+
return innerAngle == null ? DEFAULT_INNER_ANGLE : innerAngle.get();
164+
}
165+
166+
public final DoubleProperty innerAngleProperty() {
167+
if (innerAngle == null) {
168+
innerAngle = getLightDoubleProperty("innerAngle", DEFAULT_INNER_ANGLE);
169+
}
170+
return innerAngle;
171+
}
172+
173+
174+
/**
175+
* The angle of the spotlight's outer cone, in degrees (as shown in the class doc image). A point whose angle to the
176+
* light is greater than this angle receives no light ({@code spot = 0}). A point whose angle to the light is less
177+
* than the outer angle but greater than the inner angle receives partial intensity governed by the falloff factor.
178+
* See the class doc for more information.
179+
* <p>
180+
* The valid range is {@code innerAngle <= outerAngle <= 180}; values outside of this range can produce unexpected
181+
* results.
182+
*
183+
* @defaultValue 30
184+
*/
185+
private DoubleProperty outerAngle;
186+
187+
public final void setOuterAngle(double value) {
188+
outerAngleProperty().set(value);
189+
}
190+
191+
private static final double DEFAULT_OUTER_ANGLE = NGSpotLight.getDefaultOuterAngle();
192+
193+
public final double getOuterAngle() {
194+
return outerAngle == null ? DEFAULT_OUTER_ANGLE : outerAngle.get();
195+
}
196+
197+
public final DoubleProperty outerAngleProperty() {
198+
if (outerAngle == null) {
199+
outerAngle = getLightDoubleProperty("outerAngle", DEFAULT_OUTER_ANGLE);
200+
}
201+
return outerAngle;
202+
}
203+
204+
205+
/**
206+
* The intensity falloff factor of the spotlight's outer cone. A point whose angle to the light is
207+
* greater than the inner angle but less than the outer angle receives partial intensity governed by this factor.
208+
* The larger the falloff, the sharper the drop in intensity from the inner cone. A falloff factor of 1 gives a
209+
* linear drop in intensity, values greater than 1 give a convex drop, and values smaller than 1 give a concave
210+
* drop. See the class doc for more information.
211+
* <p>
212+
* The valid range is {@code 0 <= falloff}; values outside of this range can produce unexpected results.
213+
*
214+
* @defaultValue 1
215+
*/
216+
private DoubleProperty falloff;
217+
218+
public final void setFalloff(double value) {
219+
falloffProperty().set(value);
220+
}
221+
222+
private static final double DEFAULT_FALLOFF = NGSpotLight.getDefaultFalloff();
223+
224+
public final double getFalloff() {
225+
return falloff == null ? DEFAULT_FALLOFF : falloff.get();
226+
}
227+
228+
public final DoubleProperty falloffProperty() {
229+
if (falloff == null) {
230+
falloff = getLightDoubleProperty("falloff", DEFAULT_FALLOFF);
231+
}
232+
return falloff;
233+
}
234+
235+
/*
236+
* Note: This method MUST only be called via its accessor method.
237+
*/
238+
private NGNode doCreatePeer() {
239+
return new NGSpotLight();
240+
}
241+
242+
private void doUpdatePeer() {
243+
if (isDirty(DirtyBits.NODE_LIGHT)) {
244+
NGSpotLight peer = getPeer();
245+
peer.setDirection(getDirection());
246+
peer.setInnerAngle((float) getInnerAngle());
247+
peer.setOuterAngle((float) getOuterAngle());
248+
peer.setFalloff((float) getFalloff());
249+
}
250+
}
251+
}

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DContext.cc

+7-6
Original file line numberDiff line numberDiff line change
@@ -511,18 +511,19 @@ JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetAmbientLight
511511

512512
/*
513513
* Class: com_sun_prism_d3d_D3DContext
514-
* Method: nSetPointLight
515-
* Signature: (JJIFFFFFFF)V
514+
* Method: nSetLight
515+
* Signature: (JJIFFFFFFFFFFFFFFFFF)V
516516
*/
517-
JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetPointLight
517+
JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetLight
518518
(JNIEnv *env, jclass, jlong ctx, jlong nativeMeshView, jint index,
519519
jfloat x, jfloat y, jfloat z, jfloat r, jfloat g, jfloat b, jfloat w,
520-
jfloat ca, jfloat la, jfloat qa, jfloat range)
520+
jfloat ca, jfloat la, jfloat qa, jfloat range,
521+
jfloat dirX, jfloat dirY, jfloat dirZ, jfloat innerAngle, jfloat outerAngle, jfloat falloff)
521522
{
522-
TraceLn(NWT_TRACE_INFO, "D3DContext_nSetPointLight");
523+
TraceLn(NWT_TRACE_INFO, "D3DContext_nSetLight");
523524
D3DMeshView *meshView = (D3DMeshView *) jlong_to_ptr(nativeMeshView);
524525
RETURN_IF_NULL(meshView);
525-
meshView->setPointLight(index, x, y, z, r, g, b, w, ca, la, qa, range);
526+
meshView->setLight(index, x, y, z, r, g, b, w, ca, la, qa, range, dirX, dirY, dirZ, innerAngle, outerAngle, falloff);
526527
}
527528

528529
/*

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DLight.cc

+14-13
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,20 @@ using std::endl;
3434
D3DLight::~D3DLight() {
3535
}
3636

37-
D3DLight::D3DLight() {
38-
color[0] = 0;
39-
color[1] = 0;
40-
color[2] = 0;
41-
position[0] = 0;
42-
position[1] = 0;
43-
position[2] = 0;
44-
position[3] = 0; // padding since SetPixelShaderConstantF only takes vec4f; position[3] is unused
45-
w = 0;
46-
attenuation[0] = 1;
47-
attenuation[1] = 0;
48-
attenuation[2] = 0;
49-
maxRange = 0;
37+
D3DLight::D3DLight() :
38+
color(),
39+
position(),
40+
w(0),
41+
attenuation(),
42+
maxRange(0),
43+
direction(),
44+
innerAngle(0),
45+
outerAngle(0),
46+
falloff(0)
47+
{}
48+
49+
bool D3DLight::isPointLight() {
50+
return falloff == 0 && outerAngle == 180;
5051
}
5152

5253
void D3DLight::setColor(float r, float g, float b) {

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DLight.h

+7-3
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,22 @@ class D3DLight {
3434
public:
3535
D3DLight();
3636
virtual ~D3DLight();
37+
bool isPointLight();
3738
void setColor(float r, float g, float b);
3839
void setPosition(float x, float y, float z);
39-
// void setRange(float r);
40-
float position[4]; // Only need x, y, z. The last float is needed for padding when upload to shader.
40+
41+
float position[3];
4142
float color[3];
4243
float w;
4344
float attenuation[3]; // ca, la, qa
4445
float maxRange;
46+
float direction[3];
47+
float innerAngle;
48+
float outerAngle;
49+
float falloff;
4550

4651
private:
4752

4853
};
4954

5055
#endif /* D3DLIGHT_H */
51-

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DMeshView.cc

+86-29
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525

2626
#include <iostream>
27+
#define _USE_MATH_DEFINES
28+
#include <math.h>
2729
#include "D3DMeshView.h"
2830
#include "D3DPhongShader.h"
2931

@@ -37,7 +39,6 @@ D3DMeshView::~D3DMeshView() {
3739
// The freeing of native resources is handled by its Java layer.
3840
mesh = NULL;
3941
material = NULL;
40-
4142
}
4243

4344
D3DMeshView::D3DMeshView(D3DContext *ctx, D3DMesh *pMesh) {
@@ -72,22 +73,29 @@ void D3DMeshView::setAmbientLight(float r, float g, float b) {
7273
ambientLightColor[2] = b;
7374
}
7475

75-
void D3DMeshView::setPointLight(int index, float x, float y, float z,
76-
float r, float g, float b, float w,
77-
float ca, float la, float qa, float maxRange) {
76+
void D3DMeshView::setLight(int index, float x, float y, float z, float r, float g, float b, float w,
77+
float ca, float la, float qa, float maxRange,
78+
float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff) {
7879
// NOTE: We only support up to 3 point lights at the present
79-
if (index >= 0 && index <= 2) {
80-
lights[index].position[0] = x;
81-
lights[index].position[1] = y;
82-
lights[index].position[2] = z;
83-
lights[index].color[0] = r;
84-
lights[index].color[1] = g;
85-
lights[index].color[2] = b;
86-
lights[index].w = w;
87-
lights[index].attenuation[0] = ca;
88-
lights[index].attenuation[1] = la;
89-
lights[index].attenuation[2] = qa;
90-
lights[index].maxRange = maxRange;
80+
if (index >= 0 && index <= MAX_NUM_LIGHTS - 1) {
81+
D3DLight& light = lights[index];
82+
light.position[0] = x;
83+
light.position[1] = y;
84+
light.position[2] = z;
85+
light.color[0] = r;
86+
light.color[1] = g;
87+
light.color[2] = b;
88+
light.w = w;
89+
light.attenuation[0] = ca;
90+
light.attenuation[1] = la;
91+
light.attenuation[2] = qa;
92+
light.maxRange = maxRange;
93+
light.direction[0] = dirX;
94+
light.direction[1] = dirY;
95+
light.direction[2] = dirZ;
96+
light.innerAngle = innerAngle;
97+
light.outerAngle = outerAngle;
98+
light.falloff = falloff;
9199
lightsDirty = TRUE;
92100
}
93101
}
@@ -98,7 +106,7 @@ void D3DMeshView::computeNumLights() {
98106
lightsDirty = false;
99107

100108
int n = 0;
101-
for (int i = 0; i != 3; ++i)
109+
for (int i = 0; i != MAX_NUM_LIGHTS; ++i)
102110
n += lights[i].w ? 1 : 0;
103111

104112
numLights = n;
@@ -137,10 +145,35 @@ void D3DMeshView::render() {
137145

138146
computeNumLights();
139147
// We only support up to 3 point lights at the present
140-
for (int i = 0; i < 3; i++) {
148+
for (int i = 0; i < MAX_NUM_LIGHTS; i++) {
141149
status = SUCCEEDED(device->SetVertexShaderConstantF(VSR_LIGHTS + i*2, lights[i].position, 1));
142150
}
143151

152+
float lightsNormDirection[MAX_NUM_LIGHTS * 4]; // 3 lights x (3 coords + 1 padding)
153+
for (int i = 0, d = 0; i < MAX_NUM_LIGHTS; i++) {
154+
if (lights[i].isPointLight()) {
155+
lightsNormDirection[d++] = 0;
156+
lightsNormDirection[d++] = 0;
157+
lightsNormDirection[d++] = 1;
158+
lightsNormDirection[d++] = 0;
159+
} else {
160+
float dirX = lights[i].direction[0];
161+
float dirY = lights[i].direction[1];
162+
float dirZ = lights[i].direction[2];
163+
float length = sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
164+
lightsNormDirection[d++] = dirX / length;
165+
lightsNormDirection[d++] = dirY / length;
166+
lightsNormDirection[d++] = dirZ / length;
167+
lightsNormDirection[d++] = 0;
168+
}
169+
}
170+
171+
status = SUCCEEDED(device->SetVertexShaderConstantF(VSR_DIRS, lightsNormDirection, MAX_NUM_LIGHTS));
172+
if (!status) {
173+
cout << "D3DMeshView.render() - SetVertexShaderConstantF (VSR_DIRS) failed !!!" << endl;
174+
return;
175+
}
176+
144177
status = SUCCEEDED(device->SetVertexShaderConstantF(VSR_AMBIENTCOLOR, ambientLightColor, 1));
145178
if (!status) {
146179
cout << "D3DMeshView.render() - SetVertexShaderConstantF (VSR_AMBIENTCOLOR) failed !!!" << endl;
@@ -159,10 +192,11 @@ void D3DMeshView::render() {
159192
return;
160193
}
161194

162-
float lightsColor[12]; // 3 lights x (3 color + 1 padding)
163-
float lightsAttenuation[12]; // 3 lights x (3 attenuation factors + 1 padding)
164-
float lightsRange[12]; // 3 lights x (1 maxRange + 3 padding)
165-
for (int i = 0, c = 0, a = 0, r = 0; i < 3; i++) {
195+
float lightsColor[MAX_NUM_LIGHTS * 4]; // 3 lights x (3 color + 1 padding)
196+
float lightsAttenuation[MAX_NUM_LIGHTS * 4]; // 3 lights x (3 attenuation factors + 1 padding)
197+
float lightsRange[MAX_NUM_LIGHTS * 4]; // 3 lights x (1 maxRange + 3 padding)
198+
float spotLightsFactors[MAX_NUM_LIGHTS * 4]; // 3 lights x (2 angles + 1 falloff + 1 padding)
199+
for (int i = 0, c = 0, a = 0, r = 0, s = 0; i < MAX_NUM_LIGHTS; i++) {
166200
float w = lights[i].w;
167201
lightsColor[c++] = lights[i].color[0] * w;
168202
lightsColor[c++] = lights[i].color[1] * w;
@@ -178,20 +212,44 @@ void D3DMeshView::render() {
178212
lightsRange[r++] = 0;
179213
lightsRange[r++] = 0;
180214
lightsRange[r++] = 0;
215+
216+
if (lights[i].isPointLight()) {
217+
spotLightsFactors[s++] = -1; // cos(180)
218+
spotLightsFactors[s++] = 2; // cos(0) - cos(180)
219+
spotLightsFactors[s++] = 0;
220+
spotLightsFactors[s++] = 0;
221+
} else {
222+
// preparing for: I = pow((cosAngle - cosOuter) / (cosInner - cosOuter), falloff)
223+
float cosInner = cos(lights[i].innerAngle * M_PI / 180);
224+
float cosOuter = cos(lights[i].outerAngle * M_PI / 180);
225+
spotLightsFactors[s++] = cosOuter;
226+
spotLightsFactors[s++] = cosInner - cosOuter;
227+
spotLightsFactors[s++] = lights[i].falloff;
228+
spotLightsFactors[s++] = 0;
229+
}
181230
}
182-
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHTCOLOR, lightsColor, 3));
231+
232+
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHTCOLOR, lightsColor, MAX_NUM_LIGHTS));
183233
if (!status) {
184-
cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_LIGHTCOLOR) failed !!!" << endl;
234+
cout << "D3DMeshView.render() - SetPixelShaderConstantF(PSR_LIGHTCOLOR) failed !!!" << endl;
185235
return;
186236
}
187-
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHT_ATTENUATION, lightsAttenuation, 3));
237+
238+
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHT_ATTENUATION, lightsAttenuation, MAX_NUM_LIGHTS));
188239
if (!status) {
189-
cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_LIGHT_ATTENUATION) failed !!!" << endl;
240+
cout << "D3DMeshView.render() - SetPixelShaderConstantF(PSR_LIGHT_ATTENUATION) failed !!!" << endl;
190241
return;
191242
}
192-
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHT_RANGE, lightsRange, 3));
243+
244+
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_LIGHT_RANGE, lightsRange, MAX_NUM_LIGHTS));
193245
if (!status) {
194-
cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_LIGHT_RANGE) failed !!!" << endl;
246+
cout << "D3DMeshView.render() - SetPixelShaderConstantF(PSR_LIGHT_RANGE) failed !!!" << endl;
247+
return;
248+
}
249+
250+
status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_SPOTLIGHT_FACTORS, spotLightsFactors, MAX_NUM_LIGHTS));
251+
if (!status) {
252+
cout << "D3DMeshView.render() - SetPixelShaderConstantF(PSR_SPOTLIGHT_FACTORS) failed !!!" << endl;
195253
return;
196254
}
197255

@@ -234,5 +292,4 @@ void D3DMeshView::render() {
234292
SUCCEEDED(device->SetIndices(mesh->getIndexBuffer()));
235293
SUCCEEDED(device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,
236294
mesh->getNumVertices(), 0, (mesh->getNumIndices()/3)));
237-
238295
}

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DMeshView.h

+7-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "D3DMesh.h"
3232
#include "D3DPhongMaterial.h"
3333

34+
#define MAX_NUM_LIGHTS 3
35+
3436
class D3DMeshView {
3537
public:
3638
D3DMeshView(D3DContext *pCtx, D3DMesh *pMesh);
@@ -39,17 +41,19 @@ class D3DMeshView {
3941
void setMaterial(D3DPhongMaterial *pMaterial);
4042
void setWireframe(bool wf);
4143
void setAmbientLight(float r, float g, float b);
42-
void setPointLight(int index, float x, float y, float z,
44+
void setLight(int index, float x, float y, float z,
4345
float r, float g, float b, float w,
44-
float ca, float la, float qa, float maxRange);
46+
float ca, float la, float qa, float maxRange,
47+
float dirX, float dirY, float dirZ,
48+
float innerAngle, float outerAngle, float falloff);
4549
void computeNumLights();
4650
void render();
4751

4852
private:
4953
D3DContext *context;
5054
D3DMesh *mesh;
5155
D3DPhongMaterial *material;
52-
D3DLight lights[3];
56+
D3DLight lights[MAX_NUM_LIGHTS];
5357
float ambientLightColor[3];
5458
int numLights;
5559
bool lightsDirty;
@@ -58,4 +62,3 @@ class D3DMeshView {
5862
};
5963

6064
#endif /* D3DMESHVIEW_H */
61-

‎modules/javafx.graphics/src/main/native-prism-d3d/D3DPhongShader.h

+32-20
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,38 @@
2626
#ifndef D3DPHONGSHADER_H
2727
#define D3DPHONGSHADER_H
2828

29-
// VSR implies Vertex Shader Registers
30-
#define VSR_VIEWPROJMATRIX 0 // 4 total
31-
#define VSR_CAMERAPOS 4 // 1 total
32-
// lighting
33-
// 5 lights (3 in use, 2 reserved)
34-
// with 2 registers = 10 registers
35-
#define VSR_LIGHTS 10
36-
// 8 ambient points + 2 coords : 10 registers
37-
#define VSR_AMBIENTCOLOR 20
38-
// world
39-
#define VSR_WORLDMATRIX 30
40-
41-
// PSR implies Pixel Shader Registers
42-
// we have 224 float constants for ps 3.0
43-
#define PSR_DIFFUSECOLOR 0
44-
#define PSR_SPECULARCOLOR 1
45-
#define PSR_LIGHTCOLOR 4 // 3 lights + 2 reserve
46-
#define PSR_LIGHT_ATTENUATION 9 // 3 lights + 2 reserve
47-
#define PSR_LIGHT_RANGE 14 // 3 lights + 2 reserve
29+
// Register assignments. Each register can hold 1 float4 vector.
30+
// See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-vs-registers-constant-float
31+
32+
// VSR implies Vertex Shader Registers. Assignments happen in vsConstants.h
33+
// We have at least 256 constant float registers for vs 3.0
34+
// See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-vs-registers-vs-3-0
35+
#define VSR_VIEWPROJMATRIX 0 // 4x4 matrix = 4: c0-3
36+
#define VSR_CAMERAPOS 4 // 1 position: c4
37+
// Registers 5-9 (inclusive) are reserved
38+
39+
// Lights: 5 lights (3 in use, 2 reserved)
40+
#define VSR_LIGHTS 10 // 1 position + 1 color = 5 * 2 = 10: c10-19
41+
#define VSR_DIRS 20 // 1 direction = 5 * 1 = 5: c20-24
42+
43+
#define VSR_AMBIENTCOLOR 25 // 8 ambient points + 2 coords = 10 (only 1 is used): c25-34
44+
45+
#define VSR_WORLDMATRIX 35 // 4x3 matrix = 3: c35-37
46+
47+
// PSR implies Pixel Shader Registers. Assignments happen in psConstants.h
48+
// We have 224 constant float registers for ps 3.0
49+
// See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-ps-registers-ps-3-0
50+
51+
// Material
52+
#define PSR_DIFFUSECOLOR 0 // 1 color: c0
53+
#define PSR_SPECULARCOLOR 1 // 1 color (including the specular power): c1
54+
// Registers 2-3 (inclusive) are reserved
55+
56+
// Lights: 5 lights (3 in use, 2 reserved)
57+
#define PSR_LIGHTCOLOR 4 // 1 color: c4-8
58+
#define PSR_LIGHT_ATTENUATION 9 // 1 attenuation: c9-13
59+
#define PSR_LIGHT_RANGE 14 // 1 range (max range at [0], reserved min range at [1], [2] and [3] unused): c14-18
60+
#define PSR_SPOTLIGHT_FACTORS 19 // 1 spotlight: c19-23
4861

4962
// SR implies Sampler Registers
5063
#define SR_DIFFUSEMAP 0
@@ -109,4 +122,3 @@ static const int maxLights = 3;
109122
};
110123

111124
#endif /* D3DPHONGSHADER_H */
112-

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/Mtl1PS.hlsl

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ float4 main(ObjectPsIn objAttr, LocalBump lSpace) : color {
8888
float3 diff = 0;
8989
float3 spec = 0;
9090

91-
phong(n, nEye, sPower, lSpace.lights, diff, spec, 0, nSpecular);
91+
phong(n, nEye, sPower, lSpace.lights, lSpace.lightDirs, diff, spec, 0, nSpecular);
9292

9393
float3 rez = (ambColor.xyz+diff)*tDiff.xyz + spec*tSpec.rgb;
9494

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/psConstants.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@
2323
* questions.
2424
*/
2525

26-
// see D3DPhongShader.h
26+
// see D3DPhongShader.h for register assignments
2727

2828
static const int numMaxLights = 5;
2929

3030
float4 gDiffuseColor : register(c0);
3131
float4 gSpecularColor : register(c1); // specular power is in the alpha
32-
float4 gLightColor[numMaxLights] : register(c4); // [c4 .. c8]
33-
float4 gLightAttenuation[numMaxLights] : register(c9); // [c9 .. c13]
34-
float4 gLightRange[numMaxLights] : register(c14); // [c14 .. c18]
32+
float4 gLightColor[numMaxLights] : register(c4);
33+
float4 gLightAttenuation[numMaxLights] : register(c9);
34+
float4 gLightRange[numMaxLights] : register(c14); // {max range, reserved min range, _, _}
35+
float4 gSpotLightFactors[numMaxLights] : register(c19); // {cos(outer), cos(inner) - cos(outer), falloff, _}
3536

36-
float4 gSomethingElse : register(c19);
37+
float4 gSomethingElse : register(c24);

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/psMath.h

+69-15
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,76 @@ float4 retNormal(float3 n) { return float4( n*0.5+0.5,1); }
5858

5959
float4 retr(float x) { return float4(x.xxx,1); }
6060

61-
void phong(
62-
float3 n, float3 e, float power, in float4 L[LocalBump::nLights],
63-
in out float3 d, in out float3 s, int _s, int _e)
64-
{
61+
// Because pow(0, 0) is undefined (https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pow),
62+
// we need special treatment for falloff == 0 cases
63+
64+
/*
65+
* The methods 'computeSpotlightFactor' and 'computeSpotlightFactor2' are alternatives to the 'computeSpotlightFactor3'
66+
* method that was chosen for its better performance. They are kept for the case that a future changes will make them
67+
* more performant.
68+
*
69+
float computeSpotlightFactor(float3 l, float3 lightDir, float cosOuter, float denom, float falloff) {
70+
if (falloff == 0 && cosOuter == -1) { // point light optimization (cosOuter == -1 is outerAngle == 180)
71+
return 1;
72+
}
73+
float cosAngle = dot(normalize(-lightDir), l);
74+
float cutoff = cosAngle - cosOuter;
75+
if (falloff != 0) {
76+
return pow(saturate(cutoff / denom), falloff);
77+
}
78+
return cutoff >= 0 ? 1 : 0;
79+
}
80+
81+
float computeSpotlightFactor2(float3 l, float3 lightDir, float cosOuter, float denom, float falloff) {
82+
if (falloff != 0) {
83+
float cosAngle = dot(normalize(-lightDir), l);
84+
float cutoff = cosAngle - cosOuter;
85+
return pow(saturate(cutoff / denom), falloff);
86+
}
87+
if (cosOuter == -1) { // point light optimization (cosOuter == -1 is outerAngle == 180)
88+
return 1;
89+
}
90+
float cosAngle = dot(normalize(-lightDir), l);
91+
float cutoff = cosAngle - cosOuter;
92+
return cutoff >= 0 ? 1 : 0;
93+
}
94+
*/
95+
96+
float computeSpotlightFactor3(float3 l, float3 lightDir, float cosOuter, float denom, float falloff) {
97+
float cosAngle = dot(normalize(-lightDir), l);
98+
float cutoff = cosAngle - cosOuter;
99+
if (falloff != 0) {
100+
return pow(saturate(cutoff / denom), falloff);
101+
}
102+
return cutoff >= 0 ? 1 : 0;
103+
}
104+
105+
void computeLight(float i, float3 n, float3 refl, float specPower, float3 L, float3 lightDir, in out float3 d, in out float3 s) {
106+
float dist = length(L);
107+
if (dist > gLightRange[i].x) {
108+
return;
109+
}
110+
float3 l = normalize(L);
111+
112+
float cosOuter = gSpotLightFactors[i].x;
113+
float denom = gSpotLightFactors[i].y;
114+
float falloff = gSpotLightFactors[i].z;
115+
float spotlightFactor = computeSpotlightFactor3(l, lightDir, cosOuter, denom, falloff);
116+
117+
float ca = gLightAttenuation[i].x;
118+
float la = gLightAttenuation[i].y;
119+
float qa = gLightAttenuation[i].z;
120+
float invAttnFactor = ca + la * dist + qa * dist * dist;
121+
122+
float3 attenuatedColor = gLightColor[i].xyz * spotlightFactor / invAttnFactor;
123+
d += saturate(dot(n, l)) * attenuatedColor;
124+
s += pow(saturate(dot(-refl, l)), specPower) * attenuatedColor;
125+
}
126+
127+
void phong(float3 n, float3 e, float specPower, in float4 L[LocalBump::nLights], in float4 lightDirs[LocalBump::nLights],
128+
in out float3 d, in out float3 s, int _s, int _e) {
65129
float3 refl = reflect(e, n);
66130
for (int i = _s; i < _e; i++) {
67-
float dist = length(L[i].xyz);
68-
if (dist <= gLightRange[i].x) {
69-
float ca = gLightAttenuation[i].x;
70-
float la = gLightAttenuation[i].y;
71-
float qa = gLightAttenuation[i].z;
72-
float3 attenuatedColor = gLightColor[i].xyz / (ca + la * dist + qa * dist * dist);
73-
74-
float3 l = normalize(L[i].xyz);
75-
d += saturate(dot(n, l)) * attenuatedColor;
76-
s += pow(saturate(dot(-refl, l)), power) * attenuatedColor;
77-
}
131+
computeLight(i, n, refl, specPower, L[i].xyz, lightDirs[i].xyz, d, s);
78132
}
79133
}

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/vs2ps.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -32,14 +32,15 @@ struct LocalBump {
3232

3333
static const float nLights = 3;
3434

35-
float3 eye : texcoord2;
36-
float4 lights[nLights] : texcoord3; // 3, 4,5 [6]
37-
float3 debug : texcoord7;
35+
float3 eye : texcoord2;
36+
float4 lights[nLights] : texcoord3; // 3, 4, 5 [6]
37+
float4 lightDirs[nLights] : texcoord7; // 7, 8, 9 [10]
38+
// float3 debug : texcoord11;
3839
};
3940

4041
struct LocalBumpOut {
4142
float4 pos : position;
42-
float oFog : fog;
43+
float oFog : fog;
4344

4445
LocalBump lBump;
4546
};
@@ -49,4 +50,3 @@ struct ObjVsOutput {
4950
LocalBumpOut light;
5051
ObjectPsIn objAttr;
5152
};
52-

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/vsConstants.h

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -38,20 +38,23 @@ struct Light {
3838
float4 color;
3939
};
4040

41+
// See D3DPhongShader.h for register assignments
42+
4143
// camera
42-
float4x4 mViewProj : register (c0);
44+
float4x4 mViewProj : register(c0);
4345
float4 gCameraPos : register(c4);
4446

4547

4648
float4 gReserved5[5] : register(c5);
4749

4850
// lighting
49-
Light sLights[5] : register(c10);
50-
float4 gAmbinet : register (c20);
51-
float4 gAmbinetData[10] : register (c20);
51+
Light sLights[5] : register(c10);
52+
float4 gLightsNormDir[5] : register(c20);
53+
float4 gAmbinet : register(c25);
54+
float4 gAmbinetData[10] : register(c25);
5255

5356
// world
54-
float4x3 mWorld : register (c30);
55-
float4x3 mBones[MAX_BONES] : register (c30);
57+
float4x3 mWorld : register(c35);
58+
float4x3 mBones[MAX_BONES] : register(c35);
5659

57-
float4 gReserved240[16] : register(c240);
60+
float4 gReserved245[11] : register(c245);

‎modules/javafx.graphics/src/main/native-prism-d3d/hlsl/vsMath.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -74,19 +74,19 @@ void calcLocalBump(float4 ipos, float4 iTn, in float4x3 mW, out LocalBumpOut r)
7474

7575
for (int k=0; k<LocalBump::nLights; ++k) {
7676
float3 L = sLights[k].pos.xyz - pos;
77+
float3 D = gLightsNormDir[k].xyz;
7778
r.lBump.lights[k] = float4(getLocalVector(L, n), 1);
79+
r.lBump.lightDirs[k] = float4(getLocalVector(D, n), 1);
7880
}
7981

8082
r.pos = mul(float4(pos,1), mViewProj);
8183

82-
// r.Debug = r.Pos;
84+
// r.Debug = r.Pos;
8385

84-
r.lBump.debug = n[0];
86+
// r.lBump.debug = n[0];
8587

8688
r.oFog = 1; // getFogExp2(pos);
8789

8890
}
8991

9092
float4 retFloat(float x) { return float4(x.xxx,1); }
91-
92-

‎modules/javafx.graphics/src/main/native-prism-es2/GLContext.c

+41-28
Original file line numberDiff line numberDiff line change
@@ -2114,18 +2114,24 @@ JNIEXPORT jlong JNICALL Java_com_sun_prism_es2_GLContext_nCreateES2MeshView
21142114
meshViewInfo->ambientLightColor[0] = 0;
21152115
meshViewInfo->ambientLightColor[1] = 0;
21162116
meshViewInfo->ambientLightColor[2] = 0;
2117-
meshViewInfo->pointLightIndex = 0;
2118-
meshViewInfo->pointLightColor[0] = 0;
2119-
meshViewInfo->pointLightColor[1] = 0;
2120-
meshViewInfo->pointLightColor[2] = 0;
2121-
meshViewInfo->pointLightPosition[0] = 0;
2122-
meshViewInfo->pointLightPosition[1] = 0;
2123-
meshViewInfo->pointLightPosition[2] = 0;
2124-
meshViewInfo->pointLightWeight = 0;
2125-
meshViewInfo->pointLightAttenuation[0] = 1;
2126-
meshViewInfo->pointLightAttenuation[1] = 0;
2127-
meshViewInfo->pointLightAttenuation[2] = 0;
2128-
meshViewInfo->pointLightMaxRange = 0;
2117+
meshViewInfo->lightIndex = 0;
2118+
meshViewInfo->lightColor[0] = 0;
2119+
meshViewInfo->lightColor[1] = 0;
2120+
meshViewInfo->lightColor[2] = 0;
2121+
meshViewInfo->lightPosition[0] = 0;
2122+
meshViewInfo->lightPosition[1] = 0;
2123+
meshViewInfo->lightPosition[2] = 0;
2124+
meshViewInfo->lightWeight = 0;
2125+
meshViewInfo->lightAttenuation[0] = 1;
2126+
meshViewInfo->lightAttenuation[1] = 0;
2127+
meshViewInfo->lightAttenuation[2] = 0;
2128+
meshViewInfo->lightMaxRange = 0;
2129+
meshViewInfo->lightDir[0] = 1;
2130+
meshViewInfo->lightDir[1] = 0;
2131+
meshViewInfo->lightDir[2] = 0;
2132+
meshViewInfo->lightInnerAngle = 0;
2133+
meshViewInfo->lightOuterAngle = 0;
2134+
meshViewInfo->lightFalloff = 0;
21292135

21302136
return ptr_to_jlong(meshViewInfo);
21312137
}
@@ -2264,32 +2270,39 @@ JNIEXPORT void JNICALL Java_com_sun_prism_es2_GLContext_nSetAmbientLight
22642270

22652271
/*
22662272
* Class: com_sun_prism_es2_GLContext
2267-
* Method: nSetPointLight
2268-
* Signature: (JJIFFFFFFF)V
2273+
* Method: nSetLight
2274+
* Signature: (JJIFFFFFFFFFFFFFFFFF)V
22692275
*/
2270-
JNIEXPORT void JNICALL Java_com_sun_prism_es2_GLContext_nSetPointLight
2276+
JNIEXPORT void JNICALL Java_com_sun_prism_es2_GLContext_nSetLight
22712277
(JNIEnv *env, jclass class, jlong nativeCtxInfo, jlong nativeMeshViewInfo,
22722278
jint index, jfloat x, jfloat y, jfloat z, jfloat r, jfloat g, jfloat b, jfloat w,
2273-
jfloat ca, jfloat la, jfloat qa, jfloat maxRange)
2279+
jfloat ca, jfloat la, jfloat qa, jfloat maxRange, jfloat dirX, jfloat dirY, jfloat dirZ,
2280+
jfloat innerAngle, jfloat outerAngle, jfloat falloff)
22742281
{
22752282
ContextInfo *ctxInfo = (ContextInfo *) jlong_to_ptr(nativeCtxInfo);
22762283
MeshViewInfo *meshViewInfo = (MeshViewInfo *) jlong_to_ptr(nativeMeshViewInfo);
22772284
// NOTE: We only support up to 3 point lights at the present
22782285
if ((ctxInfo == NULL) || (meshViewInfo == NULL) || (index < 0) || (index > 2)) {
22792286
return;
22802287
}
2281-
meshViewInfo->pointLightIndex = index;
2282-
meshViewInfo->pointLightPosition[0] = x;
2283-
meshViewInfo->pointLightPosition[1] = y;
2284-
meshViewInfo->pointLightPosition[2] = z;
2285-
meshViewInfo->pointLightColor[0] = r;
2286-
meshViewInfo->pointLightColor[1] = g;
2287-
meshViewInfo->pointLightColor[2] = b;
2288-
meshViewInfo->pointLightWeight = w;
2289-
meshViewInfo->pointLightAttenuation[0] = ca;
2290-
meshViewInfo->pointLightAttenuation[1] = la;
2291-
meshViewInfo->pointLightAttenuation[2] = qa;
2292-
meshViewInfo->pointLightMaxRange = maxRange;
2288+
meshViewInfo->lightIndex = index;
2289+
meshViewInfo->lightPosition[0] = x;
2290+
meshViewInfo->lightPosition[1] = y;
2291+
meshViewInfo->lightPosition[2] = z;
2292+
meshViewInfo->lightColor[0] = r;
2293+
meshViewInfo->lightColor[1] = g;
2294+
meshViewInfo->lightColor[2] = b;
2295+
meshViewInfo->lightWeight = w;
2296+
meshViewInfo->lightAttenuation[0] = ca;
2297+
meshViewInfo->lightAttenuation[1] = la;
2298+
meshViewInfo->lightAttenuation[2] = qa;
2299+
meshViewInfo->lightMaxRange = maxRange;
2300+
meshViewInfo->lightDir[0] = dirX;
2301+
meshViewInfo->lightDir[1] = dirY;
2302+
meshViewInfo->lightDir[2] = dirZ;
2303+
meshViewInfo->lightInnerAngle = innerAngle;
2304+
meshViewInfo->lightOuterAngle = outerAngle;
2305+
meshViewInfo->lightFalloff = falloff;
22932306
}
22942307

22952308
/*

‎modules/javafx.graphics/src/main/native-prism-es2/PrismES2Defs.h

+10-6
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,16 @@ struct MeshViewInfoRec {
376376
MeshInfo *meshInfo;
377377
PhongMaterialInfo *phongMaterialInfo;
378378
GLfloat ambientLightColor[3];
379-
GLuint pointLightIndex;
380-
GLfloat pointLightWeight;
381-
GLfloat pointLightPosition[3];
382-
GLfloat pointLightColor[3];
383-
GLfloat pointLightAttenuation[3];
384-
GLfloat pointLightMaxRange;
379+
GLuint lightIndex;
380+
GLfloat lightColor[3];
381+
GLfloat lightPosition[3];
382+
GLfloat lightWeight;
383+
GLfloat lightAttenuation[3];
384+
GLfloat lightMaxRange;
385+
GLfloat lightDir[3];
386+
GLfloat lightInnerAngle;
387+
GLfloat lightOuterAngle;
388+
GLfloat lightFalloff;
385389
GLboolean cullEnable;
386390
GLenum cullMode;
387391
GLenum fillMode;

‎modules/javafx.graphics/src/main/resources/com/sun/prism/es2/glsl/main.vert

+14
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ struct Light {
3636
vec4 pos;
3737
vec3 color;
3838
vec3 attn;
39+
vec3 dir;
3940
float range;
41+
float cosOuter;
42+
float denom; // cosInner - cosOuter
43+
float falloff;
4044
};
4145

4246
//3 lights used
4347
uniform Light lights[3];
4448

4549
varying vec4 lightTangentSpacePositions[3];
50+
varying vec4 lightTangentSpaceDirections[3];
4651
varying vec2 oTexCoords;
4752
varying vec3 eyePos;
4853

@@ -97,6 +102,15 @@ void main()
97102
L = lights[2].pos.xyz - worldPos.xyz;
98103
lightTangentSpacePositions[2] = vec4( getLocalVector(L,tangentFrame)*lights[2].pos.w, 1.0);
99104

105+
vec3 D = lights[0].dir.xyz;
106+
lightTangentSpaceDirections[0] = vec4( getLocalVector(D,tangentFrame), 1.0);
107+
108+
D = lights[1].dir.xyz;
109+
lightTangentSpaceDirections[1] = vec4( getLocalVector(D,tangentFrame), 1.0);
110+
111+
D = lights[2].dir.xyz;
112+
lightTangentSpaceDirections[2] = vec4( getLocalVector(D,tangentFrame), 1.0);
113+
100114
mat4 mvpMatrix = viewProjectionMatrix * worldMatrix;
101115

102116
//Send texcoords to Pixel Shader and calculate vertex position.

‎modules/javafx.graphics/src/main/resources/com/sun/prism/es2/glsl/main1Light.frag

+72-12
Original file line numberDiff line numberDiff line change
@@ -59,39 +59,99 @@ struct Light {
5959
vec4 pos;
6060
vec3 color;
6161
vec3 attn;
62+
vec3 dir;
6263
float range;
64+
float cosOuter;
65+
float denom; // cosInner - cosOuter
66+
float falloff;
6367
};
6468

6569
uniform vec3 ambientColor;
6670
uniform Light lights[3];
6771

6872
varying vec3 eyePos;
6973
varying vec4 lightTangentSpacePositions[3];
74+
varying vec4 lightTangentSpaceDirections[3];
75+
76+
// Because pow(0, 0) is undefined (https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/pow.xhtml),
77+
// we need special treatment for falloff == 0 cases
78+
float computeSpotlightFactor(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
79+
if (falloff == 0.0 && cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
80+
return 1.0;
81+
}
82+
float cosAngle = dot(normalize(-lightDir), l);
83+
float cutoff = cosAngle - cosOuter;
84+
if (falloff != 0.0) {
85+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
86+
}
87+
return cutoff >= 0.0 ? 1.0 : 0.0;
88+
}
89+
90+
/*
91+
* The methods 'computeSpotlightFactor2' and 'computeSpotlightFactor3' are alternatives to the 'computeSpotlightFactor'
92+
* method that was chosen for its better performance. They are kept for the case that a future changes will make them
93+
* more performant.
94+
*
95+
float computeSpotlightFactor2(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
96+
if (falloff != 0.0) {
97+
float cosAngle = dot(normalize(-lightDir), l);
98+
float cutoff = cosAngle - cosOuter;
99+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
100+
}
101+
if (cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
102+
return 1.0;
103+
}
104+
float cosAngle = dot(normalize(-lightDir), l);
105+
float cutoff = cosAngle - cosOuter;
106+
return cutoff >= 0.0 ? 1.0 : 0.0;
107+
}
108+
109+
float computeSpotlightFactor3(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
110+
float cosAngle = dot(normalize(-lightDir), l);
111+
float cutoff = cosAngle - cosOuter;
112+
if (falloff != 0.0) {
113+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
114+
}
115+
return cutoff >= 0.0 ? 1.0 : 0.0;
116+
}
117+
*/
118+
119+
void computeLight(int i, vec3 n, vec3 refl, float specPower, inout vec3 d, inout vec3 s) {
120+
Light light = lights[i];
121+
vec3 pos = lightTangentSpacePositions[i].xyz;
122+
float dist = length(pos);
123+
if (dist > light.range) {
124+
return;
125+
}
126+
vec3 l = normalize(pos);
127+
128+
vec3 lightDir = lightTangentSpaceDirections[i].xyz;
129+
float spotlightFactor = computeSpotlightFactor(l, lightDir, light.cosOuter, light.denom, light.falloff);
130+
131+
float invAttnFactor = light.attn.x + light.attn.y * dist + light.attn.z * dist * dist;
132+
133+
vec3 attenuatedColor = light.color.rgb * spotlightFactor / invAttnFactor;
134+
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
135+
s += pow(clamp(dot(-refl, l), 0.0, 1.0), specPower) * attenuatedColor;
136+
}
70137

71138
void main()
72139
{
73140
vec4 diffuse = apply_diffuse();
74141

75142
if (diffuse.a == 0.0) discard;
76143

144+
vec3 n = apply_normal();
145+
vec3 refl = reflect(normalize(eyePos), n);
146+
77147
vec3 d = vec3(0.0);
78148
vec3 s = vec3(0.0);
79149

80150
vec4 specular = apply_specular();
151+
float power = specular.a;
81152

82-
float maxRange = lights[0].range;
83-
float dist = length(lightTangentSpacePositions[0].xyz);
84-
if (dist <= maxRange) {
85-
vec3 n = apply_normal();
86-
vec3 refl = reflect(normalize(eyePos), n);
87-
vec3 l = normalize(lightTangentSpacePositions[0].xyz);
153+
computeLight(0, n, refl, power, d, s);
88154

89-
float power = specular.a;
90-
91-
vec3 attenuatedColor = (lights[0].color).rgb / (lights[0].attn.x + lights[0].attn.y * dist + lights[0].attn.z * dist * dist);
92-
d = clamp(dot(n, l), 0.0, 1.0) * attenuatedColor;
93-
s = pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
94-
}
95155
vec3 rez = (ambientColor + d) * diffuse.xyz + s * specular.rgb;
96156
rez += apply_selfIllum().xyz;
97157

‎modules/javafx.graphics/src/main/resources/com/sun/prism/es2/glsl/main2Lights.frag

+69-16
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,81 @@ struct Light {
5959
vec4 pos;
6060
vec3 color;
6161
vec3 attn;
62+
vec3 dir;
6263
float range;
64+
float cosOuter;
65+
float denom; // cosInner - cosOuter
66+
float falloff;
6367
};
6468

6569
uniform vec3 ambientColor;
6670
uniform Light lights[3];
6771

6872
varying vec3 eyePos;
6973
varying vec4 lightTangentSpacePositions[3];
74+
varying vec4 lightTangentSpaceDirections[3];
75+
76+
// Because pow(0, 0) is undefined (https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/pow.xhtml),
77+
// we need special treatment for falloff == 0 cases
78+
float computeSpotlightFactor(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
79+
if (falloff == 0.0 && cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
80+
return 1.0;
81+
}
82+
float cosAngle = dot(normalize(-lightDir), l);
83+
float cutoff = cosAngle - cosOuter;
84+
if (falloff != 0.0) {
85+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
86+
}
87+
return cutoff >= 0.0 ? 1.0 : 0.0;
88+
}
89+
90+
/*
91+
* The methods 'computeSpotlightFactor2' and 'computeSpotlightFactor3' are alternatives to the 'computeSpotlightFactor'
92+
* method that was chosen for its better performance. They are kept for the case that a future changes will make them
93+
* more performant.
94+
*
95+
float computeSpotlightFactor2(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
96+
if (falloff != 0.0) {
97+
float cosAngle = dot(normalize(-lightDir), l);
98+
float cutoff = cosAngle - cosOuter;
99+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
100+
}
101+
if (cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
102+
return 1.0;
103+
}
104+
float cosAngle = dot(normalize(-lightDir), l);
105+
float cutoff = cosAngle - cosOuter;
106+
return cutoff >= 0.0 ? 1.0 : 0.0;
107+
}
108+
109+
float computeSpotlightFactor3(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
110+
float cosAngle = dot(normalize(-lightDir), l);
111+
float cutoff = cosAngle - cosOuter;
112+
if (falloff != 0.0) {
113+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
114+
}
115+
return cutoff >= 0.0 ? 1.0 : 0.0;
116+
}
117+
*/
118+
119+
void computeLight(int i, vec3 n, vec3 refl, float specPower, inout vec3 d, inout vec3 s) {
120+
Light light = lights[i];
121+
vec3 pos = lightTangentSpacePositions[i].xyz;
122+
float dist = length(pos);
123+
if (dist > light.range) {
124+
return;
125+
}
126+
vec3 l = normalize(pos);
127+
128+
vec3 lightDir = lightTangentSpaceDirections[i].xyz;
129+
float spotlightFactor = computeSpotlightFactor(l, lightDir, light.cosOuter, light.denom, light.falloff);
130+
131+
float invAttnFactor = light.attn.x + light.attn.y * dist + light.attn.z * dist * dist;
132+
133+
vec3 attenuatedColor = light.color.rgb * spotlightFactor / invAttnFactor;
134+
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
135+
s += pow(clamp(dot(-refl, l), 0.0, 1.0), specPower) * attenuatedColor;
136+
}
70137

71138
void main()
72139
{
@@ -83,23 +150,9 @@ void main()
83150
vec4 specular = apply_specular();
84151
float power = specular.a;
85152

86-
float maxRange = lights[0].range;
87-
float dist = length(lightTangentSpacePositions[0].xyz);
88-
if (dist <= maxRange) {
89-
vec3 l = normalize(lightTangentSpacePositions[0].xyz);
90-
vec3 attenuatedColor = (lights[0].color).rgb / (lights[0].attn.x + lights[0].attn.y * dist + lights[0].attn.z * dist * dist);
91-
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
92-
s += pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
93-
}
153+
computeLight(0, n, refl, power, d, s);
154+
computeLight(1, n, refl, power, d, s);
94155

95-
maxRange = lights[1].range;
96-
dist = length(lightTangentSpacePositions[1].xyz);
97-
if (dist <= maxRange) {
98-
vec3 l = normalize(lightTangentSpacePositions[1].xyz);
99-
vec3 attenuatedColor = (lights[1].color).rgb / (lights[1].attn.x + lights[1].attn.y * dist + lights[1].attn.z * dist * dist);
100-
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
101-
s += pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
102-
}
103156
vec3 rez = (ambientColor + d) * diffuse.xyz + s * specular.rgb;
104157
rez += apply_selfIllum().xyz;
105158

‎modules/javafx.graphics/src/main/resources/com/sun/prism/es2/glsl/main3Lights.frag

+70-26
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,81 @@ struct Light {
5959
vec4 pos;
6060
vec3 color;
6161
vec3 attn;
62+
vec3 dir;
6263
float range;
64+
float cosOuter;
65+
float denom; // cosInner - cosOuter
66+
float falloff;
6367
};
6468

6569
uniform vec3 ambientColor;
6670
uniform Light lights[3];
6771

6872
varying vec3 eyePos;
6973
varying vec4 lightTangentSpacePositions[3];
74+
varying vec4 lightTangentSpaceDirections[3];
75+
76+
// Because pow(0, 0) is undefined (https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/pow.xhtml),
77+
// we need special treatment for falloff == 0 cases
78+
float computeSpotlightFactor(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
79+
if (falloff == 0.0 && cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
80+
return 1.0;
81+
}
82+
float cosAngle = dot(normalize(-lightDir), l);
83+
float cutoff = cosAngle - cosOuter;
84+
if (falloff != 0.0) {
85+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
86+
}
87+
return cutoff >= 0.0 ? 1.0 : 0.0;
88+
}
89+
90+
/*
91+
* The methods 'computeSpotlightFactor2' and 'computeSpotlightFactor3' are alternatives to the 'computeSpotlightFactor'
92+
* method that was chosen for its better performance. They are kept for the case that a future changes will make them
93+
* more performant.
94+
*
95+
float computeSpotlightFactor2(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
96+
if (falloff != 0.0) {
97+
float cosAngle = dot(normalize(-lightDir), l);
98+
float cutoff = cosAngle - cosOuter;
99+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
100+
}
101+
if (cosOuter == -1.0) { // point light optimization (cosOuter == -1 is outerAngle == 180)
102+
return 1.0;
103+
}
104+
float cosAngle = dot(normalize(-lightDir), l);
105+
float cutoff = cosAngle - cosOuter;
106+
return cutoff >= 0.0 ? 1.0 : 0.0;
107+
}
108+
109+
float computeSpotlightFactor3(vec3 l, vec3 lightDir, float cosOuter, float denom, float falloff) {
110+
float cosAngle = dot(normalize(-lightDir), l);
111+
float cutoff = cosAngle - cosOuter;
112+
if (falloff != 0.0) {
113+
return pow(clamp(cutoff / denom, 0.0, 1.0), falloff);
114+
}
115+
return cutoff >= 0.0 ? 1.0 : 0.0;
116+
}
117+
*/
118+
119+
void computeLight(int i, vec3 n, vec3 refl, float specPower, inout vec3 d, inout vec3 s) {
120+
Light light = lights[i];
121+
vec3 pos = lightTangentSpacePositions[i].xyz;
122+
float dist = length(pos);
123+
if (dist > light.range) {
124+
return;
125+
}
126+
vec3 l = normalize(pos);
127+
128+
vec3 lightDir = lightTangentSpaceDirections[i].xyz;
129+
float spotlightFactor = computeSpotlightFactor(l, lightDir, light.cosOuter, light.denom, light.falloff);
130+
131+
float invAttnFactor = light.attn.x + light.attn.y * dist + light.attn.z * dist * dist;
132+
133+
vec3 attenuatedColor = light.color.rgb * spotlightFactor / invAttnFactor;
134+
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
135+
s += pow(clamp(dot(-refl, l), 0.0, 1.0), specPower) * attenuatedColor;
136+
}
70137

71138
void main()
72139
{
@@ -83,32 +150,9 @@ void main()
83150
vec4 specular = apply_specular();
84151
float power = specular.a;
85152

86-
float maxRange = lights[0].range;
87-
float dist = length(lightTangentSpacePositions[0].xyz);
88-
if (dist <= maxRange) {
89-
vec3 l = normalize(lightTangentSpacePositions[0].xyz);
90-
vec3 attenuatedColor = (lights[0].color).rgb / (lights[0].attn.x + lights[0].attn.y * dist + lights[0].attn.z * dist * dist);
91-
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
92-
s += pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
93-
}
94-
95-
maxRange = lights[1].range;
96-
dist = length(lightTangentSpacePositions[1].xyz);
97-
if (dist <= maxRange) {
98-
vec3 l = normalize(lightTangentSpacePositions[1].xyz);
99-
vec3 attenuatedColor = (lights[1].color).rgb / (lights[1].attn.x + lights[1].attn.y * dist + lights[1].attn.z * dist * dist);
100-
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
101-
s += pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
102-
}
103-
104-
maxRange = lights[2].range;
105-
dist = length(lightTangentSpacePositions[2].xyz);
106-
if (dist <= maxRange) {
107-
vec3 l = normalize(lightTangentSpacePositions[2].xyz);
108-
vec3 attenuatedColor = (lights[2].color).rgb / (lights[2].attn.x + lights[2].attn.y * dist + lights[2].attn.z * dist * dist);
109-
d += clamp(dot(n,l), 0.0, 1.0) * attenuatedColor;
110-
s += pow(clamp(dot(-refl, l), 0.0, 1.0), power) * attenuatedColor;
111-
}
153+
computeLight(0, n, refl, power, d, s);
154+
computeLight(1, n, refl, power, d, s);
155+
computeLight(2, n, refl, power, d, s);
112156

113157
vec3 rez = (ambientColor + d) * diffuse.xyz + s * specular.rgb;
114158
rez += apply_selfIllum().xyz;

‎tests/performance/3DLighting/attenuation/AttenLightingSample.java

+56-9
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@
2525

2626
package attenuation;
2727

28+
import javafx.beans.binding.Bindings;
2829
import javafx.beans.property.DoubleProperty;
30+
import javafx.geometry.Point3D;
2931
import javafx.scene.PointLight;
32+
import javafx.scene.SpotLight;
3033
import javafx.scene.control.Label;
3134
import javafx.scene.control.Slider;
3235
import javafx.scene.control.TextField;
3336
import javafx.scene.layout.HBox;
3437
import javafx.scene.layout.VBox;
38+
import javafx.scene.transform.Rotate;
3539
import javafx.util.converter.NumberStringConverter;
3640

3741
/**
@@ -40,25 +44,68 @@
4044
public class AttenLightingSample extends LightingSample {
4145

4246
@Override
43-
protected VBox addLightControls(PointLight light) {
47+
protected VBox addPointLightControls(PointLight light) {
4448
var vbox = super.addLightControls(light);
45-
var range = createSliderControl("range", light.maxRangeProperty(), 0, 100, light.getMaxRange());
49+
var range = createSliderControl("range", light.maxRangeProperty(), 0, 500, 150);
4650
var c = createSliderControl("constant", light.constantAttenuationProperty(), -1, 1, light.getConstantAttenuation());
47-
var lc = createSliderControl("linear", light.linearAttenuationProperty(), -1, 1, light.getLinearAttenuation());
48-
var qc = createSliderControl("quadratic", light.quadraticAttenuationProperty(), -1, 1, light.getQuadraticAttenuation());
51+
var lc = createSliderControl("linear", light.linearAttenuationProperty(), -0.1, 0.1, light.getLinearAttenuation());
52+
var qc = createSliderControl("quadratic", light.quadraticAttenuationProperty(), -0.01, 0.01, light.getQuadraticAttenuation());
4953
vbox.getChildren().addAll(range, c, lc, qc);
5054
return vbox;
5155
}
5256

57+
@Override
58+
protected VBox addSpotLightControls(SpotLight light) {
59+
var vbox = addPointLightControls(light);
60+
var ia = createSliderControl("inner", light.innerAngleProperty(), 0, 180, light.getInnerAngle());
61+
var oa = createSliderControl("outer", light.outerAngleProperty(), 0, 180, light.getOuterAngle());
62+
var fo = createSliderControl("falloff", light.falloffProperty(), -5, 5, light.getFalloff());
63+
64+
var transX = new Rotate(0, Rotate.X_AXIS);
65+
var transY = new Rotate(0, Rotate.Y_AXIS);
66+
var transZ = new Rotate(0, Rotate.Z_AXIS);
67+
light.getTransforms().addAll(transX, transY, transZ);
68+
var rotX = createSliderControl("rot x", transX.angleProperty(), -180, 180, 0);
69+
var rotY = createSliderControl("rot y", transY.angleProperty(), -180, 180, 0);
70+
var rotZ = createSliderControl("rot z", transZ.angleProperty(), -180, 180, 0);
71+
72+
var sliderX = createSlider(-5, 5, light.getDirection().getX());
73+
var sliderY = createSlider(-5, 5, light.getDirection().getY());
74+
var sliderZ = createSlider(-5, 5, light.getDirection().getZ());
75+
light.directionProperty().bind(Bindings.createObjectBinding(() ->
76+
new Point3D(sliderX.getValue(), sliderY.getValue(), sliderZ.getValue()),
77+
sliderX.valueProperty(), sliderY.valueProperty(), sliderZ.valueProperty()));
78+
var dirX = createSliderControl("dir x", sliderX);
79+
var dirY = createSliderControl("dir y", sliderY);
80+
var dirZ = createSliderControl("dir z", sliderZ);
81+
82+
vbox.getChildren().addAll(ia, oa, fo, rotX, rotY, rotZ, dirX, dirY, dirZ);
83+
return vbox;
84+
}
85+
5386
private HBox createSliderControl(String name, DoubleProperty property, double min, double max, double start) {
54-
var slider = new Slider(min, max, start);
55-
slider.setShowTickMarks(true);
56-
slider.setShowTickLabels(true);
57-
property.bindBidirectional(slider.valueProperty());
87+
var slider = createSlider(min, max, start);
88+
property.bind(slider.valueProperty());
89+
return createSliderControl(name, slider);
90+
}
91+
92+
private HBox createSliderControl(String name, Slider slider) {
93+
var tf = createTextField(slider);
94+
return new HBox(5, new Label(name), slider, tf);
95+
}
96+
97+
private TextField createTextField(Slider slider) {
5898
var tf = new TextField();
5999
tf.textProperty().bindBidirectional(slider.valueProperty(), new NumberStringConverter());
60100
tf.setMaxWidth(50);
61-
return new HBox(5, new Label(name), slider, tf);
101+
return tf;
102+
}
103+
104+
private Slider createSlider(double min, double max, double start) {
105+
var slider = new Slider(min, max, start);
106+
slider.setShowTickMarks(true);
107+
slider.setShowTickLabels(true);
108+
return slider;
62109
}
63110

64111
public static void main(String[] args) {

‎tests/performance/3DLighting/attenuation/CameraScene3D.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@
4343
class CameraScene3D extends Pane {
4444

4545
public DoubleProperty xPan = new SimpleDoubleProperty();
46-
public DoubleProperty yPan = new SimpleDoubleProperty(-10);
46+
public DoubleProperty yPan = new SimpleDoubleProperty();
4747
public DoubleProperty zoom = new SimpleDoubleProperty();
4848
public DoubleProperty zAngle = new SimpleDoubleProperty();
4949
public DoubleProperty isometricAngle = new SimpleDoubleProperty();
5050

5151
public DoubleProperty panSensitivity = new SimpleDoubleProperty(1);
5252
public DoubleProperty zoomSensitivity = new SimpleDoubleProperty(1);
5353
public DoubleProperty zRotationSensitivity = new SimpleDoubleProperty(1);
54+
public DoubleProperty isoRotationSensitivity = new SimpleDoubleProperty(1);
5455
public BooleanProperty isZoomTotal = new SimpleBooleanProperty();
5556

5657
protected PerspectiveCamera camera = new PerspectiveCamera(true);
@@ -144,6 +145,8 @@ private final void setUIBindings() {
144145
deltaX = positiveY ? -deltaX : deltaX;
145146
deltaY = positiveX ? deltaY : -deltaY;
146147
rotate((deltaX + deltaY)/2);
148+
} else if (e.getButton() == MouseButton.MIDDLE) {
149+
swivle(deltaY);
147150
}
148151
});
149152
}
@@ -170,4 +173,8 @@ private void zoom(double amount) {
170173
private void rotate(double amount) {
171174
zAngle.set(zAngle.get() - amount * zRotationSensitivity.get());
172175
}
176+
177+
private void swivle(double amount) {
178+
isometricAngle.set(isometricAngle.get() - amount * isoRotationSensitivity.get());
179+
}
173180
}

‎tests/performance/3DLighting/attenuation/Environment.java

+45-14
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,33 @@
2929
import java.util.List;
3030

3131
import javafx.scene.AmbientLight;
32+
import javafx.scene.Group;
33+
import javafx.scene.LightBase;
34+
import javafx.scene.Node;
3235
import javafx.scene.PointLight;
36+
import javafx.scene.SpotLight;
3337
import javafx.scene.paint.Color;
3438
import javafx.scene.paint.PhongMaterial;
39+
import javafx.scene.shape.Box;
3540
import javafx.scene.shape.MeshView;
36-
import javafx.scene.shape.Shape3D;
3741
import javafx.scene.shape.Sphere;
3842
import javafx.scene.shape.TriangleMesh;
43+
import javafx.scene.transform.Rotate;
3944

4045
class Environment extends CameraScene3D {
4146

42-
private final PointLight light1 = new PointLight(Color.RED);
43-
private final PointLight light2 = new PointLight(Color.BLUE);
44-
private final PointLight light3 = new PointLight(Color.MAGENTA);
45-
final PointLight[] lights = {light1, light2, light3};
47+
private final static double LIGHT_Z_DIST = 50;
48+
private final static double LIGHT_X_DIST = 50;
4649

47-
private Shape3D currentShape;
50+
private final PointLight pointLight1 = new PointLight(Color.RED);
51+
private final PointLight pointLight2 = new PointLight(Color.BLUE);
52+
private final PointLight pointLight3 = new PointLight(Color.MAGENTA);
53+
private final SpotLight spotLight1 = new SpotLight(Color.RED);
54+
private final SpotLight spotLight2 = new SpotLight(Color.BLUE);
55+
private final SpotLight spotLight3 = new SpotLight(Color.MAGENTA);
56+
final LightBase[] lights = {pointLight1, pointLight2, pointLight3, spotLight1, spotLight2, spotLight3};
57+
58+
private Node currentShape;
4859

4960
private final AmbientLight worldLight = new AmbientLight();
5061

@@ -53,24 +64,44 @@ class Environment extends CameraScene3D {
5364
zoom.set(-350);
5465

5566
for (var light : lights) {
56-
light.setTranslateZ(-50);
67+
light.setTranslateZ(-LIGHT_Z_DIST);
5768
var lightRep = new Sphere(2);
5869
lightRep.setMaterial(new PhongMaterial(light.getColor()));
5970
lightRep.translateXProperty().bind(light.translateXProperty());
6071
lightRep.translateYProperty().bind(light.translateYProperty());
6172
lightRep.translateZProperty().bind(light.translateZProperty());
62-
rootGroup.getChildren().addAll(light, lightRep);
73+
rootGroup.getChildren().addAll(light , lightRep);
6374
}
64-
light1.setTranslateX(40);
65-
light2.setTranslateX(-40);
66-
light1.setUserData("RED");
67-
light2.setUserData("BLUE");
68-
light3.setUserData("MAGENTA");
75+
76+
pointLight1.setTranslateX(LIGHT_X_DIST);
77+
spotLight1.setTranslateX(LIGHT_X_DIST);
78+
pointLight2.setTranslateX(-LIGHT_X_DIST);
79+
spotLight2.setTranslateX(-LIGHT_X_DIST);
80+
81+
pointLight1.setUserData("RED");
82+
pointLight2.setUserData("BLUE");
83+
pointLight3.setUserData("MAGENTA");
84+
spotLight1.setUserData("RED");
85+
spotLight2.setUserData("BLUE");
86+
spotLight3.setUserData("MAGENTA");
6987

7088
rootGroup.getChildren().add(worldLight);
7189
rootGroup.setMouseTransparent(true);
7290
}
7391

92+
Group createBoxes() {
93+
var front = new Box(200, 200, 1);
94+
var back = new Box(200, 200, 1);
95+
var side = new Box(200, 200, 1);
96+
side.setRotationAxis(Rotate.Y_AXIS);
97+
side.setRotate(90);
98+
side.setTranslateX(LIGHT_Z_DIST * 2);
99+
side.setTranslateZ(-LIGHT_Z_DIST);
100+
front.setTranslateZ(LIGHT_Z_DIST);
101+
back.setTranslateZ(-LIGHT_Z_DIST * 3);
102+
return new Group(front, back, side);
103+
}
104+
74105
Sphere createSphere(int subdivisions) {
75106
return new Sphere(50, subdivisions);
76107
}
@@ -112,7 +143,7 @@ MeshView createMeshView(int quadNum) {
112143
return mv;
113144
}
114145

115-
void switchTo(Shape3D node) {
146+
void switchTo(Node node) {
116147
worldLight.getExclusionScope().remove(currentShape);
117148
worldLight.getExclusionScope().add(node);
118149
rootGroup.getChildren().remove(currentShape);

‎tests/performance/3DLighting/attenuation/FPSCounter.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ public void handle(long now) {
5858
totalElapsedTime += currTime - lastTime;
5959
totalElapsedFrames += 1;
6060

61-
double elapsedSeconds = (double) elapsedTime / 1e9;
62-
double totalElapsedSeconds = (double) totalElapsedTime / 1e9;
61+
double elapsedSeconds = elapsedTime / 1e9;
62+
double totalElapsedSeconds = totalElapsedTime / 1e9;
6363
if (elapsedSeconds >= 5.0) {
6464
double fps = elapsedFrames / elapsedSeconds;
6565
System.out.println();

‎tests/performance/3DLighting/attenuation/LightingSample.java

+88-28
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,25 @@
2828
import javafx.animation.Animation;
2929
import javafx.animation.TranslateTransition;
3030
import javafx.application.Application;
31+
import javafx.beans.binding.When;
32+
import javafx.scene.Group;
33+
import javafx.scene.LightBase;
34+
import javafx.scene.Node;
3135
import javafx.scene.PointLight;
3236
import javafx.scene.Scene;
37+
import javafx.scene.SpotLight;
3338
import javafx.scene.control.Button;
3439
import javafx.scene.control.CheckBox;
3540
import javafx.scene.control.Label;
36-
import javafx.scene.control.Separator;
41+
import javafx.scene.control.ScrollPane;
3742
import javafx.scene.control.Slider;
43+
import javafx.scene.control.TitledPane;
3844
import javafx.scene.layout.HBox;
3945
import javafx.scene.layout.Priority;
4046
import javafx.scene.layout.VBox;
41-
import javafx.scene.shape.Shape3D;
47+
import javafx.scene.paint.Color;
48+
import javafx.scene.paint.PhongMaterial;
49+
import javafx.scene.shape.Box;
4250
import javafx.stage.Stage;
4351
import javafx.util.Duration;
4452
import javafx.util.converter.NumberStringConverter;
@@ -59,18 +67,38 @@ public class LightingSample extends Application {
5967
public void start(Stage stage) throws Exception {
6068
environment.setStyle("-fx-background-color: teal");
6169

62-
var subdivisionSlider = new Slider(10, 200, 60);
63-
subdivisionSlider.setMajorTickUnit(10);
64-
setupSlier(subdivisionSlider);
70+
var sphereControls = createSphereControls();
71+
var meshControls = createMeshControls();
72+
var boxesControls = createBoxesControls();
6573

66-
var subdivisionLabel = new Label();
67-
subdivisionLabel.textProperty().bindBidirectional(subdivisionSlider.valueProperty(), new NumberStringConverter("#"));
74+
var playButton = new Button("Start");
75+
playButton.setOnAction(e -> startMeasurement());
6876

69-
var sphere = new Button("Sphere");
70-
sphere.setOnAction(e -> switchTo(environment.createSphere((int) subdivisionSlider.getValue())));
77+
var stopButton = new Button("Stop");
78+
stopButton.setOnAction(e -> stopMeasurement());
79+
80+
var controls = new VBox(3, sphereControls, meshControls, boxesControls, new HBox(5, playButton, stopButton));
81+
for (var light : environment.lights) {
82+
VBox vBox = null;
83+
if (light instanceof SpotLight) {
84+
vBox = addSpotLightControls((SpotLight) light);
85+
} else if (light instanceof PointLight) {
86+
vBox = addPointLightControls((PointLight) light);
87+
}
88+
controls.getChildren().add(new TitledPane(light.getUserData() + " " + light.getClass().getSimpleName(), vBox));
89+
}
7190

72-
var quadSlider = new Slider(500, 10_000, 1000);
73-
quadSlider.setMajorTickUnit(500);
91+
var hBox = new HBox(new ScrollPane(controls), environment);
92+
HBox.setHgrow(environment, Priority.ALWAYS);
93+
stage.setScene(new Scene(hBox));
94+
stage.setWidth(1100);
95+
stage.setHeight(735);
96+
stage.show();
97+
}
98+
99+
private HBox createMeshControls() {
100+
var quadSlider = new Slider(100, 5000, 1000);
101+
quadSlider.setMajorTickUnit(100);
74102
setupSlier(quadSlider);
75103

76104
var quadLabel = new Label();
@@ -79,20 +107,37 @@ public void start(Stage stage) throws Exception {
79107
var mesh = new Button("Mesh");
80108
mesh.setOnAction(e -> switchTo(environment.createMeshView((int) quadSlider.getValue())));
81109

82-
var sphereBox = new HBox(sphere, subdivisionSlider, subdivisionLabel);
83110
var meshBox = new HBox(mesh, quadSlider, quadLabel);
111+
return meshBox;
112+
}
84113

85-
var controls = new VBox(sphereBox, meshBox);
86-
for (var light : environment.lights) {
87-
controls.getChildren().add(addLightControls(light));
88-
}
114+
private HBox createSphereControls() {
115+
var subdivisionSlider = new Slider(10, 1000, 60);
116+
subdivisionSlider.setMajorTickUnit(50);
117+
setupSlier(subdivisionSlider);
89118

90-
var hBox = new HBox(controls, environment);
91-
HBox.setHgrow(environment, Priority.ALWAYS);
92-
stage.setScene(new Scene(hBox));
93-
stage.setWidth(1100);
94-
stage.setHeight(735);
95-
stage.show();
119+
var subdivisionLabel = new Label();
120+
subdivisionLabel.textProperty().bindBidirectional(subdivisionSlider.valueProperty(), new NumberStringConverter("#"));
121+
122+
var sphere = new Button("Sphere");
123+
sphere.setOnAction(e -> switchTo(environment.createSphere((int) subdivisionSlider.getValue())));
124+
125+
var sphereBox = new HBox(sphere, subdivisionSlider, subdivisionLabel);
126+
return sphereBox;
127+
}
128+
129+
private HBox createBoxesControls() {
130+
var box = new Button("Boxes (static)");
131+
var specular = new CheckBox("Specular");
132+
var specularBinding = new When(specular.selectedProperty()).then(Color.WHITE).otherwise(Color.BLACK);
133+
var mat = new PhongMaterial(Color.WHITE);
134+
mat.specularColorProperty().bind(specularBinding);
135+
box.setOnAction(e -> {
136+
Group boxes = environment.createBoxes();
137+
boxes.getChildren().forEach(n -> ((Box) n).setMaterial(mat));
138+
switchTo(boxes);
139+
});
140+
return new HBox(5, box, specular);
96141
}
97142

98143
private void setupSlier(Slider slider) {
@@ -102,11 +147,18 @@ private void setupSlier(Slider slider) {
102147
slider.setSnapToTicks(true);
103148
}
104149

105-
protected VBox addLightControls(PointLight light) {
150+
protected VBox addPointLightControls(PointLight light) {
151+
return addLightControls(light);
152+
}
153+
154+
protected VBox addSpotLightControls(SpotLight light) {
155+
return addLightControls(light);
156+
}
157+
158+
protected VBox addLightControls(LightBase light) {
106159
var lightOn = new CheckBox("On/Off");
107-
lightOn.setSelected(true);
108160
light.lightOnProperty().bind(lightOn.selectedProperty());
109-
return new VBox(new Separator(), new Label(light.getUserData() + " light"), lightOn);
161+
return new VBox(lightOn);
110162
}
111163

112164
private TranslateTransition createAnimation() {
@@ -118,15 +170,23 @@ private TranslateTransition createAnimation() {
118170
return anim;
119171
}
120172

121-
private void switchTo(Shape3D node) {
122-
fpsCouner.stop();
123-
fpsCouner.reset();
173+
private void switchTo(Node node) {
174+
stopMeasurement();
124175
environment.switchTo(node);
125176
animation.setNode(node);
177+
}
178+
179+
private void startMeasurement() {
126180
animation.playFromStart();
127181
fpsCouner.start();
128182
}
129183

184+
private void stopMeasurement() {
185+
fpsCouner.stop();
186+
fpsCouner.reset();
187+
animation.stop();
188+
}
189+
130190
public static void main(String[] args) {
131191
launch(args);
132192
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.javafx.scene.lighting3D;
27+
28+
import java.util.concurrent.CountDownLatch;
29+
30+
import org.junit.AfterClass;
31+
32+
import javafx.application.Application;
33+
import javafx.application.Platform;
34+
import javafx.scene.Group;
35+
import javafx.scene.LightBase;
36+
import javafx.scene.Scene;
37+
import javafx.scene.image.WritableImage;
38+
import javafx.scene.shape.Box;
39+
import javafx.stage.Stage;
40+
import javafx.stage.WindowEvent;
41+
42+
// Since there appears to be a bug in snapshot with subscene, we are taking a snapshot of the scene and not
43+
// the box, so the center of the box will be at the top left, (0, 0), of the image, and the light is
44+
// straight in front. Without attenuation, at (0, 0) it will give its full color.
45+
//
46+
// x
47+
// -----------
48+
// | /
49+
// | / d - the distance of the light from the object
50+
// | / x - the horizontal distance of the sample point on the object
51+
// | / a - the angle to the sample point
52+
// d| /
53+
// | / tan(a) = x/d
54+
// | / cos(a) = cos(atan(x/d)) = 1 / sqrt((x/d)^2 + 1)
55+
// |a /
56+
// | /
57+
// |/
58+
//
59+
public abstract class LightingTest {
60+
61+
// 1d/255 is the smallest color resolution, but we use 10d/255 to avoid precision problems
62+
protected static final double DELTA = 10d/255;
63+
protected static final String FAIL_MESSAGE = "Wrong color value";
64+
65+
protected static final int LIGHT_DIST = 60;
66+
67+
protected static LightBase light;
68+
69+
private static final Box BOX = new Box(150, 150, 1);
70+
71+
protected static CountDownLatch startupLatch;
72+
private static Stage stage;
73+
74+
public static class TestApp extends Application {
75+
76+
@Override
77+
public void start(Stage mainStage) {
78+
stage = mainStage;
79+
light.setTranslateZ(-LIGHT_DIST);
80+
stage.setScene(new Scene(new Group(light, BOX)));
81+
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e -> Platform.runLater(startupLatch::countDown));
82+
stage.show();
83+
}
84+
}
85+
86+
protected WritableImage snapshot() {
87+
return BOX.getScene().snapshot(null);
88+
}
89+
90+
protected double calculateLambertTerm(double x) {
91+
return Math.cos(Math.atan(x/LIGHT_DIST));
92+
}
93+
94+
@AfterClass
95+
public static void teardown() {
96+
Platform.runLater(() -> {
97+
stage.hide();
98+
Platform.exit();
99+
});
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,41 +25,33 @@
2525

2626
package test.javafx.scene.lighting3D;
2727

28+
import static org.junit.Assert.assertEquals;
2829
import static org.junit.Assert.assertTrue;
2930
import static org.junit.Assume.assumeTrue;
30-
import static org.junit.Assert.assertEquals;
3131

3232
import java.util.concurrent.CountDownLatch;
3333
import java.util.concurrent.TimeUnit;
3434

35-
import org.junit.AfterClass;
3635
import org.junit.Before;
3736
import org.junit.BeforeClass;
3837
import org.junit.Test;
3938

4039
import javafx.application.Application;
4140
import javafx.application.ConditionalFeature;
4241
import javafx.application.Platform;
43-
import javafx.scene.Group;
4442
import javafx.scene.PointLight;
45-
import javafx.scene.Scene;
4643
import javafx.scene.paint.Color;
47-
import javafx.scene.shape.Box;
48-
import javafx.stage.Stage;
49-
import javafx.stage.WindowEvent;
5044
import test.util.Util;
5145

52-
public class PointLightAttenuationTest {
46+
public class PointLightAttenuationTest extends LightingTest {
47+
48+
// X coordinates for the point used in Lambert tests
49+
private static final int[] LAMBERT_SAMPLE_DISTS = new int[] {0, 30, 60};
5350

54-
// 1d/255 is the smallest color resolution, but we use 10d/255 to avoid precision problems
55-
private static final double DELTA = 10d/255;
56-
private static final int LIGTH_DIST = 60;
57-
private static final int SAMPLE_DIST = 60;
51+
// X coordinate for the point used in attenuation tests
52+
private static final int ATTN_SAMPLE_DIST = LIGHT_DIST;
5853

59-
private static CountDownLatch startupLatch;
60-
private static Stage stage;
61-
private static PointLight light = new PointLight(Color.BLUE);
62-
private static Box box = new Box(150, 150, 1);
54+
private static final PointLight LIGHT = new PointLight(Color.BLUE);
6355

6456
public static void main(String[] args) throws Exception {
6557
initFX();
@@ -68,78 +60,76 @@ public static void main(String[] args) throws Exception {
6860
@BeforeClass
6961
public static void initFX() throws Exception {
7062
startupLatch = new CountDownLatch(1);
71-
new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
63+
LightingTest.light = LIGHT;
64+
new Thread(() -> Application.launch(TestApp.class, (String[]) null)).start();
7265
assertTrue("Timeout waiting for FX runtime to start", startupLatch.await(15, TimeUnit.SECONDS));
7366
}
7467

7568
@Before
7669
public void setupEach() {
7770
assumeTrue(Platform.isSupported(ConditionalFeature.SCENE3D));
71+
LIGHT.setLinearAttenuation(0);
72+
LIGHT.setQuadraticAttenuation(0);
73+
LIGHT.setMaxRange(Double.POSITIVE_INFINITY);
7874
}
7975

80-
public static class TestApp extends Application {
81-
82-
@Override
83-
public void start(Stage mainStage) {
84-
stage = mainStage;
85-
light.setTranslateZ(-LIGTH_DIST);
86-
var root = new Group(light, box);
87-
var scene = new Scene(root);
88-
stage.setScene(scene);
89-
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e -> Platform.runLater(startupLatch::countDown));
90-
stage.show();
91-
}
76+
// The Lambert term is dot(N,L) = cos(a)
77+
@Test
78+
public void testLambert() {
79+
Util.runAndWait(() -> {
80+
var snapshot = snapshot();
81+
for (int x : LAMBERT_SAMPLE_DISTS) {
82+
double sampledBlue = snapshot.getPixelReader().getColor(x, 0).getBlue();
83+
assertEquals(FAIL_MESSAGE + " for " + x, calculateLambertTerm(x), sampledBlue, DELTA);
84+
}
85+
});
9286
}
9387

9488
@Test
9589
public void testAttenuation() {
9690
Util.runAndWait(() -> {
97-
// Since there appears to be a bug in snapshot with subscene, we are taking a snapshot of the scene and not
98-
// the box, so the center of the box will be at the top left, (0, 0), of the image, and the light is
99-
// straight in front. Without attenuation, at (0, 0) it will give its full color. At (SAMPLE_DIST, 0) and
100-
// LIGTH_DIST == SAMPLE_DIST, it will give cos(45) = 1/sqrt(2) of its color.
101-
var snapshot = box.getScene().snapshot(null);
102-
double nonAttenBlueCenter = snapshot.getPixelReader().getColor(0, 0).getBlue();
103-
double nonAttenBlueDiag = snapshot.getPixelReader().getColor(SAMPLE_DIST, 0).getBlue();
104-
assertEquals("Wrong color value", 1, nonAttenBlueCenter, DELTA);
105-
assertEquals("Wrong color value", 1/Math.sqrt(2), nonAttenBlueDiag, DELTA);
106-
107-
double diagDist = Math.sqrt(LIGTH_DIST * LIGTH_DIST + SAMPLE_DIST * SAMPLE_DIST);
108-
109-
light.setLinearAttenuation(0.01);
110-
double attnCenter = 1 / (1 + 0.01 * LIGTH_DIST);
111-
double attnDiag = 1 / (1 + 0.01 * diagDist);
112-
snapshot = box.getScene().snapshot(null);
113-
double attenBlueCenter = snapshot.getPixelReader().getColor(0, 0).getBlue();
114-
double attenBlueDiag = snapshot.getPixelReader().getColor(SAMPLE_DIST, 0).getBlue();
115-
assertEquals("Wrong color value", nonAttenBlueCenter * attnCenter, attenBlueCenter, DELTA);
116-
assertEquals("Wrong color value", nonAttenBlueDiag * attnDiag, attenBlueDiag, DELTA);
117-
118-
light.setLinearAttenuation(0);
119-
light.setQuadraticAttenuation(0.01);
120-
attnCenter = 1 / (1 + 0.01 * LIGTH_DIST * LIGTH_DIST);
121-
attnDiag = 1 / (1 + 0.01 * diagDist * diagDist);
122-
snapshot = box.getScene().snapshot(null);
123-
attenBlueCenter = snapshot.getPixelReader().getColor(0, 0).getBlue();
124-
attenBlueDiag = snapshot.getPixelReader().getColor(SAMPLE_DIST, 0).getBlue();
125-
assertEquals("Wrong color value", nonAttenBlueCenter * attnCenter, attenBlueCenter, DELTA);
126-
assertEquals("Wrong color value", nonAttenBlueDiag * attnDiag, attenBlueDiag, DELTA);
127-
128-
light.setQuadraticAttenuation(0);
129-
light.setMaxRange((LIGTH_DIST + diagDist) / 2);
130-
snapshot = box.getScene().snapshot(null);
131-
nonAttenBlueCenter = snapshot.getPixelReader().getColor(0, 0).getBlue();
132-
nonAttenBlueDiag = snapshot.getPixelReader().getColor(SAMPLE_DIST, 0).getBlue();
133-
assertEquals("Wrong color value, should be in range", 1, nonAttenBlueCenter, DELTA);
134-
assertEquals("Wrong color value, should be out of range", 0, nonAttenBlueDiag, DELTA);
91+
double diagDist = Math.sqrt(LIGHT_DIST * LIGHT_DIST + ATTN_SAMPLE_DIST * ATTN_SAMPLE_DIST);
92+
double lambertCenter = calculateLambertTerm(0);
93+
double lambertSample = calculateLambertTerm(ATTN_SAMPLE_DIST);
94+
95+
LIGHT.setLinearAttenuation(0.01);
96+
doAttenuationTest(diagDist, lambertCenter, lambertSample);
97+
98+
LIGHT.setLinearAttenuation(0);
99+
LIGHT.setQuadraticAttenuation(0.01);
100+
doAttenuationTest(diagDist, lambertCenter, lambertSample);
135101
});
136102
}
137103

138-
@AfterClass
139-
public static void teardown() {
140-
Platform.runLater(() -> {
141-
stage.hide();
142-
Platform.exit();
104+
private void doAttenuationTest(double diagDist, double lambertCenter, double lambertSample) {
105+
var snapshot = snapshot();
106+
107+
var attn = calculateAttenuationFactor(LIGHT_DIST);
108+
var sampledBlue = snapshot.getPixelReader().getColor(0, 0).getBlue();
109+
assertEquals(FAIL_MESSAGE, lambertCenter * attn, sampledBlue, DELTA);
110+
111+
attn = calculateAttenuationFactor(diagDist);
112+
sampledBlue = snapshot.getPixelReader().getColor(ATTN_SAMPLE_DIST, 0).getBlue();
113+
assertEquals(FAIL_MESSAGE, lambertSample * attn, sampledBlue, DELTA);
114+
}
115+
116+
@Test
117+
public void testRange() {
118+
Util.runAndWait(() -> {
119+
double diagDist = Math.sqrt(LIGHT_DIST * LIGHT_DIST + ATTN_SAMPLE_DIST * ATTN_SAMPLE_DIST);
120+
LIGHT.setMaxRange((LIGHT_DIST + diagDist) / 2);
121+
var snapshot = snapshot();
122+
123+
double sampledBlue = snapshot.getPixelReader().getColor(0, 0).getBlue();
124+
assertEquals(FAIL_MESSAGE + ", should be in range", 1, sampledBlue, DELTA);
125+
126+
sampledBlue = snapshot.getPixelReader().getColor(ATTN_SAMPLE_DIST, 0).getBlue();
127+
assertEquals(FAIL_MESSAGE + ", should be out of range", 0, sampledBlue, DELTA);
143128
});
144129
}
130+
131+
private double calculateAttenuationFactor(double dist) {
132+
return 1 / (LIGHT.getConstantAttenuation() + LIGHT.getLinearAttenuation() * dist
133+
+ LIGHT.getQuadraticAttenuation() * dist * dist);
134+
}
145135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.javafx.scene.lighting3D;
27+
28+
import static org.junit.Assert.assertEquals;
29+
import static org.junit.Assert.assertTrue;
30+
import static org.junit.Assume.assumeTrue;
31+
32+
import java.util.concurrent.CountDownLatch;
33+
import java.util.concurrent.TimeUnit;
34+
35+
import org.junit.Before;
36+
import org.junit.BeforeClass;
37+
import org.junit.Test;
38+
39+
import javafx.application.Application;
40+
import javafx.application.ConditionalFeature;
41+
import javafx.application.Platform;
42+
import javafx.scene.SpotLight;
43+
import javafx.scene.paint.Color;
44+
import test.util.Util;
45+
46+
public class SpotLightAttenuationTest extends LightingTest {
47+
48+
// Angles for points used in spotlight factor tests
49+
private static final double INNER_ANGLE = 20;
50+
private static final double OUTER_ANGLE = 40;
51+
private static final int INSIDE_ANGLE_SAMPLE = 18;
52+
private static final int MIDDLE_ANGLE_SAMPLE = 30;
53+
private static final int OUTSIDE_ANGLE_SAMPLE = 42;
54+
55+
private static final double[] FALLOFF_FACTORS = new double[] {0.5, 1, 1.5};
56+
57+
private static final SpotLight LIGHT = new SpotLight(Color.BLUE);
58+
59+
public static void main(String[] args) throws Exception {
60+
initFX();
61+
}
62+
63+
@BeforeClass
64+
public static void initFX() throws Exception {
65+
startupLatch = new CountDownLatch(1);
66+
LightingTest.light = LIGHT;
67+
new Thread(() -> Application.launch(TestApp.class, (String[]) null)).start();
68+
assertTrue("Timeout waiting for FX runtime to start", startupLatch.await(15, TimeUnit.SECONDS));
69+
}
70+
71+
@Before
72+
public void setupEach() {
73+
assumeTrue(Platform.isSupported(ConditionalFeature.SCENE3D));
74+
}
75+
76+
@Test
77+
public void testSpotlightAttenuation() {
78+
Util.runAndWait(() -> {
79+
LIGHT.setInnerAngle(INNER_ANGLE);
80+
LIGHT.setOuterAngle(OUTER_ANGLE);
81+
for (double falloff : FALLOFF_FACTORS) {
82+
LIGHT.setFalloff(falloff);
83+
var snapshot = snapshot();
84+
85+
int innerX = angleToDist(INSIDE_ANGLE_SAMPLE);
86+
double spotFactor = 1;
87+
double sampledBlue = snapshot.getPixelReader().getColor(innerX, 0).getBlue();
88+
assertEquals(FAIL_MESSAGE, calculateLambertTerm(innerX) * spotFactor, sampledBlue, DELTA);
89+
90+
int middleX = angleToDist(MIDDLE_ANGLE_SAMPLE);
91+
spotFactor = calculateSpotlightFactor(MIDDLE_ANGLE_SAMPLE);
92+
sampledBlue = snapshot.getPixelReader().getColor(middleX, 0).getBlue();
93+
assertEquals(FAIL_MESSAGE, calculateLambertTerm(middleX) * spotFactor, sampledBlue, DELTA);
94+
95+
int outerX = angleToDist(OUTSIDE_ANGLE_SAMPLE);
96+
spotFactor = 0;
97+
sampledBlue = snapshot.getPixelReader().getColor(outerX, 0).getBlue();
98+
assertEquals(FAIL_MESSAGE, calculateLambertTerm(outerX) * spotFactor, sampledBlue, DELTA);
99+
}
100+
});
101+
}
102+
103+
// I = pow((cosAngle - cosOuter) / (cosInner - cosOuter), falloff)
104+
private double calculateSpotlightFactor(double degrees) {
105+
double numerator = degCos(degrees) - degCos(LIGHT.getOuterAngle());
106+
double denom = degCos(LIGHT.getInnerAngle()) - degCos(LIGHT.getOuterAngle());
107+
return Math.pow(numerator / denom, LIGHT.getFalloff());
108+
}
109+
110+
private double degCos(double degrees) {
111+
return Math.cos(Math.toRadians(degrees));
112+
}
113+
114+
private int angleToDist(double degrees) {
115+
return (int) (LIGHT_DIST * Math.tan(Math.toRadians(degrees)));
116+
}
117+
}

1 commit comments

Comments
 (1)

openjdk-notifier[bot] commented on Jun 24, 2021

@openjdk-notifier[bot]
Please sign in to comment.