How do you bump-map large areas in a JavaFX Canvas - javafx

I want to apply some lighting effects using JavaFX onto a canvas GraphicsContext. At first I used the Lighting.bumpInput to pass a static bump-map for lighting. However this only lights a certain area of my canvas correct.
So now I do the lighting manually by first drawing all bump-maps onto the canvas then applying my lighting effect and finally blending all the original images with BlendMode.MULTIPLY together with the lit bump-maps.
This does not look as nice as the first approach but it does its job.
Now there is still the problem that the lighting effect (Pointlight) is applied across the whole canvas no matter how big the area is.
public void start(Stage primaryStage) {
Image texture = new Image("...");
Image bumpMap = new Image("...");
Light.Point pointLight = new Light.Point(256, 256, 25, Color.WHITE);
Lighting lighting = new Lighting(pointLight);
Canvas canvas = new Canvas(1280, 800);
canvas.getGraphicsContext().setGlobalBlendMode(BlendMode.MULTIPLY);
canvas.getGraphicsContext().drawImage(bumpMap, 0, 0);
canvas.getGraphicsContext().applyEffect(lighting);
canvas.getGraphicsContext().drawImage(texture, 0, 0);
Scene scene = new Scene(new AnchorPane(canvas), 1280, 800);
primaryStage.setScene(scene);
primaryStage.show();
}
Now my question is, what is the proper way in JavaFX to apply the lighting only to a specific area on my canvas? Like to define the radius of my point light?

Well, the intensity of the light is proportional to 1/r³, wheras r is the distance (radius) vom the light source to the object. Hence you can affect the lit area by varying the radius, in your case the z-coordinate (=25) of your light source :
Light.Point pointLight = new Light.Point(256, 256, 25, Color.WHITE);
However, by doing this you will actually still have a steady transition of illumination on your canvas, there is no sharp border of illuminated and non-illuminated areas. No matter, how big you choose the value of r, the intensity will never be exactly zero.
If u want to have illumination only applied to a certain area of your canvas, I would recommend some sort of masking-workaround, i.e. duplicate your image and make for example a hole with Radius = r on it without applying lighting, then place it as overlay-mask onto your original image with all your lighting filters.

Related

Can't see the texture of the Sphere from inside in JavaFX [duplicate]

Is it possible to create a photosphere in JavaFX that is similar to photoshpere in Google map? If yes, how?
The answer is yes, you can create a photosphere in JavaFX.
As for the how, there's an easy solution based on a sphere from the 3D API, but we can implement an improved solution, with a custom mesh.
Let's start by using a regular sphere. We just need a 360º image, like this one.
As we want to see from the inside of the sphere, we have to flip the image horizontally, and add it to the sphere material's diffusion map.
Then we just need to set up a camera in the very center of the sphere, add some lights and start spinning.
#Override
public void start(Stage primaryStage) {
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setFieldOfView(90);
Sphere sphere = new Sphere(1000);
sphere.setCullFace(CullFace.NONE);
PhongMaterial material = new PhongMaterial();
/*
"SonyCenter 360panorama" by François Reincke - Own work. Made using autostitch (www.autostitch.net)..
Licensed under CC BY-SA 3.0 via Wikimedia Commons - http://commons.wikimedia.org/wiki/File:SonyCenter_360panorama.jpg#mediaviewer/File:SonyCenter_360panorama.jpg
*/
material.setDiffuseMap(new Image(getClass().getResource("SonyCenter_360panorama_reversed.jpg").toExternalForm()));
sphere.setMaterial(material);
Group root3D = new Group(camera,sphere,new AmbientLight(Color.WHITE));
Scene scene = new Scene(root3D, 800, 600, true, SceneAntialiasing.BALANCED);
scene.setCamera(camera);
primaryStage.setTitle("PhotoSphere in JavaFX3D");
primaryStage.setScene(scene);
primaryStage.show();
final Timeline rotateTimeline = new Timeline();
rotateTimeline.setCycleCount(Timeline.INDEFINITE);
camera.setRotationAxis(Rotate.Y_AXIS);
final KeyValue kv1 = new KeyValue(camera.rotateProperty(), 360);
final KeyFrame kf1 = new KeyFrame(Duration.millis(30000), kv1);
rotateTimeline.getKeyFrames().addAll(kf1);
rotateTimeline.play();
}
Now you will want to add some controls to the camera (so you can navigate). You will discover that this solution has a weak spot at the top and bottom of the sphere, due to the fact that all the top or bottom side of the image is located in one point:
You can find a solution to this problem at the F(X)yz library here. A custom mesh called SegmentedSphereMesh allows you to crop the extremes of the sphere, so the image can keep its aspect ratio without being stretched.
If you clone and build the repo, add FXyz.jar to your project, and just replace Sphere in the previous snippet with this:
SegmentedSphereMesh sphere = new SegmentedSphereMesh(100,0,26,1000);
sphere.setCullFace(CullFace.NONE);
sphere.setTextureModeImage(getClass().getResource("SonyCenter_360panorama_reversed.jpg").toExternalForm());
In the same library you can find SkyBox, based on a cube and one square image on each of its faces. Also check the advanced camera options.
Finally, note that this and more 3D advanced shapes are now demonstrated in the F(X)yz-Sampler application.

