Skip to content

Commit 15e978d

Browse files
authored
Merge pull request jMonkeyEngine#2451 from capdevon/capdevon-LightControl
Improve LightControl Javadoc & Add Light Type Validation
2 parents 6b143d7 + 81fbfd9 commit 15e978d

1 file changed

Lines changed: 157 additions & 60 deletions

File tree

Lines changed: 157 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2023 jMonkeyEngine
2+
* Copyright (c) 2009-2025 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -49,52 +49,77 @@
4949
import java.io.IOException;
5050

5151
/**
52-
* This Control maintains a reference to a Light,
53-
* which will be synched with the position (worldTranslation)
54-
* of the current spatial.
52+
* `LightControl` synchronizes the world transformation (position and/or
53+
* direction) of a `Light` with its attached `Spatial`. This control allows
54+
* a light to follow a spatial or vice-versa, depending on the chosen
55+
* {@link ControlDirection}.
56+
* <p>
57+
* This is particularly useful for attaching lights to animated characters,
58+
* moving vehicles, or dynamically controlled objects.
59+
* </p>
5560
*
56-
* @author tim
61+
* @author Tim
62+
* @author Markil 3
63+
* @author capdevon
5764
*/
5865
public class LightControl extends AbstractControl {
5966

60-
private static final String CONTROL_DIR_NAME = "controlDir";
61-
private static final String LIGHT_NAME = "light";
62-
67+
/**
68+
* Defines the direction of synchronization between the light and the spatial.
69+
*/
6370
public enum ControlDirection {
64-
6571
/**
66-
* Means, that the Light's transform is "copied"
67-
* to the Transform of the Spatial.
72+
* The light's transform is copied to the spatial's transform.
6873
*/
6974
LightToSpatial,
7075
/**
71-
* Means, that the Spatial's transform is "copied"
72-
* to the Transform of the light.
76+
* The spatial's transform is copied to the light's transform.
7377
*/
7478
SpatialToLight
7579
}
7680

81+
/**
82+
* Represents the local axis of the spatial (X, Y, or Z) to be used
83+
* for determining the light's direction when `ControlDirection` is
84+
* `SpatialToLight`.
85+
*/
86+
public enum Axis {
87+
X, Y, Z
88+
}
89+
7790
private Light light;
7891
private ControlDirection controlDir = ControlDirection.SpatialToLight;
92+
private Axis axisRotation = Axis.Z;
93+
private boolean invertAxisDirection = false;
7994

8095
/**
81-
* Constructor used for Serialization.
96+
* For serialization only. Do not use.
8297
*/
8398
public LightControl() {
8499
}
85100

86101
/**
102+
* Creates a new `LightControl` that synchronizes the light's transform to the spatial.
103+
*
87104
* @param light The light to be synced.
105+
* @throws IllegalArgumentException if the light type is not supported
106+
* (only Point, Directional, and Spot lights are supported).
88107
*/
89108
public LightControl(Light light) {
109+
validateSupportedLightType(light);
90110
this.light = light;
91111
}
92112

93113
/**
114+
* Creates a new `LightControl` with a specified synchronization direction.
115+
*
94116
* @param light The light to be synced.
95-
* @param controlDir SpatialToLight or LightToSpatial
117+
* @param controlDir The direction of synchronization (SpatialToLight or LightToSpatial).
118+
* @throws IllegalArgumentException if the light type is not supported
119+
* (only Point, Directional, and Spot lights are supported).
96120
*/
97121
public LightControl(Light light, ControlDirection controlDir) {
122+
validateSupportedLightType(light);
98123
this.light = light;
99124
this.controlDir = controlDir;
100125
}
@@ -104,6 +129,7 @@ public Light getLight() {
104129
}
105130

106131
public void setLight(Light light) {
132+
validateSupportedLightType(light);
107133
this.light = light;
108134
}
109135

@@ -115,86 +141,141 @@ public void setControlDir(ControlDirection controlDir) {
115141
this.controlDir = controlDir;
116142
}
117143

118-
// fields used when inverting ControlDirection:
144+
public Axis getAxisRotation() {
145+
return axisRotation;
146+
}
147+
148+
public void setAxisRotation(Axis axisRotation) {
149+
this.axisRotation = axisRotation;
150+
}
151+
152+
public boolean isInvertAxisDirection() {
153+
return invertAxisDirection;
154+
}
155+
156+
public void setInvertAxisDirection(boolean invertAxisDirection) {
157+
this.invertAxisDirection = invertAxisDirection;
158+
}
159+
160+
private void validateSupportedLightType(Light light) {
161+
if (light == null) {
162+
return;
163+
}
164+
165+
switch (light.getType()) {
166+
case Point:
167+
case Directional:
168+
case Spot:
169+
// These types are supported, validation passes.
170+
break;
171+
default:
172+
throw new IllegalArgumentException(
173+
"Unsupported Light type: " + light.getType());
174+
}
175+
}
176+
119177
@Override
120178
protected void controlUpdate(float tpf) {
121-
if (spatial != null && light != null) {
122-
switch (controlDir) {
123-
case SpatialToLight:
124-
spatialToLight(light);
125-
break;
126-
case LightToSpatial:
127-
lightToSpatial(light);
128-
break;
129-
}
179+
if (light == null) {
180+
return;
181+
}
182+
183+
switch (controlDir) {
184+
case SpatialToLight:
185+
spatialToLight(light);
186+
break;
187+
case LightToSpatial:
188+
lightToSpatial(light);
189+
break;
130190
}
131191
}
132192

133193
/**
134-
* Sets the light to adopt the spatial's world transformations.
194+
* Updates the light's position and/or direction to match the spatial's
195+
* world transformation.
135196
*
136-
* @author Markil 3
137-
* @author pspeed42
197+
* @param light The light whose properties will be set.
138198
*/
139199
private void spatialToLight(Light light) {
140200
TempVars vars = TempVars.get();
141201

142-
final Vector3f worldTranslation = vars.vect1;
143-
worldTranslation.set(spatial.getWorldTranslation());
144-
final Vector3f worldDirection = vars.vect2;
145-
spatial.getWorldRotation().mult(Vector3f.UNIT_Z, worldDirection).negateLocal();
202+
final Vector3f worldPosition = vars.vect1;
203+
worldPosition.set(spatial.getWorldTranslation());
204+
205+
final Vector3f lightDirection = vars.vect2;
206+
spatial.getWorldRotation().getRotationColumn(axisRotation.ordinal(), lightDirection);
207+
if (invertAxisDirection) {
208+
lightDirection.negateLocal();
209+
}
146210

147211
if (light instanceof PointLight) {
148-
((PointLight) light).setPosition(worldTranslation);
212+
((PointLight) light).setPosition(worldPosition);
213+
149214
} else if (light instanceof DirectionalLight) {
150-
((DirectionalLight) light).setDirection(worldDirection);
215+
((DirectionalLight) light).setDirection(lightDirection);
216+
151217
} else if (light instanceof SpotLight) {
152-
final SpotLight spotLight = (SpotLight) light;
153-
spotLight.setPosition(worldTranslation);
154-
spotLight.setDirection(worldDirection);
218+
SpotLight sl = (SpotLight) light;
219+
sl.setPosition(worldPosition);
220+
sl.setDirection(lightDirection);
155221
}
156222
vars.release();
157223
}
158224

159225
/**
160-
* Sets the spatial to adopt the light's world transformations.
226+
* Updates the spatial's local transformation (position and/or rotation)
227+
* to match the light's world transformation.
161228
*
162-
* @author Markil 3
229+
* @param light The light from which properties will be read.
163230
*/
164231
private void lightToSpatial(Light light) {
165232
TempVars vars = TempVars.get();
166-
Vector3f translation = vars.vect1;
167-
Vector3f direction = vars.vect2;
233+
Vector3f lightPosition = vars.vect1;
234+
Vector3f lightDirection = vars.vect2;
168235
Quaternion rotation = vars.quat1;
169-
boolean rotateSpatial = false, translateSpatial = false;
236+
boolean rotateSpatial = false;
237+
boolean translateSpatial = false;
170238

171239
if (light instanceof PointLight) {
172-
PointLight pLight = (PointLight) light;
173-
translation.set(pLight.getPosition());
240+
PointLight pl = (PointLight) light;
241+
lightPosition.set(pl.getPosition());
174242
translateSpatial = true;
243+
175244
} else if (light instanceof DirectionalLight) {
176-
DirectionalLight dLight = (DirectionalLight) light;
177-
direction.set(dLight.getDirection()).negateLocal();
245+
DirectionalLight dl = (DirectionalLight) light;
246+
lightDirection.set(dl.getDirection());
247+
if (invertAxisDirection) {
248+
lightDirection.negateLocal();
249+
}
178250
rotateSpatial = true;
251+
179252
} else if (light instanceof SpotLight) {
180-
SpotLight sLight = (SpotLight) light;
181-
translation.set(sLight.getPosition());
182-
direction.set(sLight.getDirection()).negateLocal();
183-
translateSpatial = rotateSpatial = true;
253+
SpotLight sl = (SpotLight) light;
254+
lightPosition.set(sl.getPosition());
255+
lightDirection.set(sl.getDirection());
256+
if (invertAxisDirection) {
257+
lightDirection.negateLocal();
258+
}
259+
translateSpatial = true;
260+
rotateSpatial = true;
184261
}
262+
263+
// Transform light's world properties to spatial's parent's local space
185264
if (spatial.getParent() != null) {
265+
// Get inverse of parent's world matrix
186266
spatial.getParent().getLocalToWorldMatrix(vars.tempMat4).invertLocal();
187-
vars.tempMat4.rotateVect(translation);
188-
vars.tempMat4.translateVect(translation);
189-
vars.tempMat4.rotateVect(direction);
267+
vars.tempMat4.rotateVect(lightPosition);
268+
vars.tempMat4.translateVect(lightPosition);
269+
vars.tempMat4.rotateVect(lightDirection);
190270
}
191271

272+
// Apply transformed properties to spatial's local transformation
192273
if (rotateSpatial) {
193-
rotation.lookAt(direction, Vector3f.UNIT_Y).normalizeLocal();
274+
rotation.lookAt(lightDirection, Vector3f.UNIT_Y).normalizeLocal();
194275
spatial.setLocalRotation(rotation);
195276
}
196277
if (translateSpatial) {
197-
spatial.setLocalTranslation(translation);
278+
spatial.setLocalTranslation(lightPosition);
198279
}
199280
vars.release();
200281
}
@@ -214,15 +295,31 @@ public void cloneFields(final Cloner cloner, final Object original) {
214295
public void read(JmeImporter im) throws IOException {
215296
super.read(im);
216297
InputCapsule ic = im.getCapsule(this);
217-
controlDir = ic.readEnum(CONTROL_DIR_NAME, ControlDirection.class, ControlDirection.SpatialToLight);
218-
light = (Light) ic.readSavable(LIGHT_NAME, null);
298+
light = (Light) ic.readSavable("light", null);
299+
controlDir = ic.readEnum("controlDir", ControlDirection.class, ControlDirection.SpatialToLight);
300+
axisRotation = ic.readEnum("axisRotation", Axis.class, Axis.Z);
301+
invertAxisDirection = ic.readBoolean("invertAxisDirection", false);
219302
}
220303

221304
@Override
222305
public void write(JmeExporter ex) throws IOException {
223306
super.write(ex);
224307
OutputCapsule oc = ex.getCapsule(this);
225-
oc.write(controlDir, CONTROL_DIR_NAME, ControlDirection.SpatialToLight);
226-
oc.write(light, LIGHT_NAME, null);
308+
oc.write(light, "light", null);
309+
oc.write(controlDir, "controlDir", ControlDirection.SpatialToLight);
310+
oc.write(axisRotation, "axisRotation", Axis.Z);
311+
oc.write(invertAxisDirection, "invertAxisDirection", false);
312+
}
313+
314+
@Override
315+
public String toString() {
316+
return getClass().getSimpleName() +
317+
"[light=" + (light != null ? light.getType() : null) +
318+
", controlDir=" + controlDir +
319+
", axisRotation=" + axisRotation +
320+
", invertAxisDirection=" + invertAxisDirection +
321+
", enabled=" + enabled +
322+
", spatial=" + spatial +
323+
"]";
227324
}
228-
}
325+
}

0 commit comments

Comments
 (0)