|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * Copyright (c) 2009-2023 jMonkeyEngine
|
|
|
|
|
|
+ * Copyright (c) 2009-2025 jMonkeyEngine
|
|
* All rights reserved.
|
|
* All rights reserved.
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* Redistribution and use in source and binary forms, with or without
|
|
@@ -49,52 +49,77 @@ import com.jme3.util.clone.Cloner;
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * This Control maintains a reference to a Light,
|
|
|
|
- * which will be synched with the position (worldTranslation)
|
|
|
|
- * of the current spatial.
|
|
|
|
|
|
+ * `LightControl` synchronizes the world transformation (position and/or
|
|
|
|
+ * direction) of a `Light` with its attached `Spatial`. This control allows
|
|
|
|
+ * a light to follow a spatial or vice-versa, depending on the chosen
|
|
|
|
+ * {@link ControlDirection}.
|
|
|
|
+ * <p>
|
|
|
|
+ * This is particularly useful for attaching lights to animated characters,
|
|
|
|
+ * moving vehicles, or dynamically controlled objects.
|
|
|
|
+ * </p>
|
|
*
|
|
*
|
|
- * @author tim
|
|
|
|
|
|
+ * @author Tim
|
|
|
|
+ * @author Markil 3
|
|
|
|
+ * @author capdevon
|
|
*/
|
|
*/
|
|
public class LightControl extends AbstractControl {
|
|
public class LightControl extends AbstractControl {
|
|
|
|
|
|
- private static final String CONTROL_DIR_NAME = "controlDir";
|
|
|
|
- private static final String LIGHT_NAME = "light";
|
|
|
|
-
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Defines the direction of synchronization between the light and the spatial.
|
|
|
|
+ */
|
|
public enum ControlDirection {
|
|
public enum ControlDirection {
|
|
-
|
|
|
|
/**
|
|
/**
|
|
- * Means, that the Light's transform is "copied"
|
|
|
|
- * to the Transform of the Spatial.
|
|
|
|
|
|
+ * The light's transform is copied to the spatial's transform.
|
|
*/
|
|
*/
|
|
LightToSpatial,
|
|
LightToSpatial,
|
|
/**
|
|
/**
|
|
- * Means, that the Spatial's transform is "copied"
|
|
|
|
- * to the Transform of the light.
|
|
|
|
|
|
+ * The spatial's transform is copied to the light's transform.
|
|
*/
|
|
*/
|
|
SpatialToLight
|
|
SpatialToLight
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Represents the local axis of the spatial (X, Y, or Z) to be used
|
|
|
|
+ * for determining the light's direction when `ControlDirection` is
|
|
|
|
+ * `SpatialToLight`.
|
|
|
|
+ */
|
|
|
|
+ public enum Axis {
|
|
|
|
+ X, Y, Z
|
|
|
|
+ }
|
|
|
|
+
|
|
private Light light;
|
|
private Light light;
|
|
private ControlDirection controlDir = ControlDirection.SpatialToLight;
|
|
private ControlDirection controlDir = ControlDirection.SpatialToLight;
|
|
|
|
+ private Axis axisRotation = Axis.Z;
|
|
|
|
+ private boolean invertAxisDirection = false;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Constructor used for Serialization.
|
|
|
|
|
|
+ * For serialization only. Do not use.
|
|
*/
|
|
*/
|
|
public LightControl() {
|
|
public LightControl() {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * Creates a new `LightControl` that synchronizes the light's transform to the spatial.
|
|
|
|
+ *
|
|
* @param light The light to be synced.
|
|
* @param light The light to be synced.
|
|
|
|
+ * @throws IllegalArgumentException if the light type is not supported
|
|
|
|
+ * (only Point, Directional, and Spot lights are supported).
|
|
*/
|
|
*/
|
|
public LightControl(Light light) {
|
|
public LightControl(Light light) {
|
|
|
|
+ validateSupportedLightType(light);
|
|
this.light = light;
|
|
this.light = light;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * Creates a new `LightControl` with a specified synchronization direction.
|
|
|
|
+ *
|
|
* @param light The light to be synced.
|
|
* @param light The light to be synced.
|
|
- * @param controlDir SpatialToLight or LightToSpatial
|
|
|
|
|
|
+ * @param controlDir The direction of synchronization (SpatialToLight or LightToSpatial).
|
|
|
|
+ * @throws IllegalArgumentException if the light type is not supported
|
|
|
|
+ * (only Point, Directional, and Spot lights are supported).
|
|
*/
|
|
*/
|
|
public LightControl(Light light, ControlDirection controlDir) {
|
|
public LightControl(Light light, ControlDirection controlDir) {
|
|
|
|
+ validateSupportedLightType(light);
|
|
this.light = light;
|
|
this.light = light;
|
|
this.controlDir = controlDir;
|
|
this.controlDir = controlDir;
|
|
}
|
|
}
|
|
@@ -104,6 +129,7 @@ public class LightControl extends AbstractControl {
|
|
}
|
|
}
|
|
|
|
|
|
public void setLight(Light light) {
|
|
public void setLight(Light light) {
|
|
|
|
+ validateSupportedLightType(light);
|
|
this.light = light;
|
|
this.light = light;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -115,86 +141,141 @@ public class LightControl extends AbstractControl {
|
|
this.controlDir = controlDir;
|
|
this.controlDir = controlDir;
|
|
}
|
|
}
|
|
|
|
|
|
- // fields used when inverting ControlDirection:
|
|
|
|
|
|
+ public Axis getAxisRotation() {
|
|
|
|
+ return axisRotation;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void setAxisRotation(Axis axisRotation) {
|
|
|
|
+ this.axisRotation = axisRotation;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public boolean isInvertAxisDirection() {
|
|
|
|
+ return invertAxisDirection;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void setInvertAxisDirection(boolean invertAxisDirection) {
|
|
|
|
+ this.invertAxisDirection = invertAxisDirection;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void validateSupportedLightType(Light light) {
|
|
|
|
+ if (light == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (light.getType()) {
|
|
|
|
+ case Point:
|
|
|
|
+ case Directional:
|
|
|
|
+ case Spot:
|
|
|
|
+ // These types are supported, validation passes.
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ throw new IllegalArgumentException(
|
|
|
|
+ "Unsupported Light type: " + light.getType());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void controlUpdate(float tpf) {
|
|
protected void controlUpdate(float tpf) {
|
|
- if (spatial != null && light != null) {
|
|
|
|
- switch (controlDir) {
|
|
|
|
- case SpatialToLight:
|
|
|
|
- spatialToLight(light);
|
|
|
|
- break;
|
|
|
|
- case LightToSpatial:
|
|
|
|
- lightToSpatial(light);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ if (light == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (controlDir) {
|
|
|
|
+ case SpatialToLight:
|
|
|
|
+ spatialToLight(light);
|
|
|
|
+ break;
|
|
|
|
+ case LightToSpatial:
|
|
|
|
+ lightToSpatial(light);
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Sets the light to adopt the spatial's world transformations.
|
|
|
|
|
|
+ * Updates the light's position and/or direction to match the spatial's
|
|
|
|
+ * world transformation.
|
|
*
|
|
*
|
|
- * @author Markil 3
|
|
|
|
- * @author pspeed42
|
|
|
|
|
|
+ * @param light The light whose properties will be set.
|
|
*/
|
|
*/
|
|
private void spatialToLight(Light light) {
|
|
private void spatialToLight(Light light) {
|
|
TempVars vars = TempVars.get();
|
|
TempVars vars = TempVars.get();
|
|
|
|
|
|
- final Vector3f worldTranslation = vars.vect1;
|
|
|
|
- worldTranslation.set(spatial.getWorldTranslation());
|
|
|
|
- final Vector3f worldDirection = vars.vect2;
|
|
|
|
- spatial.getWorldRotation().mult(Vector3f.UNIT_Z, worldDirection).negateLocal();
|
|
|
|
|
|
+ final Vector3f worldPosition = vars.vect1;
|
|
|
|
+ worldPosition.set(spatial.getWorldTranslation());
|
|
|
|
+
|
|
|
|
+ final Vector3f lightDirection = vars.vect2;
|
|
|
|
+ spatial.getWorldRotation().getRotationColumn(axisRotation.ordinal(), lightDirection);
|
|
|
|
+ if (invertAxisDirection) {
|
|
|
|
+ lightDirection.negateLocal();
|
|
|
|
+ }
|
|
|
|
|
|
if (light instanceof PointLight) {
|
|
if (light instanceof PointLight) {
|
|
- ((PointLight) light).setPosition(worldTranslation);
|
|
|
|
|
|
+ ((PointLight) light).setPosition(worldPosition);
|
|
|
|
+
|
|
} else if (light instanceof DirectionalLight) {
|
|
} else if (light instanceof DirectionalLight) {
|
|
- ((DirectionalLight) light).setDirection(worldDirection);
|
|
|
|
|
|
+ ((DirectionalLight) light).setDirection(lightDirection);
|
|
|
|
+
|
|
} else if (light instanceof SpotLight) {
|
|
} else if (light instanceof SpotLight) {
|
|
- final SpotLight spotLight = (SpotLight) light;
|
|
|
|
- spotLight.setPosition(worldTranslation);
|
|
|
|
- spotLight.setDirection(worldDirection);
|
|
|
|
|
|
+ SpotLight sl = (SpotLight) light;
|
|
|
|
+ sl.setPosition(worldPosition);
|
|
|
|
+ sl.setDirection(lightDirection);
|
|
}
|
|
}
|
|
vars.release();
|
|
vars.release();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Sets the spatial to adopt the light's world transformations.
|
|
|
|
|
|
+ * Updates the spatial's local transformation (position and/or rotation)
|
|
|
|
+ * to match the light's world transformation.
|
|
*
|
|
*
|
|
- * @author Markil 3
|
|
|
|
|
|
+ * @param light The light from which properties will be read.
|
|
*/
|
|
*/
|
|
private void lightToSpatial(Light light) {
|
|
private void lightToSpatial(Light light) {
|
|
TempVars vars = TempVars.get();
|
|
TempVars vars = TempVars.get();
|
|
- Vector3f translation = vars.vect1;
|
|
|
|
- Vector3f direction = vars.vect2;
|
|
|
|
|
|
+ Vector3f lightPosition = vars.vect1;
|
|
|
|
+ Vector3f lightDirection = vars.vect2;
|
|
Quaternion rotation = vars.quat1;
|
|
Quaternion rotation = vars.quat1;
|
|
- boolean rotateSpatial = false, translateSpatial = false;
|
|
|
|
|
|
+ boolean rotateSpatial = false;
|
|
|
|
+ boolean translateSpatial = false;
|
|
|
|
|
|
if (light instanceof PointLight) {
|
|
if (light instanceof PointLight) {
|
|
- PointLight pLight = (PointLight) light;
|
|
|
|
- translation.set(pLight.getPosition());
|
|
|
|
|
|
+ PointLight pl = (PointLight) light;
|
|
|
|
+ lightPosition.set(pl.getPosition());
|
|
translateSpatial = true;
|
|
translateSpatial = true;
|
|
|
|
+
|
|
} else if (light instanceof DirectionalLight) {
|
|
} else if (light instanceof DirectionalLight) {
|
|
- DirectionalLight dLight = (DirectionalLight) light;
|
|
|
|
- direction.set(dLight.getDirection()).negateLocal();
|
|
|
|
|
|
+ DirectionalLight dl = (DirectionalLight) light;
|
|
|
|
+ lightDirection.set(dl.getDirection());
|
|
|
|
+ if (invertAxisDirection) {
|
|
|
|
+ lightDirection.negateLocal();
|
|
|
|
+ }
|
|
rotateSpatial = true;
|
|
rotateSpatial = true;
|
|
|
|
+
|
|
} else if (light instanceof SpotLight) {
|
|
} else if (light instanceof SpotLight) {
|
|
- SpotLight sLight = (SpotLight) light;
|
|
|
|
- translation.set(sLight.getPosition());
|
|
|
|
- direction.set(sLight.getDirection()).negateLocal();
|
|
|
|
- translateSpatial = rotateSpatial = true;
|
|
|
|
|
|
+ SpotLight sl = (SpotLight) light;
|
|
|
|
+ lightPosition.set(sl.getPosition());
|
|
|
|
+ lightDirection.set(sl.getDirection());
|
|
|
|
+ if (invertAxisDirection) {
|
|
|
|
+ lightDirection.negateLocal();
|
|
|
|
+ }
|
|
|
|
+ translateSpatial = true;
|
|
|
|
+ rotateSpatial = true;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Transform light's world properties to spatial's parent's local space
|
|
if (spatial.getParent() != null) {
|
|
if (spatial.getParent() != null) {
|
|
|
|
+ // Get inverse of parent's world matrix
|
|
spatial.getParent().getLocalToWorldMatrix(vars.tempMat4).invertLocal();
|
|
spatial.getParent().getLocalToWorldMatrix(vars.tempMat4).invertLocal();
|
|
- vars.tempMat4.rotateVect(translation);
|
|
|
|
- vars.tempMat4.translateVect(translation);
|
|
|
|
- vars.tempMat4.rotateVect(direction);
|
|
|
|
|
|
+ vars.tempMat4.rotateVect(lightPosition);
|
|
|
|
+ vars.tempMat4.translateVect(lightPosition);
|
|
|
|
+ vars.tempMat4.rotateVect(lightDirection);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // Apply transformed properties to spatial's local transformation
|
|
if (rotateSpatial) {
|
|
if (rotateSpatial) {
|
|
- rotation.lookAt(direction, Vector3f.UNIT_Y).normalizeLocal();
|
|
|
|
|
|
+ rotation.lookAt(lightDirection, Vector3f.UNIT_Y).normalizeLocal();
|
|
spatial.setLocalRotation(rotation);
|
|
spatial.setLocalRotation(rotation);
|
|
}
|
|
}
|
|
if (translateSpatial) {
|
|
if (translateSpatial) {
|
|
- spatial.setLocalTranslation(translation);
|
|
|
|
|
|
+ spatial.setLocalTranslation(lightPosition);
|
|
}
|
|
}
|
|
vars.release();
|
|
vars.release();
|
|
}
|
|
}
|
|
@@ -214,15 +295,31 @@ public class LightControl extends AbstractControl {
|
|
public void read(JmeImporter im) throws IOException {
|
|
public void read(JmeImporter im) throws IOException {
|
|
super.read(im);
|
|
super.read(im);
|
|
InputCapsule ic = im.getCapsule(this);
|
|
InputCapsule ic = im.getCapsule(this);
|
|
- controlDir = ic.readEnum(CONTROL_DIR_NAME, ControlDirection.class, ControlDirection.SpatialToLight);
|
|
|
|
- light = (Light) ic.readSavable(LIGHT_NAME, null);
|
|
|
|
|
|
+ light = (Light) ic.readSavable("light", null);
|
|
|
|
+ controlDir = ic.readEnum("controlDir", ControlDirection.class, ControlDirection.SpatialToLight);
|
|
|
|
+ axisRotation = ic.readEnum("axisRotation", Axis.class, Axis.Z);
|
|
|
|
+ invertAxisDirection = ic.readBoolean("invertAxisDirection", false);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public void write(JmeExporter ex) throws IOException {
|
|
public void write(JmeExporter ex) throws IOException {
|
|
super.write(ex);
|
|
super.write(ex);
|
|
OutputCapsule oc = ex.getCapsule(this);
|
|
OutputCapsule oc = ex.getCapsule(this);
|
|
- oc.write(controlDir, CONTROL_DIR_NAME, ControlDirection.SpatialToLight);
|
|
|
|
- oc.write(light, LIGHT_NAME, null);
|
|
|
|
|
|
+ oc.write(light, "light", null);
|
|
|
|
+ oc.write(controlDir, "controlDir", ControlDirection.SpatialToLight);
|
|
|
|
+ oc.write(axisRotation, "axisRotation", Axis.Z);
|
|
|
|
+ oc.write(invertAxisDirection, "invertAxisDirection", false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public String toString() {
|
|
|
|
+ return getClass().getSimpleName() +
|
|
|
|
+ "[light=" + (light != null ? light.getType() : null) +
|
|
|
|
+ ", controlDir=" + controlDir +
|
|
|
|
+ ", axisRotation=" + axisRotation +
|
|
|
|
+ ", invertAxisDirection=" + invertAxisDirection +
|
|
|
|
+ ", enabled=" + enabled +
|
|
|
|
+ ", spatial=" + spatial +
|
|
|
|
+ "]";
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+}
|