JavaFX 3D Having two scenes with two cameras viewing the same objects

Is there any way to have two scenes that are having two different cameras, but looking at the same objects at the same time with out duplicating instances of every object?
I am developing a 3D game that has main scene with camera that is following the player and I would want to have another scene in the corner that is showing the bird's eye view of the same environment, like a mini map.
Any ideas how to develop this?
For starters, you can't have two scenes at the same stage. You could have two scenes and two stages, but obviously that would mean having duplicated objects, that need to be synchronized between scenes and stages.
However, there is possible way to get, in the same stage and same scene, a small mini-map (2D) node on top of the overall 3D node, each of them having their own camera.
This is based on this answer, and the existing CameraView class in the FXyz library.
As you can see, CameraView is basically an ImageView node that goes on the main scene to the right bottom corner (or anywhere else), while the 3D part goes to a SubScene in the center of the scene.
Both the subScene and the imageView can have mouse/keyboard event handling, and both have a camera, so in a way you have two different 3D views with their own control of the same 3D object.
To get a "live" ImageView that reflects the content of the subScene, looking like a real subScene, but without duplicating objects, the CameraView mainly uses:
Node::snapshot: taking snapshots of the subScene with will get an updated Image for the ImageView.
SnapshotParameters::setCamera (see javadoc. This not so well-known feature allows taking the snapshot with a given perspective based on a given camera.
AnimationTimer to do this process all over again on every frame/pulse.
The following is a simple use case of the CameraView, that can be added to your project including the org.fxyz3d:fxyz3d:0.5.2 dependency.
#Override
public void start(Stage stage) {
// 1. SubScene
// 3D node
SpringMesh spring = new SpringMesh(10, 2, 2, 8 * 2 * Math.PI, 200, 100, 0, 0);
spring.setCullFace(CullFace.NONE);
spring.setTextureModeVertices3D(1530, p -> p.f);
// root
Group worldRoot = new Group(spring);
// Camera
PerspectiveCamera camera = new PerspectiveCamera(true);
CameraTransformer cameraTransform = new CameraTransformer();
cameraTransform.setTranslate(0, 0, 0);
cameraTransform.getChildren().add(camera);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-100);
camera.setFieldOfView(20);
cameraTransform.ry.setAngle(-30.0);
cameraTransform.rx.setAngle(-15.0);
worldRoot.getChildren().add(cameraTransform);
// SubScene
SubScene subScene = new SubScene(worldRoot, 800,600, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.DARKSLATEGRAY);
subScene.setCamera(camera);
// mouse, key events on subScene:
// subScene.setOnKeyPressed(event -> {...});
// subScene.setOnMousePressed(event -> {...});
// subScene.setOnMouseDragged(event -> {...});
// 2. CameraView
CameraView cameraView = new CameraView(subScene);
cameraView.setFirstPersonNavigationEabled(true);
cameraView.setFitWidth(350);
cameraView.setFitHeight(225);
cameraView.getRx().setAngle(-45);
cameraView.getT().setZ(-100);
cameraView.getT().setY(-500);
cameraView.getCamera().setTranslateZ(-100);
// Right-bottom corner
StackPane.setAlignment(cameraView, Pos.BOTTOM_RIGHT);
StackPane.setMargin(cameraView, new Insets(5));
// 3. Scene
StackPane root = new StackPane(subScene, cameraView);
root.setStyle("-fx-background-color: DEEPSKYBLUE;");
subScene.widthProperty().bind(root.widthProperty());
subScene.heightProperty().bind(root.heightProperty());
Scene scene = new Scene(root, 810,610, true, SceneAntialiasing.BALANCED);
stage.setTitle("MiniMapTest");
stage.setScene(scene);
stage.show();
// start timer
cameraView.startViewing();
}
Running the application, you will get:
and you can move the camera on the mini-map view to get a different view of the 3D object:
Now it's up to you to play around with both 3D subScene and 2D cameraView to get the effect you want. By setting cameraView.setFirstPersonNavigationEabled(false) the mini-map won't allow user interaction, and you could control its camera (i.e, keeping a given zoom level of the subScene...).

