Skip to content

Commit 344c491

Browse files
authored
Light control fix (#1466)
* Updates the light control spatialToLight method for Directional and Spot lights to better handle rotations. The directional light was using outdated code from before 3.0 that didn't make sense in these days, to the point of confusion whenever the Also, the spotlight code wasn't working properly, as can be demonstrated by the new TestLightControlSpot test. Thanks @pspeed42 for the help with simplifying the quaternion. * Updates the light control lightToSpatial so it actually works with global coordinates and spot lights. The spot light wasn't implemented at all, and the directional light didn't work. * Fixes some error messages in the tests. There should be far less (although one will inevitably pop up when the test first starts, for some reason. * Converts spatialToLight to use TempVars. It wastes less objects. * Fixed some codacity issues. * More formatting fixes * Stops updating the rotation or translation when the parent light doesn't have those options. It is a little less error-prone. * Normalizes the rotation vector and adds some more readible names. * Fixed some formatting/comment issues.
1 parent 2c91e14 commit 344c491

File tree

5 files changed

+744
-26
lines changed

5 files changed

+744
-26
lines changed

jme3-core/src/main/java/com/jme3/scene/control/LightControl.java

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2012 jMonkeyEngine
2+
* Copyright (c) 2009-2021 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -39,6 +39,7 @@
3939
import com.jme3.light.Light;
4040
import com.jme3.light.PointLight;
4141
import com.jme3.light.SpotLight;
42+
import com.jme3.math.Quaternion;
4243
import com.jme3.math.Vector3f;
4344
import com.jme3.renderer.RenderManager;
4445
import com.jme3.renderer.ViewPort;
@@ -51,6 +52,7 @@
5152
* This Control maintains a reference to a Camera,
5253
* which will be synched with the position (worldTranslation)
5354
* of the current spatial.
55+
*
5456
* @author tim
5557
*/
5658
public class LightControl extends AbstractControl {
@@ -69,7 +71,7 @@ public enum ControlDirection {
6971
* Means, that the Spatial's transform is "copied"
7072
* to the Transform of the light.
7173
*/
72-
SpatialToLight;
74+
SpatialToLight
7375
}
7476

7577
private Light light;
@@ -127,49 +129,73 @@ protected void controlUpdate(float tpf) {
127129
}
128130
}
129131

132+
/**
133+
* Sets the light to adopt the spatial's world transformations.
134+
*
135+
* @author Markil 3
136+
* @author pspeed42
137+
*/
130138
private void spatialToLight(Light light) {
139+
TempVars vars = TempVars.get();
131140

132-
final Vector3f worldTranslation = spatial.getWorldTranslation();
141+
final Vector3f worldTranslation = vars.vect1;
142+
worldTranslation.set(spatial.getWorldTranslation());
143+
final Vector3f worldDirection = vars.vect2;
144+
spatial.getWorldRotation().mult(Vector3f.UNIT_Z, worldDirection).negateLocal();
133145

134146
if (light instanceof PointLight) {
135147
((PointLight) light).setPosition(worldTranslation);
136-
return;
137-
}
138-
139-
final TempVars vars = TempVars.get();
140-
final Vector3f vec = vars.vect1;
141-
142-
if (light instanceof DirectionalLight) {
143-
((DirectionalLight) light).setDirection(vec.set(worldTranslation).multLocal(-1.0f));
144-
}
145-
146-
if (light instanceof SpotLight) {
148+
} else if (light instanceof DirectionalLight) {
149+
((DirectionalLight) light).setDirection(worldDirection);
150+
} else if (light instanceof SpotLight) {
147151
final SpotLight spotLight = (SpotLight) light;
148152
spotLight.setPosition(worldTranslation);
149-
spotLight.setDirection(spatial.getWorldRotation().multLocal(vec.set(Vector3f.UNIT_Y).multLocal(-1)));
153+
spotLight.setDirection(worldDirection);
150154
}
151-
152155
vars.release();
153156
}
154157

158+
/**
159+
* Sets the spatial to adopt the light's world transformations.
160+
*
161+
* @author Markil 3
162+
*/
155163
private void lightToSpatial(Light light) {
156164
TempVars vars = TempVars.get();
157-
if (light instanceof PointLight) {
165+
Vector3f translation = vars.vect1;
166+
Vector3f direction = vars.vect2;
167+
Quaternion rotation = vars.quat1;
168+
boolean rotateSpatial = false, translateSpatial = false;
158169

170+
if (light instanceof PointLight) {
159171
PointLight pLight = (PointLight) light;
160-
161-
Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation());
162-
spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
172+
translation.set(pLight.getPosition());
173+
translateSpatial = true;
174+
} else if (light instanceof DirectionalLight) {
175+
DirectionalLight dLight = (DirectionalLight) light;
176+
direction.set(dLight.getDirection()).negateLocal();
177+
rotateSpatial = true;
178+
} else if (light instanceof SpotLight) {
179+
SpotLight sLight = (SpotLight) light;
180+
translation.set(sLight.getPosition());
181+
direction.set(sLight.getDirection()).negateLocal();
182+
translateSpatial = rotateSpatial = true;
183+
}
184+
if (spatial.getParent() != null) {
185+
spatial.getParent().getLocalToWorldMatrix(vars.tempMat4).invertLocal();
186+
vars.tempMat4.rotateVect(translation);
187+
vars.tempMat4.translateVect(translation);
188+
vars.tempMat4.rotateVect(direction);
163189
}
164190

165-
if (light instanceof DirectionalLight) {
166-
DirectionalLight dLight = (DirectionalLight) light;
167-
vars.vect1.set(dLight.getDirection()).multLocal(-1.0f);
168-
Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation());
169-
spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
191+
if (rotateSpatial) {
192+
rotation.lookAt(direction, Vector3f.UNIT_Y).normalizeLocal();
193+
spatial.setLocalRotation(rotation);
194+
}
195+
if (translateSpatial) {
196+
spatial.setLocalTranslation(translation);
170197
}
171198
vars.release();
172-
//TODO add code for Spot light here when it's done
173199
}
174200

175201
@Override
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright (c) 2009-2012 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package jme3test.light;
33+
34+
import com.jme3.app.SimpleApplication;
35+
import com.jme3.light.AmbientLight;
36+
import com.jme3.light.DirectionalLight;
37+
import com.jme3.material.Material;
38+
import com.jme3.math.ColorRGBA;
39+
import com.jme3.math.FastMath;
40+
import com.jme3.math.Quaternion;
41+
import com.jme3.math.Vector3f;
42+
import com.jme3.scene.Geometry;
43+
import com.jme3.scene.Node;
44+
import com.jme3.scene.control.LightControl;
45+
import com.jme3.scene.shape.Cylinder;
46+
import com.jme3.scene.shape.Dome;
47+
import com.jme3.scene.shape.Sphere;
48+
import com.jme3.util.TempVars;
49+
50+
/**
51+
* Similar to {@link TestLightControlDirectional}, except that the spatial is controlled by the light this
52+
* time.
53+
*
54+
* @author Markil 3
55+
*/
56+
public class TestLightControl2Directional extends SimpleApplication {
57+
private final Vector3f rotAxis = new Vector3f(Vector3f.UNIT_X);
58+
private final float[] angles = new float[3];
59+
60+
private Node lightNode;
61+
private DirectionalLight direction;
62+
63+
public static void main(String[] args) {
64+
TestLightControl2Directional app = new TestLightControl2Directional();
65+
app.start();
66+
}
67+
68+
public void setupLighting() {
69+
Geometry lightMdl;
70+
AmbientLight al = new AmbientLight();
71+
al.setColor(ColorRGBA.White.mult(2f));
72+
rootNode.addLight(al);
73+
74+
direction = new DirectionalLight();
75+
direction.setColor(ColorRGBA.White.mult(10));
76+
rootNode.addLight(direction);
77+
78+
lightMdl = new Geometry("Light", new Dome(Vector3f.ZERO, 2, 32, 5, false));
79+
lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
80+
lightMdl.setLocalTranslation(new Vector3f(0, 0, 0));
81+
lightMdl.setLocalRotation(new Quaternion().fromAngles(FastMath.PI / 2F, 0, 0));
82+
rootNode.attachChild(lightMdl);
83+
84+
/*
85+
* We need this Dome doesn't have a "floor."
86+
*/
87+
Geometry lightFloor = new Geometry("LightFloor", new Cylinder(2, 32, 5, .1F, true));
88+
lightFloor.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
89+
lightFloor.getMaterial().setColor("Color", ColorRGBA.White);
90+
91+
lightNode = new Node();
92+
lightNode.addControl(new LightControl(direction, LightControl.ControlDirection.LightToSpatial));
93+
lightNode.attachChild(lightMdl);
94+
lightNode.attachChild(lightFloor);
95+
96+
rootNode.attachChild(lightNode);
97+
}
98+
99+
public void setupDome() {
100+
Geometry dome = new Geometry("Dome", new Sphere(16, 32, 30, false, true));
101+
dome.setMaterial(new Material(this.assetManager, "Common/MatDefs/Light/PBRLighting.j3md"));
102+
dome.setLocalTranslation(new Vector3f(0, 0, 0));
103+
rootNode.attachChild(dome);
104+
}
105+
106+
@Override
107+
public void simpleInitApp() {
108+
this.cam.setLocation(new Vector3f(-50, 20, 50));
109+
this.cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
110+
flyCam.setMoveSpeed(30);
111+
112+
setupLighting();
113+
setupDome();
114+
}
115+
116+
@Override
117+
public void simpleUpdate(float tpf) {
118+
final Vector3f INIT_DIR = Vector3f.UNIT_Z.negate();
119+
/*
120+
* In Radians per second
121+
*/
122+
final float ROT_SPEED = FastMath.PI / 2;
123+
/*
124+
* 360 degree rotation
125+
*/
126+
final float FULL_ROT = FastMath.PI * 2;
127+
128+
TempVars vars = TempVars.get();
129+
Vector3f lightDirection = vars.vect2, nodeDirection = vars.vect3;
130+
float length;
131+
132+
angles[0] += rotAxis.x * ROT_SPEED * tpf;
133+
angles[1] += rotAxis.y * ROT_SPEED * tpf;
134+
angles[2] += rotAxis.z * ROT_SPEED * tpf;
135+
direction.setDirection(new Quaternion().fromAngles(angles).mult(INIT_DIR));
136+
super.simpleUpdate(tpf);
137+
138+
/*
139+
* Make sure they are equal.
140+
*/
141+
lightDirection.set(direction.getDirection());
142+
lightDirection.normalizeLocal();
143+
lightNode.getWorldRotation().mult(Vector3f.UNIT_Z, nodeDirection);
144+
nodeDirection.negateLocal().normalizeLocal();
145+
length = lightDirection.subtract(nodeDirection, vars.vect4).lengthSquared();
146+
length = FastMath.abs(length);
147+
if (length > .1F) {
148+
System.err.printf("Rotation not equal: is %s, needs to be %s (%f)\n", nodeDirection, lightDirection, length);
149+
}
150+
151+
if (angles[0] >= FULL_ROT || angles[1] >= FULL_ROT || angles[2] >= FULL_ROT) {
152+
direction.setDirection(INIT_DIR);
153+
angles[0] = 0;
154+
angles[1] = 0;
155+
angles[2] = 0;
156+
if (rotAxis.x > 0 && rotAxis.y == 0 && rotAxis.z == 0) {
157+
rotAxis.set(0, 1, 0);
158+
} else if (rotAxis.y > 0 && rotAxis.x == 0 && rotAxis.z == 0) {
159+
rotAxis.set(0, 0, 1);
160+
} else if (rotAxis.z > 0 && rotAxis.x == 0 && rotAxis.y == 0) {
161+
rotAxis.set(FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1);
162+
} else {
163+
rotAxis.set(1, 0, 0);
164+
}
165+
}
166+
167+
vars.release();
168+
}
169+
}

0 commit comments

Comments
 (0)