i was trying to implement a custom context menu that the menu items will appear around the node, the idea was as follows, draw a virtual circle around the target node, and compute the circumference of the circle, first menu item will be at the top of the target node, and the rest will be distributed with equal distance between each one of the menu items,,,, i didnt write any line of code because i dont know how to start at least,,, plz help ?
maybe this image will explain better
in case someone is interested, i manged to do it , but the code is kinda dirty:
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* #author Asendar
*
*/
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RadialMenuFactory {
public static final RadialMenuFactory instance = new RadialMenuFactory();
public StackPane construct(int itemsCount, int radius) {
StackPane pane = new StackPane();
double pheta = 270;
int counter = 0;
while (counter != itemsCount) {
double x = radius * Math.cos(Math.toRadians(pheta));
double y = radius * Math.sin(Math.toRadians(pheta));
Button btn = new Button("btn");
btn.setTranslateX(x);
btn.setTranslateY(y);
pane.getChildren().addAll(btn);
pheta += 360 / itemsCount;
pheta %= 360;
counter++;
}
pane.setMinHeight(300);
pane.setMinWidth(300);
return pane;
}
}
Related
How do I specify a material for an fxyz3d shape? When I add to a JavaFX Group of 3D objects the fxyz3d node
Cone cone = new Cone(coneFacets, coneRadius, coneHeight);
cone.setMaterial(Materials.redMaterial());
it turns every shape in that group solid black, not just the cone, regardless of what any of the specified materials are. If I comment out the above two lines and the one that adds the cone to the group, all the displays of the other shapes return to their specified appearances.
I am using javafx-sdk-17.0.1, fxyz3d-0.5.4.jar, JavaSE-16, Windows 10. Is Javadoc available for fxyz3d anywhere? Or is it necessary to download source and build it locally?
The redMaterial is defined as
final PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.INDIANRED);
material.setSpecularColor(Color.RED);
The following code will reproduce this. As is, both cone and cylinder display black. Comment out the four lines that create and add the Cone, and the cylinder will display red as specified by the material. (Don't otherwise use this as a starting-point example, as there are also issues with automatic scaling as the user adjusts the stage window size yet to be addressed.)
package org.javafxtests;
import org.fxyz3d.shapes.Cone;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.stage.Stage;
public class JxyzConeMaterials extends Application {
// https://www.tutorialspoint.com/javafx/index.htm
// https://www.javatpoint.com/javafx-tutorial
// https://openjfx.io/javadoc/11/
/**
* The application initialization method.
*/
#Override
public void init() throws Exception {
super.init();
}
/**
* Main entry point for all JavaFX applications. The start method is called
* after the init method has returned and the JavaFX framework and hosting
* system are ready to start the application.
*/
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Test JXzy Cone materials");
double sceneWidth = 750.0d;
double sceneHeight = 500.0d;
// The scene structure is constructed from the inside-out (bottom-up).
// A tool bar goes along the top
final FlowPane toolbar = new FlowPane();
toolbar.setPrefWidth(Double.MAX_VALUE);
toolbar.getChildren().addAll(new Label("Files"));
// A TreeView goes down the left side
final TreeView<String> treeView = new TreeView<String>();
treeView.setPrefHeight(Double.MAX_VALUE);
TreeItem<String> treeRoot = new TreeItem<String>("<empty>");
treeView.setRoot(treeRoot);
// A SubScene for viewing 3D objects goes to the right of the TreeView
final SubScene canvasScene = new SubScene(new AnchorPane(), 0, 0);
final AnchorPane canvasRootPane = (AnchorPane) canvasScene.getRoot();
canvasRootPane.setPrefWidth(Double.MAX_VALUE);
canvasRootPane.setPrefHeight(Double.MAX_VALUE);
canvasScene.setWidth(0.75 * sceneWidth); // No setPref methods
canvasScene.setHeight(sceneHeight);
// Create a controllable camera for the 3D SubScene
final PerspectiveCamera canvasCamera = new PerspectiveCamera(true);
final Group cameraTruck = new Group();
final Group cameraGimbal = new Group();
canvasCamera.setFarClip(6000);
canvasCamera.setNearClip(0.01);
cameraGimbal.getChildren().add(canvasCamera);
cameraTruck.getChildren().add(cameraGimbal);
cameraTruck.setTranslateZ(-500.0d);
canvasScene.setCamera(canvasCamera);
canvasRootPane.getChildren().add(cameraTruck);
// Create an HBox at the bottom of the scene,
// TreeView on the left and 3D canvas on the right.
HBox treeAnd3dViews = new HBox(treeView, canvasScene);
treeAnd3dViews.setFillHeight(true);
HBox.setHgrow(canvasScene, Priority.ALWAYS);
treeAnd3dViews.setMaxHeight(Double.MAX_VALUE);
treeAnd3dViews.setMaxWidth(Double.MAX_VALUE);
// Create a VBox to stack the tool bar over the above.
VBox toolbarOverViews = new VBox(toolbar, treeAnd3dViews);
toolbarOverViews.setMaxWidth(Double.MAX_VALUE);
toolbarOverViews.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(treeAnd3dViews, Priority.ALWAYS);
AnchorPane.setTopAnchor(toolbarOverViews, 0.0);
AnchorPane.setBottomAnchor(toolbarOverViews, 0.0);
AnchorPane.setLeftAnchor(toolbarOverViews, 0.0);
AnchorPane.setRightAnchor(toolbarOverViews, 0.0);
final Scene scene = new Scene(new AnchorPane(), sceneWidth, sceneHeight);
final AnchorPane sceneRootPane = (AnchorPane) scene.getRoot();
sceneRootPane.getChildren().add(toolbarOverViews);
// Draw an arrow consisting of a cylinder with a cone on top.
double lineRadius = 1.0d;
double lineLength = 25.0d;
int coneFacets = 6;
double coneRadius = 3.0d;
double coneHeight = 6.0d;
final PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.INDIANRED);
material.setSpecularColor(Color.RED);
Cylinder cylinder = new Cylinder(lineRadius, lineLength);
cylinder.setMaterial(material);
Cone cone = new Cone(coneFacets, coneRadius, coneHeight);
cone.setMaterial(material);
// The cone points in the negative Y direction
cone.setTranslateY(-(lineLength / 2.0d) - coneHeight );
canvasRootPane.getChildren().add(cylinder);
canvasRootPane.getChildren().add(cone);
// Show
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() throws Exception {
super.stop();
}
/**
* Main method to launch the application with parameters if needed.
* This may or may not be called, depending on how this application
* is launched.
*
* #param args specifies arguments to {#linkplain Application#launch)}.
*/
public static void main(String[] args) {
launch(args);
}
}
If I have two Nodes stacked on top of each other and overlapping, how can I make the Node on top mouseTransparent (so that the bottom Node can react to MouseEvents) but also have the Node on top react to some MouseEvents like onMouseEntered?
For example, consider two (let's say rectangular) Panes inside a StackPane, with the bottom one smaller and completely underneath the top one:
<StackPane>
<Pane onMouseEntered="#printA" onMouseClicked="#printB" />
<Pane onMouseEntered="#printC" />
</StackPane>
If the user moves his mouse over the top Pane then C should be printed in the console. If he also moves his mouse over the bottom Pane then A should be printed too. If he clicks with mouse over the bottom Pane then B should be printed. Clicking over the top Pane but not over the bottom Pane should do nothing.
Why do I want to do something like this? I want to detect when the mouse moves near the center of a Pane so that I can change the Pane's center content (basically from display mode to edit mode) and let the user interact with the new content. I want the detection area to be larger than the center itself and thus it will overlap with some other things inside the Pane. So the Pane center can't be the detector, it has to be something transparent stacked on top. The detector also has to remain there so it can detect when the mouse moves away again.
There are lots of questions on Stackoverflow that appear similar, but almost all of them are solved by setMouseTransparent(true) or setPickOnBounds(true). setMouseTransparent doesn't work here since then the top Pane won't print C. setPickOnBounds makes the Pane mouseTransparent everywhere the Pane is alpha/visually transparent, but then the transparent parts won't print C and the opaque parts prevent the lower Pane from printing A or B. So even if the top Pane is completely transparent or completely opaque it doesn't solve my issue. Setting the visibility to false for the top Pane also won't work since then the top Pane cannot print C.
One way I can think, is to observe the mouse movements on the parent node, to see if the mouse pointer falls on the detection zone of the desired node. This way you don't need a dummy (transparent)node for detection.
So the idea is as follows:
<StackPane id="parent">
<Pane onMouseEntered="#printA" onMouseClicked="#printB" />
// Other nodes in the parent
</StackPane>
As usual, you will have handlers on the center node.
Add a mouseMoved handler on the parent node to detect for mouse entering in detection zone.
Please check the below working demo :
import javafx.application.Application;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MouseEventsDemo extends Application {
double detectionSize = 30;
#Override
public void start(Stage primaryStage) throws Exception {
Pane center = getBlock("red");
center.setOnMouseEntered(e -> System.out.println("Entered on center pane..."));
center.setOnMouseClicked(e -> System.out.println("Clicked on center pane..."));
// Simulating that the 'center' is surrounded by other nodes
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.addRow(0, getBlock("yellow"), getBlock("pink"), getBlock("orange"));
grid.addRow(1, getBlock("orange"), center, getBlock("yellow"));
grid.addRow(2, getBlock("yellow"), getBlock("pink"), getBlock("orange"));
// Adding rectangle only for zone visual purpose
Rectangle zone = new Rectangle(center.getPrefWidth() + 2 * detectionSize, center.getPrefHeight() + 2 * detectionSize);
zone.setStyle("-fx-stroke:blue;-fx-stroke-width:1px;-fx-fill:transparent;-fx-opacity:.4");
zone.setMouseTransparent(true);
StackPane parent = new StackPane(grid, zone);
VBox root = new VBox(parent);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setSpacing(20);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
addDetectionHandler(parent, center);
}
private Pane getBlock(String color) {
Pane block = new Pane();
block.setStyle("-fx-background-color:" + color);
block.setMaxSize(100, 100);
block.setPrefSize(100, 100);
return block;
}
private void addDetectionHandler(StackPane parent, Pane node) {
final String key = "onDetectionZone";
parent.setOnMouseMoved(e -> {
boolean mouseEntered = (boolean) node.getProperties().computeIfAbsent(key, p -> false);
if (!mouseEntered && isOnDetectionZone(e, node)) {
node.getProperties().put(key, true);
// Perform your mouse enter operations on detection zone,.. like changing to edit mode.. or what ever
System.out.println("Entered on center pane detection zone...");
node.setStyle("-fx-background-color:green");
} else if (mouseEntered && !isOnDetectionZone(e, node)) {
node.getProperties().put(key, false);
// Perform your mouse exit operations from detection zone,.. like change back to default state from edit mode
System.out.println("Exiting from center pane detection zone...");
node.setStyle("-fx-background-color:red");
}
});
}
private boolean isOnDetectionZone(MouseEvent e, Pane node) {
Bounds b = node.localToScene(node.getBoundsInLocal());
double d = detectionSize;
Bounds detectionBounds = new BoundingBox(b.getMinX() - d, b.getMinY() - d, b.getWidth() + 2 * d, b.getHeight() + 2 * d);
return detectionBounds.contains(e.getSceneX(), e.getSceneY());
}
}
Note: You can go with a more better approach for checking if the mouse pointer falls in detection zone of the desired node :)
UPDATE : Approach#2
Looks like what you are seeking is a node which resembles a picture frame that has a hole through it :). If that is the case, the approach i can think of is to build a shape (like a frame) and place it over the desired node.
And then you can simply add mouse handlers to the detection zone and the center nodes.
Please check the below working demo:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Polyline;
import javafx.stage.Stage;
public class MouseEventsDemo2 extends Application {
double detectionSize = 30;
#Override
public void start(Stage primaryStage) throws Exception {
Pane center = getBlock("red");
center.getChildren().add(new Label("Hello"));
center.setOnMouseEntered(e -> {
System.out.println("Entered on center pane...");
center.setStyle("-fx-background-color:green");
});
center.setOnMouseClicked(e -> System.out.println("Clicked on center pane..."));
// Simulating that the 'center' is surrounded by other nodes
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.addRow(0, getBlock("yellow"), getBlock("pink"), getBlock("orange"));
grid.addRow(1, getBlock("orange"), center, getBlock("yellow"));
grid.addRow(2, getBlock("yellow"), getBlock("pink"), getBlock("orange"));
StackPane parent = new StackPane(grid);
VBox root = new VBox(parent);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setSpacing(20);
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
// Building the frame using Polyline node
Polyline zone = new Polyline();
zone.setStyle("-fx-fill:grey;-fx-opacity:.5;-fx-stroke-width:0px;");
zone.setOnMouseEntered(e -> {
System.out.println("Entered on detection zone...");
center.setStyle("-fx-background-color:green");
});
zone.setOnMouseExited(e -> {
System.out.println("Exited on detection zone...");
center.setStyle("-fx-background-color:red");
});
zone.setOnMouseClicked(e -> System.out.println("Clicked on detection zone..."));
parent.getChildren().add(zone);
parent.layoutBoundsProperty().addListener(p -> updatePolylineZone(center, zone));
center.layoutBoundsProperty().addListener(p -> updatePolylineZone(center, zone));
updatePolylineZone(center, zone);
}
/**
* Update the poly line shape to build a frame around the center node if the parent or center bounds changed.
*/
private void updatePolylineZone(Pane center, Polyline zone) {
zone.getPoints().clear();
Bounds b = center.localToParent(center.getBoundsInLocal());
double s = detectionSize;
ObservableList<Double> pts = FXCollections.observableArrayList();
// A-------------------------B
// | |
// | a---------------b |
// | | | |
// | | | |
// | | | |
// | d---------------c |
// | |
// D-------------------------C
// Outer square
pts.addAll(b.getMinX() - s, b.getMinY() - s); // A
pts.addAll(b.getMaxX() + s, b.getMinY() - s); // B
pts.addAll(b.getMaxX() + s, b.getMaxY() + s); // C
pts.addAll(b.getMinX() - s, b.getMaxY() + s); // D
// Inner Square
pts.addAll(b.getMinX() + 1, b.getMaxY() - 1); // d
pts.addAll(b.getMaxX() - 1, b.getMaxY() - 1); // c
pts.addAll(b.getMaxX() - 1, b.getMinY() + 1); // b
pts.addAll(b.getMinX() + 1, b.getMinY() + 1); // a
// Closing the loop
pts.addAll(b.getMinX() - s, b.getMinY() - s); // A
pts.addAll(b.getMinX() - s, b.getMaxY() + s); // D
pts.addAll(b.getMinX() + 1, b.getMaxY() - 1); // d
pts.addAll(b.getMinX() + 1, b.getMinY() + 1); // a
pts.addAll(b.getMinX() - s, b.getMinY() - s); // A
zone.getPoints().addAll(pts);
}
private Pane getBlock(String color) {
Pane block = new Pane();
block.setStyle("-fx-background-color:" + color);
block.setMaxSize(100, 100);
block.setPrefSize(100, 100);
return block;
}
}
Here is a potential strategy which might be used.
Create a node which is the shape of the area you want to detect interaction with.
In the example below it is a circle and in this description I term it the "detection node".
Make the detection node mouse transparent so that it doesn't consume or interact with any of the standard mouse events.
Add an event listener to the scene (using an event handler or event filter as appropriate).
When the scene event listener recognizes an event which should also be routed to the detection node, copy the event and fire it specifically at the detection node.
The detection node can then respond to the duplicated event.
The original event is also processed unaware of the detection node, so it can interact with other nodes in the scene as though the detection node was never there.
The effect will be that two events occur and can be separately handled by the target node and by the detection node.
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.concurrent.atomic.AtomicBoolean;
public class LayersFX extends Application {
private final ListView<String> logViewer = new ListView<>();
// set to true if you with to see move events in the log.
private final boolean LOG_MOVES = false;
#Override
public void start(Stage stage) {
Rectangle square = new Rectangle(200, 200, Color.LIGHTSKYBLUE);
Circle circle = new Circle(80, Color.LEMONCHIFFON);
StackPane stack = new StackPane(square, circle);
addEventHandlers(square);
addEventHandlers(circle);
VBox layout = new VBox(10, stack, logViewer);
layout.setPadding(new Insets(10));
logViewer.setPrefSize(200, 200);
Scene scene = new Scene(layout);
routeMouseEventsToNode(scene, circle);
stage.setScene(scene);
stage.show();
}
/**
* Intercepts mouse events from the scene and created duplicate events which
* are routed to the node when appropriate.
*/
private void routeMouseEventsToNode(Scene scene, Node node) {
// make the node transparent to standard mouse events.
// it will only receive mouse events we specifically create and send to it.
node.setMouseTransparent(true);
// consume all events for the target node so that we don't
// accidentally let a duplicated event bubble back up to the scene
// and inadvertently cause an infinite loop.
node.addEventHandler(EventType.ROOT, Event::consume);
// Atomic isn't used here for concurrency, it is just
// a trick to make the boolean value effectively final
// so that it can be used in the lambda.
AtomicBoolean inNode = new AtomicBoolean(false);
scene.setOnMouseMoved(
event -> {
boolean wasInNode = inNode.get();
boolean nowInNode = node.contains(
node.sceneToLocal(
event.getSceneX(),
event.getSceneY()
)
);
inNode.set(nowInNode);
if (nowInNode) {
node.fireEvent(
event.copyFor(
node,
node,
MouseEvent.MOUSE_MOVED
)
);
}
if (!wasInNode && nowInNode) {
node.fireEvent(
event.copyFor(
node,
node,
MouseEvent.MOUSE_ENTERED_TARGET
)
);
}
if (wasInNode && !nowInNode) {
node.fireEvent(
event.copyFor(
node,
node,
MouseEvent.MOUSE_EXITED_TARGET
)
);
}
}
);
}
private void addEventHandlers(Node node) {
String nodeName = node.getClass().getSimpleName();
node.setOnMouseEntered(
event -> log("Entered " + nodeName)
);
node.setOnMouseExited(
event -> log("Exited " + nodeName)
);
node.setOnMouseClicked(
event -> log("Clicked " + nodeName)
);
node.setOnMouseMoved(event -> {
if (LOG_MOVES) {
log(
"Moved in " + nodeName +
" (" + Math.floor(event.getX()) + "," + Math.floor(event.getY()) + ")"
);
}
});
}
private void log(String msg) {
logViewer.getItems().add(msg);
logViewer.scrollTo(logViewer.getItems().size() - 1);
}
public static void main(String[] args) {
launch(args);
}
}
There is probably some better way of doing this using a custom event dispatch chain, but the logic above is what I came up with. The logic appears to do what you asked in your question, though it may not have the full functionality that you need for your actual application.
As mentioned in the title, i have two Circle 's the first is draggable and the second is fixed, I would rotate (with the drag) the first one around the second without overlapping them but my Circle reacts oddly, I'm sure the error comes from the drag condition but I don't know how to solve it, that's why I need your help, here is a minimal and testable code :
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Collision extends Application{
private Pane root = new Pane();
private Scene scene;
private Circle CA = new Circle(20);
private Circle CB = new Circle(20);
private double xOffset = 0;
private double yOffset = 0;
#Override
public void start(Stage stage) throws Exception{
initCircles();
scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
private void initCircles(){
CA.setCenterX(100);
CA.setCenterY(100);
CA.setFill(Color.rgb(255, 0, 0,0.2));
CA.setStroke(Color.BLACK);
CB.setCenterX(250);
CB.setCenterY(200);
CB.setFill(Color.rgb(255, 0, 0,0.2));
CB.setStroke(Color.BLACK);
CA.setOnMousePressed(evt->{
xOffset = CA.getCenterX() - evt.getSceneX();
yOffset = CA.getCenterY() - evt.getSceneY();
});
CA.setOnMouseDragged(evt->{
//get Scene coordinate from MouseEvent
drag(evt.getSceneX(),evt.getSceneY());
});
root.getChildren().addAll(CA,CB);
}
private void drag(double x, double y){
/* calculate the distance between
* the center of the first and the second circle
*/
double distance = Math.sqrt (Math.pow(CA.getCenterX() - CB.getCenterX(),2) + Math.pow(CA.getCenterY() - CB.getCenterY(),2));
if (!(distance < (CA.getRadius() + CB.getRadius()))){
CA.setCenterX(x + xOffset);
CA.setCenterY(y + yOffset);
}else{
/**************THE PROBLEM :Condition to drag************/
CA.setCenterX(CA.getCenterX() - (CB.getCenterX()-CA.getCenterX()));
CA.setCenterY(CA.getCenterY() - (CB.getCenterY()-CA.getCenterY()));
/*What condition must be established for the
* circle to behave correctly
*/
/********************************************************/
}
}
public static void main(String[] args) {
launch(args);
}
}
Here is a brief overview :
Note:
for my defense, i searched and found several subject close to mine but which have no precise or exact solution, among which:
-The circle remains blocked at the time of the collision
-Two circle that push each other
-JavaScript, Difficult to understand and convert to java
Thank you for your help !
Point2D can be interpreted as a 2D vector, and has useful methods for creating new vectors from it, etc. You can do:
private void drag(double x, double y){
// place drag wants to move circle to:
Point2D newCenter = new Point2D(x + xOffset, y+yOffset);
// center of fixed circle:
Point2D fixedCenter = new Point2D(CB.getCenterX(), CB.getCenterY());
// minimum distance between circles:
double minDistance = CA.getRadius() + CB.getRadius() ;
// if they overlap, adjust newCenter:
if (newCenter.distance(fixedCenter) < minDistance) {
// vector between fixedCenter and newCenter:
Point2D newDelta = newCenter.subtract(fixedCenter);
// adjust so that length of delta is distance between two centers:
Point2D adjustedDelta = newDelta.normalize().multiply(minDistance);
// move newCenter to match adjusted delta:
newCenter = fixedCenter.add(adjustedDelta);
}
CA.setCenterX(newCenter.getX());
CA.setCenterY(newCenter.getY());
}
Obviously, you could do all this without using Point2D and just doing the computation, but I think the API calls make the code easier to understand.
I am developping a game, in JavaFX 3D, in which the player is running down a long road. There are items and obstacles coming his way. The point is: now the objects just appear out of nowhere if they enter my clipping distance. Therefore I want to create some kind of fog (or anything else that "hides" the initialization of the objects)
The problem is that I cannot find any example in which this is done. I am looking for an example piece of code/link to source/any other advice.
Having said it's too broad a question, and there must be many, many ways to do this, here's one possible implementation. This is 2D but could easily be adapted for a 3D app (I think). The idea is just to let a few light gray circles drift around on a white background, and apply an enormous blur to the whole thing.
You could then let your objects appear with this as a background, and fade them in from gray to their real color (or some such). The colors and velocities of the circles, and the blur radius, probably need some tuning...
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class FogExample extends Application {
private static final int WIDTH = 600 ;
private static final int HEIGHT = 600 ;
#Override
public void start(Stage primaryStage) {
Fog fog = new Fog(WIDTH, HEIGHT);
Scene scene = new Scene(new StackPane(fog.getView()), WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Fog {
private final int width ;
private final int height ;
private final Pane fog ;
private final Random RNG = new Random();
public Fog(int width, int height) {
this.width = width ;
this.height = height ;
this.fog = new Pane();
Rectangle rect = new Rectangle(0, 0, width, height);
rect.setFill(Color.rgb(0xe0, 0xe0, 0xe0));
fog.getChildren().add(rect);
for (int i = 0; i < 50; i++) {
fog.getChildren().add(createFogElement());
}
fog.setEffect(new GaussianBlur((width + height) / 2.5));
}
private Circle createFogElement() {
Circle circle = new Circle(RNG.nextInt(width - 50) + 25, RNG.nextInt(height - 50) + 25, 15 + RNG.nextInt(50));
int shade = 0xcf + RNG.nextInt(0x20);
circle.setFill(Color.rgb(shade, shade, shade));
AnimationTimer anim = new AnimationTimer() {
double xVel = RNG.nextDouble()*40 - 20 ;
double yVel = RNG.nextDouble()*40 - 20 ;
long lastUpdate = 0 ;
#Override
public void handle(long now) {
if (lastUpdate > 0) {
double elapsedSeconds = (now - lastUpdate) / 1_000_000_000.0 ;
double x = circle.getCenterX() ;
double y = circle.getCenterY() ;
if ( x + elapsedSeconds * xVel > width || x + elapsedSeconds * xVel < 0) {
xVel = - xVel ;
}
if ( y + elapsedSeconds * yVel > height || y + elapsedSeconds * yVel < 0) {
yVel = - yVel ;
}
circle.setCenterX(x + elapsedSeconds*xVel);
circle.setCenterY(y + elapsedSeconds * yVel);
}
lastUpdate = now ;
}
};
anim.start();
return circle ;
}
public Node getView() {
return fog ;
}
}
public static void main(String[] args) {
launch(args);
}
}
Typical 3D systems use a Particle system to do this which involves a combination of transparency, alpha fluctuations, and billboard textures. Believe it or not Fog, smoke and flames... particle effects... are not actually 3D at all but 2D images that have been positioned, sized, and colored to appear in 3D. They are then animated rapidly enough so that the human viewer can't make out the nuances of the 2D images.
The problem with JavaFX 3D is that it does not support transparency "yet".(it is unofficially available). Also there is no particle type object that will let you overlay shapes and textures as James_D suggests without manually managing the positioning with respect to the camera.
However there is hope... the F(X)yz project provides a BillBoard class which will allow you to place an image perpendicular to the camera. You can rapidly update this image using a Timer and some Random.next() type Circle creations for the fog using the approach James_D suggested. An example on using the BillBoard in a manner like this is in BillBoardBehaviourTest and CameraViewTest. Performance is demonstrated in CameraViewTest.
What I would do if I were you is setup a large frustrum wide BillBoard object at the back -Z position of your frustrum (where the objects enter the scene) and then setup a Timer/Random circle generator in a pane off screen. Then using the approach in the CameraViewTest, snapshot the off screen pane (which has the circles) and then set that image to the BillBoard. You will have to have the SnapShot call be on some timer itself to achieve an effect of animation. The CameraViewTest demonstrates how to do that. The effect to the user will be a bunch of moving blurry tiny circles.
Fog on the cheap!
It seems to be a simple problem. But I found no simple solution. If you scale Nodes, the new form will be in the center of the parent. But I would like that the new form has the same Top-Left Corner as the old one.
The expample code is:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TestScale extends Application{
Group root;
Pane pane;
Scene scene;
Rectangle rect0;
#Override
public void start(Stage stage) {
root = new Group();
scene = new Scene(root, 200, 160);
rect0=new Rectangle(0, 0, 200, 160);
rect0.setFill(Color.BLUE);
pane = new Pane();
pane.getChildren().add(rect0);
Button btnForward = new Button();
btnForward.setText(">");
btnForward.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
transform(pane);
}
});
root.getChildren().add(pane);
root.getChildren().add(btnForward);
stage.setScene(scene);
stage.show();
}
void transform (Node node){
node.setScaleX(0.5);
node.setScaleY(0.5);
}
public static void main(String[] args) {
launch(args);
}
}
All tests with Stackpane, Borderpane, Anchorpane, Groups delivers no easy solution. The only way seems to be with setTransformX and setTransformY. But I need for this a complex calculation of the arguments.
When you use ScaleX/ScaleY, scaling occurs from the center of the node.
From JavaDocs
The pivot point about which the scale occurs is the center of the untransformed layoutBounds.
So, if you want to translate the scaling co-ordinates, you need to take the scaling compression into account when you set the required translation values.
As your current pivot is center, you need to set Translate to a negative value. Since the compression of X and Y is half, so you need to translate to 1/4 of total size of the scene.
node.setScaleX(0.5);
node.setScaleY(0.5);
node.setTranslateX(0 - node.getScene().getWidth()/4);
node.setTranslateY(0 - node.getScene().getHeight()/4);
Here ist the code to transform an rectangle within an image:
The procedure deliver a scalefaktor for setScaleX and setScaleY (scale) and set value tx for setTransformX and ty for setTransformY.
public Scaler(double sceneWidth, double sceneHeight, double imgWidth, double imgHeight,
int x, int y, int width, int height) {
double scrnRatio = sceneHeight / sceneWidth;
double offsetX = 0.;
double offsetY = 0.;
if (height / (double)width > scrnRatio) {
offsetX = (height / scrnRatio - width) / 2.;
scale = sceneHeight/height;
} else {
offsetY = (width * scrnRatio - height) / 2.;
scale = sceneWidth/width;
}
double dh = (1. - scale) / 2.;
tx = -(x - offsetX) * scale - dh * imgWidth;
ty = -(y - offsetY) * scale - dh * imgHeight;
}
There is no way for an easier code?