JavaFX - Get Coordinates of Node Relative to its Parent

I am making a simple graphical interface for saving previously generated images. All images come to me square but I want to allow for some cropping functionality (more precisely cutting off equal parts from the bottom and top of the image). I want to do this by allowing the user to drag a shaded region over the image which will tell the user that this region will be cropped out. See the below image for details. To enable this drag functionality I have added small triangles that I want the user to drag which in turn will move the shaded regions about. However the coordinates for the triangles are all weird and seem nonsensical. Therefor I was wondering what the best way is to get the coordinates of the triangles in relation to the ImageView (or their first common parent node) in terms of ImageView-side-lengths. So if the triangle is in the center its coordinates are [0.5, 0.5] for instance.
The Image view will be moving around inside the scene and will also be changing size so it is vital that I can get the coordinates relative to not only the ImageView but also to the size of the ImageView.
Here is also the surrounding hierarchy of nodes if that helps. The Polygons are the triangles and the regions are the rectangles.
Thanks for all forms of help!
Node.getBoundsInParent returns the bounds of a node in it's parent coordinates. E.g. polygon.getBoundsInParent() would return the bounds in the VBox.
If you need to "go up" one additional step, you can use parent.localToParent to do this. vBox.localToParent(boundsInVbox) returns the bounds in the coordinate system of the AnchorPane.
To get values relative to the size of the image, you simply need to divide by it's size.
The following example only allows you to move the cover regions to in one direction and does not check, if the regions intersect, but it should be sufficient to demonstrate the approach.
The interesting part is the event handler of the button. It restricts the viewport of the second image to the part of the first image that isn't covered.
private static void setSideAnchors(Node node) {
AnchorPane.setLeftAnchor(node, 0d);
AnchorPane.setRightAnchor(node, 0d);
}
#Override
public void start(Stage primaryStage) {
// create covering area
Region topRegion = new Region();
topRegion.setStyle("-fx-background-color: white;");
Polygon topArrow = new Polygon(0, 0, 20, 0, 10, 20);
topArrow.setFill(Color.WHITE);
VBox top = new VBox(topRegion, topArrow);
top.setAlignment(Pos.TOP_CENTER);
topArrow.setOnMouseClicked(evt -> {
topRegion.setPrefHeight(topRegion.getPrefHeight() + 10);
});
// create bottom covering area
Region bottomRegion = new Region();
bottomRegion.setStyle("-fx-background-color: white;");
Polygon bottomArrow = new Polygon(0, 20, 20, 20, 10, 0);
bottomArrow.setFill(Color.WHITE);
VBox bottom = new VBox(bottomArrow, bottomRegion);
bottom.setAlignment(Pos.BOTTOM_CENTER);
bottomArrow.setOnMouseClicked(evt -> {
bottomRegion.setPrefHeight(bottomRegion.getPrefHeight() + 10);
});
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/402px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg");
ImageView imageView = new ImageView(image);
setSideAnchors(top);
setSideAnchors(bottom);
setSideAnchors(imageView);
AnchorPane.setTopAnchor(top, 0d);
AnchorPane.setBottomAnchor(bottom, 0d);
AnchorPane.setTopAnchor(imageView, 0d);
AnchorPane.setBottomAnchor(imageView, 0d);
AnchorPane container = new AnchorPane(imageView, top, bottom);
ImageView imageViewRestricted = new ImageView(image);
Button button = new Button("restrict");
button.setOnAction(evt -> {
// determine bouns of Regions in AnchorPane
Bounds topBounds = top.localToParent(topRegion.getBoundsInParent());
Bounds bottomBounds = bottom.localToParent(bottomRegion.getBoundsInParent());
// set viewport accordingly
imageViewRestricted.setViewport(new Rectangle2D(
0,
topBounds.getMaxY(),
image.getWidth(),
bottomBounds.getMinY() - topBounds.getMaxY()));
});
HBox root = new HBox(container, button, imageViewRestricted);
root.setFillHeight(false);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

How to select a 2D node in a 3D scene?

Here is my code. You can copy-paste and follow what I write bellow to see the problem yourself.
public class MyApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(new MyView(), 100, 150);
stage.setScene(scene);
stage.show();
}
private class MyView extends BorderPane {
MyView() {
GridPane board = new GridPane();
int size = 3;
for (int i = 0; i < size*size; i++) {
BorderPane pane = new BorderPane();
pane.setMinSize(30, 30);
pane.setBackground(new Background(new BackgroundFill(Color.RED, null, null)));
pane.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID,
null, null, null)));
pane.setOnMousePressed(e -> {
PickResult pick = e.getPickResult();
Pane selectedNode = (Pane) pick.getIntersectedNode();
selectedNode.setBackground(new Background(new BackgroundFill(Color.GREEN, null, null)));
});
board.add(pane, i / size, i % size);
}
Box box = new Box(20d, 20d, 20d);
BorderPane boardPane = new BorderPane(box, null, null, board, null);
Group root = new Group(boardPane);
SubScene scene = new SubScene(root, USE_PREF_SIZE, USE_PREF_SIZE, true, SceneAntialiasing.BALANCED);
scene.widthProperty().bind(widthProperty());
scene.heightProperty().bind(heightProperty());
setCenter(scene);
}
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
I create a subscene with a grid of squares. When i press on a square I want its background to change color. This works in 2 situations:
if I don't add the Box to the boardPane
if I don't set the scene with a depth buffer
or both. But if i both add the box and set a depth buffer, the squares don't receive the event. instead the boardPane receives it. i guess it's something to do with 2D nodes in a 3D scene.
I tried setting combinations of these methods :setPickOnBounds, setDepthTest, setMouseTransparent but nothing worked.
What's the solution?
Once you have a 3D scene, there aren't really such things as 2D nodes, everything in the scene graph has an x, y and z co-ordinate; even things that were previously treated as 2D nodes when the depth buffer was set to false.
When you place a box in your root border pane, that root border pane assumes the 3D co-ordinates of the box. For picking purposes, the box you have defined is represented in 3D space from z co-ordinates -10 to 10, so the root border pane is defined as -10 for picking purposes. The border panes that you place inside the root border pane have no z co-ordinate defined for them, so they end up at a z co-ordinate of 0, which, from the viewers perspective is behind the root border pane.
So, the root border pane is receiving clicks, but because it is now on a different z plane than the rest of its contents, the other 2D square contents you have defined do not receive clicks. One could argue that the root border pane is not rendered at all as it has no color, so it should be treated as transparent and the clicks just go through to the child nodes, but it seems that is just not how the 3D picking algorithm for JavaFX works.
For your example, to get everything in the same Z plane and picking working correctly, add the following line inside your for loop: pane.setTranslateZ(-10);.
Note: I debugged this by adding the following line to your source code (which reported to me the target of the mouse clicks and the x,y,z pick result co-ordinate for each click):
root.setOnMouseClicked(System.out::println);
My advice is to avoid using layout panes that are designed for 2D purposes (e.g. border panes) to attempt laying out elements in 3D space. The JavaFX layout panes are really only meant for 2D layout. To handle positioning in 3D space you should manage co-ordinates yourself, by just using Groups rather than anything that derives from Pane. At the very least don't try adding 3D elements into 2D layout panes as the results can be confusing (as you have discovered).
You can further separate 2D and 3D items by placing them in different sub scenes (that is really what the intention of the sub scene notion in JavaFX is I think). An example of an application with multiple sub scenes is shown in the following answer: How to create custom 3d model in JavaFX 8?

