Skip to content

Instantly share code, notes, and snippets.

@Birdasaur
Created June 1, 2022 02:40
Show Gist options
  • Save Birdasaur/bf8ad77d052f42aaf943933fdaf1496e to your computer and use it in GitHub Desktop.
Save Birdasaur/bf8ad77d052f42aaf943933fdaf1496e to your computer and use it in GitHub Desktop.
Quick JavaFX 18 test of adding multiple 3D SpotLight objects to the scene.
package edu.jhuapl.trinity.javafx;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SpotLight;
import javafx.scene.SubScene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.stage.Stage;
import org.fxyz3d.geometry.MathUtils;
import org.fxyz3d.utils.CameraTransformer;
public class SpotLightTest extends Application {
protected Box box;
PerspectiveCamera camera = new PerspectiveCamera(true);
public Group sceneRoot = new Group();
public SubScene subScene;
public CameraTransformer cameraTransform = new CameraTransformer();
private double cameraDistance = -4000;
private final double sceneWidth = 10000;
private final double sceneHeight = 4000;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
@Override
public void start(Stage primaryStage) throws Exception {
subScene = new SubScene(sceneRoot, sceneWidth, sceneHeight, true, SceneAntialiasing.BALANCED);
//Start Tracking mouse movements only when a button is pressed
subScene.setOnMouseDragged((MouseEvent me) -> mouseDragCamera(me));
subScene.setOnScroll((ScrollEvent event) -> {
double modifier = 50.0;
double modifierFactor = 0.1;
if (event.isControlDown()) {
modifier = 1;
}
if (event.isShiftDown()) {
modifier = 100.0;
}
double z = camera.getTranslateZ();
double newZ = z + event.getDeltaY() * modifierFactor * modifier;
camera.setTranslateZ(newZ);
});
StackPane stackPane = new StackPane(subScene);
subScene.widthProperty().bind(stackPane.widthProperty());
subScene.heightProperty().bind(stackPane.heightProperty());
subScene.setFill(Color.LIGHTGREY);
camera = new PerspectiveCamera(true);
//setup camera transform for rotational support
cameraTransform.setTranslate(0, 0, 0);
cameraTransform.getChildren().add(camera);
camera.setNearClip(0.1);
camera.setFarClip(100000.0);
camera.setTranslateZ(cameraDistance);
cameraTransform.ry.setAngle(-45.0);
cameraTransform.rx.setAngle(-10.0);
subScene.setCamera(camera);
//create multiple spot lights
List<SpotLight> lights = new ArrayList<>();
lights.add(new SpotLight(Color.WHITE));
lights.add(new SpotLight(Color.GREEN));
lights.add(new SpotLight(Color.RED));
for(int i=0; i< lights.size(); i++) {
SpotLight spotLight = lights.get(i);
spotLight.setDirection(new Point3D(0, 1, 0));
spotLight.setInnerAngle(120);
spotLight.setOuterAngle(30);
spotLight.setFalloff(-0.4);
spotLight.setTranslateZ(2000 * i);
spotLight.setTranslateY(-200);
}
box = new Box(sceneWidth, 50, sceneWidth);
box.setDrawMode(DrawMode.FILL);
box.setCullFace(CullFace.BACK);
box.setMaterial(new PhongMaterial(Color.CYAN));
sceneRoot.getChildren().addAll(cameraTransform, box);
sceneRoot.getChildren().addAll(lights);
BorderPane bpOilSpill = new BorderPane(subScene);
stackPane.getChildren().clear();
stackPane.getChildren().addAll(bpOilSpill);
stackPane.setPadding(new Insets(10));
stackPane.setBackground(new Background(new BackgroundFill(Color.rgb(255, 255, 255), CornerRadii.EMPTY, Insets.EMPTY)));
Scene scene = new Scene(stackPane, 1000, 1000);
scene.setOnMouseEntered(event -> subScene.requestFocus());
primaryStage.setTitle("Multiple SpotLight Test");
primaryStage.setScene(scene);
primaryStage.show();
}
private void mouseDragCamera(MouseEvent me) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 10.0;
double modifierFactor = 0.1;
if (me.isControlDown()) {
modifier = 1;
}
if (me.isShiftDown()) {
modifier = 50.0;
}
if (me.isPrimaryButtonDown()) {
if(me.isAltDown()) { //roll
cameraTransform.rz.setAngle(((cameraTransform.rz.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
} else {
cameraTransform.ry.setAngle(((cameraTransform.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
cameraTransform.rx.setAngle(
MathUtils.clamp(-60,
(((cameraTransform.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180),
60)); // -
}
} else if (me.isMiddleButtonDown() ) {
cameraTransform.t.setX(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
cameraTransform.t.setY(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
}
}
public static void main(String[] args){launch(args);}
}
@Birdasaur
Copy link
Author

image
@jperedadnr

Adding more than 2 SpotLight objects to a JavaFX 3D scene root as of 18 simply does not work. There should be 3 spot lights in the above image... white, green and red. It simply does not add anything past the 2nd spot light. This behavior is consistent with my other code bases.

@Birdasaur
Copy link
Author

This gist is a simple example demonstrating the above image. Am I missing something?

@jperedadnr
Copy link

jperedadnr commented Jun 1, 2022

I see a "black-hole", running with latest 19-ea

image

However, changing red to yellow works fine:

image

also, changing the material's diffuse color from Cyan to Lightgrey works with red:

image

So I guess there is an interaction between lights and material, but maybe @nlisker can "share some light" on this :)

@Birdasaur
Copy link
Author

  • So it half works with 19ea... but only with certain color combinations? Is that the assessment?
  • Did you try it with 18 and see the same thing I am seeing?
  • Have you ever been able to add more than two SpotLights to the scene? (this is my first attempt to use them)

I ask because the special effect I'm going for is to have multiple (half dozen or so) spotlights per scene.

@jperedadnr
Copy link

It works the same with 18.0.1
It seems that adding a fourth spotlight doesn't work.

@nlisker
Copy link

nlisker commented Jun 1, 2022

There is already a lighting test sample similar to yours, only more comprehensive, in the JavaFX codebase: https://github.com/openjdk/jfx/tree/master/tests/performance/3DLighting/attenuation. I suggest you give it a try.

JavaFX currently limits to 3 lights per mesh (not including AmbientLights, which are free, so 3 total from PointLights, SpotLights and DirectionalLights). There is no limitation per scene. The limitation is for both the D3D and OpenGL pipelines. I'm working on removing this limitation under JDK-8091256, which is blocked by openjdk/jfx#789, which is under review. Removing this limitation will incur a performance cost depending on the approach we take with varying shaders.
Adding a 4th light will be ignored, only the first 3 are taken into account. You can remove and re-add a light to change their order. If you have the default camera light then you can use only 2 spot lights in addition.

There shouldn't be a difference between 18 and 19, I don't remember that anything changed in that area of the code.

@nlisker
Copy link

nlisker commented Jun 1, 2022

By the way, here are 3 SpotLights together: openjdk/jfx#334 (comment)

@nlisker
Copy link

nlisker commented Jun 1, 2022

I took a quick look at the code

            spotLight.setInnerAngle(120);
            spotLight.setOuterAngle(30);
            spotLight.setFalloff(-0.4);

This is all "illegal". As per the docs, the inner angle needs to be smaller than the outer angle, and the falloff needs to be >=0. You can use illegal values like these to achieve all sorts of effects, like a ring light, shadow caster, and so on, but you need to know what you're doing.

@Birdasaur
Copy link
Author

Birdasaur commented Jun 2, 2022

Actually those values are exactly what I wanted to create a focused spot beam on a 3D object. If the outer angle is less than the inner angle that requires the falloff to be negative to get the focused circle effect I was looking for.

Doing it the way the docs says creates too much attenuation and not enough... "light density"? not sure what the correct term is here.
If there is a more proper way to get the effect that I want shown here then I'm open to suggestions:

image

@Birdasaur
Copy link
Author

like a ring light,
Now that would be interesting... creating a ring of light. I could totally use that as a special effect

@nlisker
Copy link

nlisker commented Jun 2, 2022

Not sure what you mean by "too much attenuation". You can control the attenuation directly with the attenuation parameters. If you want a more focused beam then make the inner and outer angle close and the falloff small.

@Birdasaur
Copy link
Author

As you can see I built a bunch of controls to play with the parameters... but I must not have tried that combination. I'll try it and report back!

@Birdasaur
Copy link
Author

image

Yes I see how the parameters work. I was also able to create a "shadow cast" effect which was super cool. I can smell a fun game mechanic in that somehow. Wasn't able to create a perfect ring of light... although I suppose you could just create a literal eclipse with another 3D object in front of the object yes?

@nlisker
Copy link

nlisker commented Jun 2, 2022

There are no shadows, so a 3D object will not block the light and cast a shadow.

If you want a ring of light set the outer angle to be larger than the inner angle (with a positive falloff):

image

You can also use 2 spot lights where one is smaller and has a negative attenuation so that it subtracts light from the second one:

image

@Birdasaur
Copy link
Author

This is pretty cool dude

@nlisker
Copy link

nlisker commented Jun 3, 2022

So have all issues been resolved?

@Birdasaur
Copy link
Author

well actually I never did manage to get the third light to show up but Jose was able to by changing the material colors. (must be due to underlying calculations that certain color combinations are diminished at the third light? You did say that order matters .

regardless please don't think I still have an issue. Your contribution to Javafx 3D is great!!
I would LOVE to have more lights but I understand that for now I will have 3 to work with.

@nlisker
Copy link

nlisker commented Jun 3, 2022

If you're using a CYAN (00FFFF) material color and a RED (FF0000) light you will not see the light because the contribution from these is 0. This is explained in the PhongMaterial docs.

@Birdasaur
Copy link
Author

Gotcha. So practically speaking its a sort of AND operation. This makes sense. Thanks!
Last question... is there some way/method for knowing how much total light contribution is being applied to a given node programmatically?

I can think of some cool game ideas if you could programmatically determine the amount of light contribution (even better if its known by RGB channel) on a node.

@nlisker
Copy link

nlisker commented Jun 3, 2022

The amount of light is per pixel, not per node. There is no way to determine it per pixel programatically, otherwise no one would need shaders. At best you can take a snapshot and sample that specific pixel for its RGB. Remember that it depends also on the location of the camera because of the way the light reflects. If you want some sort of approximation per node then use the formula in PhongMaterial.

@Birdasaur
Copy link
Author

If you want some sort of approximation per node then use the formula in PhongMaterial.

Oh good idea. An approximation would be fine. I suddenly have an idea for a game mechanic where you must use spot lights to shine light on "adversaries" or other targets. Each target would require different amounts of light/color to be "defeated".

By using the lighting I can play games with visibility... certain nodes would be difficult to see in bright lights so you may have to invert the light to be a shadow cast.

I can see a flat plane with 3D shapes moving around on the plane either towards you or towards a goal and you are using light/shadow to manipulate the playing field.

@Birdasaur
Copy link
Author

oooo different colors could put spin and velocity effects on the 3D shapes. Oh this could be really cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment