Add touch gestures to legacy swing application - java

I have a legacy swing application that I need to add touch gestures to,specifically pinch to zoom and touch and drag.
I tried the SwingNode of JDK 8 and I can run the swing application there, but the display performance was cut by more than 50% which won't work. SwingTextureRenderer in MT4J has the same issue and that is without even trying to redispatch touch events as mouse events.
I thought about a glass pane approach using a JavaFX layer on top and capturing the touch events and attempting to dispatch them as mouse events to the Swing app underneath.
Does anyone have an alternative approach? The target platform is windows 8.
Bounty coming as soon as Stackoverflow opens it up. I need this one pretty rapidly.
EDIT:
Here is what I tried with SwingNode (the mouse redispatch didn't work). The SwingNode stuff might be a distraction from the best solution so ignore this if you have a better idea for getting touch into swing:
#Override
public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
createAndSetSwingContent(swingNode);
StackPane pane = new StackPane();
pane.getChildren().add(swingNode);
stage.setScene(new Scene(pane, 640, 480));
stage.show();
}
private void createAndSetSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
UILib.setPlatformLookAndFeel();
// create GraphViewTouch
String datafile = null;
String label = "label";
final JPanel frame = GraphView.demoFrameless(datafile, label);
swingNode.setContent(frame);
swingNode.setOnZoom(new EventHandler<ZoomEvent>() {
#Override public void handle(ZoomEvent event) {
MouseWheelEvent me = new MouseWheelEvent(frame, 1, System.currentTimeMillis(), 0, (int)Math.round(event.getSceneX()), (int)Math.round(event.getSceneY()), (int)Math.round(event.getScreenX()), (int)Math.round(event.getScreenY()), (int)Math.round(event.getZoomFactor()), false, MouseWheelEvent.WHEEL_UNIT_SCROLL, (int)Math.round(event.getZoomFactor()), (int)Math.round(event.getZoomFactor()), event.getZoomFactor());
frame.dispatchEvent(me);
System.out.println("GraphView: Zoom event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
}
});
}

You can use JNA to parse the messages from Windows.
Some documentation on the multi touch events from Microsoft:
https://learn.microsoft.com/en-us/windows/desktop/wintouch/wm-touchdown
Some documentation on how to do it in Java and sample code:
https://github.com/fmsbeekmans/jest/wiki/Native-Multitouch-for-AWT-component-(Windows)

hmm... this is a tough one, but there is a chance.
What I would do is make multiple MouseListener classes (relative to the number of mouse events you want to pick up), and than create some sort of system to detect certain adjustments, eg. (zoom)
1st listener:
public void mousePressed(MouseEvent e){
//Set click to true
clk = true;
//set first mouse position
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Second Listener
public void mousePressed(MouseEvent e){
//set clicked to true
clk = true;
//set first mouse position (listener 2)
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Main handler
if(Listener1.get1stMousePos() < Listener1.get2ndMousePos() && Listener2.get1stMousePos() < Listener2.get2ndMousePos() && Listener1.clk && Listener2.clk){
zoomMethod((Listener1.get1stMousePos - Listener1.get2ndMousePos()) + (Listener1.get1stListener2.get2ndMousePos());
}
And than just do this to add it to the window:
window.addMouseListener(Listener1);
window.addMouseListener(Listener2);
Hope you find a way.

For the pinch-to-zoom:
Try the GestureMagnificationListener by guigarage.com. It provides a method:
public void magnify(GestureMagnificationEvent me){...}

Related

Mouse release event doesn't happen on Ubuntu when target Node changes scene?

This question deals with mouse behavior across operating systems; specifically, my code works on Windows and Mac OS X, but not on Ubuntu.
Ultimately what I am trying to do is make a special Pane subclass ("ConvertiblePane") that exists within a parent Pane on a main stage/scene, but is magically transferred into its own temporary stage/scene when dragged, thus becoming independent and able to be placed anywhere on the screen. When the user releases the mouse button, the ConvertiblePane should drop back onto its original parent Pane and lose the temporary stage. (In my full program, the original parent Stage resizes/repositions itself to accommodate the ConvertiblePane wherever it is dropped.)
This brings me to my issue. When I press the mouse on the ConvertiblePane, it triggers a MousePress within the main scene as expected, at which point the ConvertiblePane is moved to the temporary stage. As I drag the mouse, it triggers MouseDrag within the temporary scene and moves the temp stage. Ok, great.
When I release the mouse button, however, I experience different behavior on different operating systems. On Windows (7) and Mac OS X (10.12.6), a MouseRelease occurs in the temp scene, sending the pane back to its original parent in the main stage as expected. On Ubuntu, however, no MouseRelease seems to be generated in either the main scene or the temp scene.
Here's the relevant code as an MCV example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class ConvertibleTest extends Application {
#Override
public void start(Stage primaryStage) {
// Set up the main stage and scene with a
// Pane as root and a ConvertiblePane child:
primaryStage.initStyle(StageStyle.TRANSPARENT);
Pane root = new Pane();
ConvertiblePane conv = new ConvertiblePane();
root.getChildren().add(conv);
Scene scene = new Scene(root, 400, 400, Color.PINK);
primaryStage.setTitle("Convertible Test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class ConvertiblePane extends Pane
{
private final Group TEMP_ROOT = new Group();
private final Stage TEMP_STAGE = new Stage(StageStyle.TRANSPARENT);
private final Scene TEMP_SCENE = new Scene(TEMP_ROOT);
private Pane originalParent = null;
private double deltaX = 0.0;
private double deltaY = 0.0;
private String name = null;
public void onMousePress(MouseEvent event)
{
// Save deltaX/Y for later:
Point2D delta = this.sceneToLocal(event.getX(), event.getY());
deltaX = delta.getX();
deltaY = delta.getY();
if (!isIndependent())
{
makeIndependent();
}
}
public void onMouseDrag(MouseEvent event)
{
// Keep the TEMP_STAGE relative to the original click point:
TEMP_STAGE.setX(event.getScreenX()-deltaX);
TEMP_STAGE.setY(event.getScreenY()-deltaY);
}
public void onMouseRelease(MouseEvent event)
{
if (isIndependent())
{
returnToParent();
}
}
public ConvertiblePane()
{
this.setPrefSize(100, 100);
this.setBackground(new Background(new BackgroundFill(Color.GREEN, new CornerRadii(10), Insets.EMPTY)));
this.setVisible(true);
// Attach event code and report to System.out what is happening:
this.setOnMousePressed((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Pressed as Independent");
else
System.out.println("Pressed as Child");
onMousePress(event);
});
this.setOnMouseDragged((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Dragged as Independent");
else
System.out.println("Dragged as Child");
onMouseDrag(event);
});
this.setOnMouseReleased((MouseEvent event) -> {
if (this.getScene() == TEMP_SCENE)
System.out.println("Released as Independent");
else
System.out.println("Released as Child");
onMouseRelease(event);
});
}
public boolean isIndependent()
{
// Return whether this ConvertiblePane is "independent" (exists in its own temp scene)
return this.getScene() == TEMP_SCENE;
}
public void makeIndependent()
{
// Get the point where this ConvertiblePane appears on screen:
Point2D screenPt = this.localToScreen(0, 0);
// Save the originaParent of this ConvertiblePane; we will return to it later:
originalParent = (Pane)getParent();
// Remove this ConvertiblePane from its originalParent:
originalParent.getChildren().remove(this);
// Set this ConvertiblePane as the root of the TEMP_SCENE on the TEMP_STAGE:
TEMP_SCENE.setRoot(this);
TEMP_STAGE.setScene(TEMP_SCENE);
System.out.println("Transferred to TEMP.");
this.relocate(0, 0);
// Show the TEMP_STAGE in the same location on screen where this ConvertiblePane originally was:
TEMP_STAGE.setX(screenPt.getX());
TEMP_STAGE.setY(screenPt.getY());
TEMP_STAGE.show();
}
public void returnToParent()
{
// Reset deltas:
deltaX = 0;
deltaY = 0;
// Get the location of this ConvertiblePane on screen:
Point2D screenPt = this.localToScreen(0, 0);
// Set TEMP_ROOT as the root of TEMP_SCENE; this will allow us to detach
// this ConvertiblePane from being the scene root (since root cannot == null).
TEMP_SCENE.setRoot(TEMP_ROOT);
// Hide the TEMP_STAGE:
TEMP_STAGE.hide();
// Add this ConvertiblePane back to the originalParent:
originalParent.getChildren().add(this);
System.out.println("Transferred to MAIN.");
// Relocate this ConvertiblePane within the originalParent to maintain its position on screen
Point2D parentPt = originalParent.screenToLocal(screenPt);
this.relocate(parentPt.getX(), parentPt.getY());
}
}
As you can see, there is some basic reporting in the event handling methods; and the makeIndependent() and returnToParent() methods output "Transferred to TEMP." and "Transferred to MAIN." respectively.
If I click the mouse on the ConvertiblePane, drag a few pixels, and release it, this is the output:
(on Windows or Mac OS X)
Pressed as Child
Transferred to TEMP.
Dragged as Independent
Dragged as Independent
Dragged as Independent
Released as Independent
Transferred to MAIN.
(on Ubuntu)
Pressed as Child
Transferred to TEMP.
Dragged as Independent
Dragged as Independent
Dragged as Independent
I have also tried adding event filters to the two Scenes; but the result is the same: MouseRelease occurs on Win/Mac, but not Ubuntu.
If anyone can explain this behavior, or suggest something, that would be great. Alternatively... is there any "global" (pre-scene) creation of MouseEvents that I could catch? I mean, I don't really care about the details of the mouse release; I just want an event to tell me when to add the ConvertiblePane back to the main stage.
Thanks!
After spending several weeks on this, I couldn't find a way to fire a proper MouseReleased event on Ubuntu for this situation; however, I did come up with a hack that does the job well enough. Basically, instead of being notified when MouseReleased occurs, I'm checking every 10 milliseconds to see whether the mouse button is no longer down.
Explanation: When the node is transferred to the temporary Scene, the Timeline is started to "move" the mouse pointer in place every 10 milliseconds. This triggers either a MouseDragged event (if the mouse button is still down) or a MouseMoved event (if the mouse button is up); so I can then simulate the MouseReleased event and call my procedure for adding the node back to the main stage. At that point, of course, I also stop the Timeline.
Here's the relevant code to demonstrate this; perhaps it will be of use to someone else as well.
// The robot is needed to "move" the mouse in place,
// triggering a MOUSE_MOVED event.
private static Robot robot = null;
static {
try {
robot = new Robot();
} catch (AWTException ex) {
Logger.getLogger(ConvertiblePane.class.getName()).log(Level.SEVERE, null, ex);
}
}
// clickWaiter will move the mouse in place every 10 milliseconds,
// triggering a MOUSE_MOVED event if the mouse is no longer pressed.
private final Timeline clickWaiter = new Timeline(new KeyFrame(Duration.millis(10), (ActionEvent event) -> {
// Get the mouse position from MouseInfo class.
Point mouse = MouseInfo.getPointerInfo().getLocation();
// "Move" the mouse in place to trigger a mouseMove or mouseDrag event.
robot.mouseMove(mouse.x, mouse.y);
}));
public ConvertiblePane()
{
...
// MOUSE_MOVED will be triggered by the robot when the mouse button is no longer pressed:
TEMP_SCENE.addEventFilter(MouseEvent.MOUSE_MOVED, (MouseEvent event) ->
{
if (!event.isPrimaryButtonDown())
{
System.out.println("Temp.release (sim)");
clickWaiter.stop();
// Simulate MOUSE_RELEASED event.
}
});
}
public void makeIndependent()
{
...
// Start the clickWaiter, part of the Linux hack:
clickWaiter.playFromStart();
}

Javafx- Mouse click event that undoes what the previous mouse click did

So I currently have a 3x3 gridpane of which I add a pane with a white background to each space on the grid.
What I am trying to do is have it so that if a user clicks on one of these spaces, the white pane will change to another color. Then if the user were to click on the space again, the pane would change back to white. If clicked again, then it would change to that color again, and so on.
In short, a click would cause an action, and the next click, and those after it would reverse/undo the previous action.
However, I can only get the initial click to work with this. Everything else I've thought to add to this hasn't worked.
#Override
public void handle(MouseEvent me) {
if (me.getClickCount() == 1) {
pane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));}
Any help would be really appreciated!
If it really has to be a Pane, you could try to write a custom Pane which will make it easier for you to control its behaviour:
class MyPane extends Pane{
private Background standard, other;
public MyPane(){
standard = new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));
other = new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY));
this.setBackground(standard);
}
public void changeColor(){
if(this.getBackground().equals(standard){
this.setBackground(other);
else{
this.setBackground(standard);
}
}
public void setBackground(Background bckgrnd){
this.other = bckgrnd;
}
If you use this class instead of a standard Pane, you are able to control the color changing simply via
#Override
public void handle(MouseEvent me){
myPane.changeColor();
}
If you would use the Rectangleclass, you could use the following code:
#Override
public void handle(MouseEvent me){
if(rectangle.getFill() == standard){
rectangle.setFill(other);
}else{
rectangle.setFill(standard);
}
provided that you defined the 2 Paint variables standard and other, e.g. :
private final Paint standard = Color.WHITE;
private Paint other = Color.RED;
see Color

JavaFX - TextArea, how to activate the textarea only on double click?

JavaFX 2.2 - JDK 1.8.0_121
I have a TextArea inside a rectangle which also happens to have a mouse listener. The problem is that when I click on the TextArea it consumes the event and the rectangle doesn't get the click.
Consider the following code for example:
Group g = new Group();
Rectangle rect = new Rectangle(100,100);
TextArea textArea = new TextArea("Test");
textArea.setTranslateX(rect.getX());
textArea.setTranslateY(rect.getY());
textArea.setMinWidth(rect.getWidth());
textArea.setMinHeight(rect.getHeight());
//Calling a method to add an onMouseClickedProperty() mouse listener to the rectangle
addMouseListener(rect)
g.getChildren().addAll(rect, textArea);
In the above case the TextArea takes as much space as the rectangle so when I click on it the onMouseClickedProperty() event gets consumed by the TextArea.
Is there a way to "disable" or "remove" the onMouseClickedProperty() from the TextArea and instead have it fired when a double click occurs? In hopes that the single mouse click will be consumed by the rectangle instead.
Thanks.
EDIT:
I found a solution that works, it's not perfect but it's much more appropriate than what it was discussed in the comments.
Since you can't prevent the TextArea from consuming the MOUSED_PRESSED event the only way to process an event before TextArea areas does is using event filters.
So using the example code from above where i call the method addMouseListener(rect) instead of using just a mouse listener I'm adding an event filter, and instead of adding that to the rectangular shape I add it to the group.
private void addMouseLisenter(Group group){
group.addEventFilter(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
//Code here
}
});
}
This way both the group and the TextArea get the mouse click.
Note: If you want only the group to get the mouse click you can add event.consume().
I hope that helps someone in the future looking for something similar.
I am pretty sure you cannot get away from having to have the MouseListener unfortunately as that is primary class for all mouse events if you wanted the text area to react at all to mouse it has to have the listener.
as far as detecting double clicks there is another thread that contains the answer that you are seeking answered by Uluk Biy.
there is another answer on there proposed by mipa that might answer your question on detection of difference between single and double click however if the Nodes overlap each other.
EDIT
perhaps in this case it might be worth modifying mipa's answer, try adding this to your code (in applicable areas)
Integer clickCount = 0;
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
ScheduledFuture<?> scheduledFuture;
root.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton().equals(MouseButton.PRIMARY) && clickCount < 1) {
scheduledFuture = executor.schedule(() -> clickAction(), 500, TimeUnit.MILLISECONDS);
}
clickCount += 1;
}
});
private void clickAction() {
if (clickCount == 1) {
//actions for single click
clickCount = 0;
} else if (clickCount > 1) {
//action for multiple clicks
clickCount = 0;
}
}
I found a much more appropriate solution than what was discussed, check EDIT in my question.

Pilling two obj with OnMousePressed on top of each other in javafx

Been trying around and searching but couldn't find any solution, so I finally decided to give up and ask further...
Creating a javafx app, I load tiles in a TilePane.
This tiles are clickable and lead to a details page of their respective content.
On each tile, if they do belong to a certain pack, I do display the pack name, that is also clickable and lead to a page showing that specific pack content.
So that means the container, the tile, that is a Pane is clickable and on top of it I have a Label that is claickable also. What happens is when I do click the Label, it also triggers the Pane onMousePressed()... Here is a part of the tile creation code, the part focused on the onMousePressed(). I tried to make the Pane react by double click and the Label by single, it works, but I want to Pane to open with a single click.
I would be more than thankfull for any ideas how to solve that.
public DownloadTile (Downloadable upload, MainApp mainApp) {
_mainApp = mainApp;
_upload = upload;
_tile = new Pane();
_tile.setPrefHeight(100);
_tile.setPrefWidth(296);
_tile.setStyle("-fx-background-color: #ffffff;");
_tile.setCursor(Cursor.HAND);
}
public void refresh() {
_tile.getChildren().clear();
_tile.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isPrimaryButtonDown() /*&& event.getClickCount() == 2*/) {
_mainApp.showDownloadDialog(dt, _upload);
}
}
});
if (_upload.getPack() != null) {
Label pack = new Label();
pack.setText(_upload.getPack());
pack.getStyleClass().add("pack-link");
pack.setCursor(Cursor.HAND);
pack.relocate(10, 48);
_tile.getChildren().add(pack);
pack.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isPrimaryButtonDown()) {
_mainApp.showPackPage(_upload);
}
}
});
}
}
Your label will receive the mouseclick first (since it's on top), so after you have processed the click, you can stop it from being passed down the chain using 'consume':
pane.setOnMouseClicked(
(Event event) -> {
// process your click here
System.out.println("Panel clicked");
pane.requestFocus();
event.consume();
};

JavaFX Shape.intersect() performance issue

I have start writing a shooting game JavaFX application. I am using Shape.intersect() to check the collision of bullet and the target. Below is my code and I made it simple so as to post here.
public class TestShapeIntersect extends Application{
AnchorPane anchorPane;
ArrayList<Rectangle> targetObjects;
public static void main(String[] arg){
launch(arg);
}
#Override
public void start(Stage stage) throws Exception {
final Rectangle gun = new Rectangle(50, 50, Color.RED);
anchorPane = new AnchorPane();
anchorPane.getChildren().add(gun);
generateTargetObjects(50); // Number of target objects
anchorPane.getChildren().addAll(targetObjects);
gun.setX(50);
gun.setY(200);
Scene scene = new Scene(anchorPane,300,300,Color.GREEN);
stage.setScene(scene);
stage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
Rectangle bullet = new Rectangle(5,10,Color.ORANGE);
bullet.setX(75);
bullet.setY(200);
anchorPane.getChildren().add(bullet);
animateBullet(bullet);
}
});
}
private void generateTargetObjects(int noOfTargetObj) {
targetObjects = new ArrayList<Rectangle>();
for(int i=1; i<=noOfTargetObj;i++){
Rectangle rect = new Rectangle(30, 30, Color.YELLOW);
targetObjects.add(rect);
}
}
void animateBullet(final Rectangle bullet){
Timeline timeline = new Timeline();
timeline.setCycleCount(500);
final KeyFrame kf = new KeyFrame(Duration.millis(2), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-1);
checkCollision(bullet);
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
//This method will check if there is any collision happened between the bullets and the targets.
//If collision happens then both bullet and target object will be disappeared.
void checkCollision(Rectangle bullet){
int noOfTargetObjs = targetObjects.size();
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
targetObjects.get(i).setVisible(false);
bullet.setVisible(false);
}
}
}
}
}
I have not yet aligned the nodes properly.Here the 'gun' rectangle will fire 'bullet' rectangle whenever any key press event is detected.
The problem is for every every first bullet fired in each application session, the very first bullet is not animated properly (means the bullet is not going in it path continuously). But after the first bullet has gone the remaining bullets are animated properly. This performance issue increases with the number of 'target' objects increases.
I have found out that the issue is because of this line:
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
Could anyone let me know why this happens and what could be the solution to resolve this issue? Or is it because of the way that I'm implementing?
I experienced a different behaviour when i executed your application. My first shot was moving fine without any interruptions in the translation. But after several shots the application began to slow down. I tried to improve the performance of your code by doing the following steps:
void animateBullet(final Rectangle bullet){
final Timeline timeline = new Timeline();
timeline.setCycleCount(125); //changed
final KeyFrame kf = new KeyFrame(Duration.millis(16), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
bullet.setY(bullet.getY()-8); //changed
checkCollisionThreaded(bullet); //changed
//added
if(bullet.getX() < 0 || bullet.getX() > bullet.getParent().getBoundsInParent().getWidth()
|| bullet.getY() < 0 || bullet.getY() > bullet.getParent().getBoundsInParent().getHeight())
{
bullet.setVisible(false);
timeline.stop();
AnchorPane ap = (AnchorPane) bullet.getParent();
ap.getChildren().remove(bullet);
}
}
});
timeline.getKeyFrames().add(kf);
timeline.play();
}
Your value for the Duration.millis factor in the KeyFrame was 2, which is not really necessary to run a fluent animation, because JavaFX has a fixed framerate of 60 frame per second, which means that every 16,7 milliseconds a new frame is rendered and displayed. So you can use 16ms as frame-duration without making the animation stutter.
The if-statement checks if the bullet is outside the visible screen, which could happen in your previous code. Non-visible nodes should be removed from the scene graph. It doesn't help if you set a node to setVisible(false), because the node will stay on the scene graph. The Timeline animation should also be stopped, because it would trigger new checkCollision calls. As you can see, i changed the method checkCollision to checkCollisionThreaded. The method is shown below.
public void checkCollisionThreaded(final Rectangle bullet)
{
final int noOfTargetObjs = targetObjects.size();
Task<Integer> t = new Task<Integer>()
{
#Override
protected Integer call() throws Exception
{
for(int i=0; i<noOfTargetObjs;i++)
{
if(targetObjects.get(i).isVisible()==true && bullet.isVisible()==true){
Shape intersectShape= Shape.intersect(bullet, targetObjects.get(i));
if(intersectShape.getBoundsInLocal().getWidth() != -1){
return i;
}
}
}
return -1;
}
#Override
protected void succeeded()
{
super.succeeded();
if(this.getValue().intValue() != -1)
{
Node obj = targetObjects.get(this.getValue().intValue());
obj.setVisible(false);
AnchorPane ap = (AnchorPane) obj.getParent();
ap.getChildren().remove(obj);
targetObjects.remove(this.getValue().intValue());
bullet.setVisible(false);
}
}
};
Thread thread = new Thread(t);
thread.start();
}
There are some violations against the rule "don't touch any objects on the scene graph with a thread different to the JavaFX application thread", but as far as i can see, only reading methods access the scene graph (and it's objects) in the call() method. This method is run on a new Thread, which improves performance. The method succeeded() is run on the JavaFX Application Thread, so that we can safely remove things from our scene graph. I assumed that you want to remove your targets from the scene once they were hit.
It should be said that there might be issues related to the multithreaded code. There could be errors when getting final int noOfTargetObjs = targetObjects.size(); while modifying it on another thread. I left out any synchronization to reduce the complexity of the code.
My guess is that you're sending way too many requests to Shape.intersect(...), which is probably a fairly expensive method to execute. Initially this is causing performance problems, but when the number of calls to the method hits some threshold, the JVM's JIT compiler kicks in and compiles that method, relieving some of the problems. (Again, this is all guesswork.)
Using a TranslateTransition for the bullet and listening to its boundsInParent property to check for collisions seems to work better. I think the reason is that using this technique only checks for collisions when the JavaFX machinery actually moves the bullet. In your code you are performing these checks much more often.
Here's an example.

Categories