Rotate a drawn rectangle in flex

i wrote the following code for drawing a rotate rectangle
var s:UIComponent = new UIComponent();
s.graphics.lineStyle(1, 0x0000FF);
s.graphics.drawRect(50, 50, 200, 200);
s.rotation = 30;
template.addChild(s);
where template is a canvas. Its rotate nicely but the problem is the position is not in right place. i.e. it is not in (50,50) after rotate. How can i solve this problem?
Thanks in advance.
What you're seeing is the default rotation around the origin in Flex (in your case the X,Y coordinates of 50,50) I am assuming that you want to rotate around the center (as I recently had to do). There is the jury rig way to do it, by adjusting the origin point based on rotation angle. Then there is the rotate effect:
import mx.effects.Rotate;
var s:UIComponent = new UIComponent();
var rotator:Rotate = new Rotate(s);
s.graphics.lineStyle(1, 0x0000FF);
s.graphics.drawRect(50, 50, 200, 200);
rotator.angleFrom = 0;
rotator.angleTo = 30;
rotator.originX = s.width/2;
rotator.originY = s.height/2;
template.addChild(s);
rotator.play();
Now I've noticed some problems with this that required me to set the width and height of the rotated object again after the play() method, but other than that I get the ideal rotation situation.
Another downside to this is that it's an effect, which means it visually rotates the object. I will post back when I correct that, if you don't want to see the object rotate.
The Answer is duration
just by adding rotator.duration = 1 before play it happens so quick the user won't see it. 1 being 1 millisecond. I tried 0, but that resulted in no rotation occurring. Obviously if you want to see the effect in action you can increase that length of time by using any value in milliseconds.

Resources