How can I animate a circle with PlayN? - playn

This is a follow up to my last question:
How can I draw a circle to the screen with PlayN?
For my simple case, I want to programmatically create a single colored circle and move it across a 2-D plain (doesn't need to use box2d lib).
A real-world example would likely involve animating several circles. Two real-world examples for this case (sorry, I had to remove the links -- not enough karma!):
Browsmos for Chrome
Ants AI Challenge
It was suggested in response to my last question that I would want to use the ImmediateLayer class, so I am looking to understand how to properly incorporate this into my game loop.
Here's is my code sample:
public class SimpleCircleAnimation implements Game {
// Surface
private GroupLayer rootLayer;
private ImmediateLayer surface;
private Canvas canvas;
private Circle circle;
private CanvasImage circleImage;
#Override
public void init() {
// create root layer
rootLayer = graphics().rootLayer();
// a simple circle object
int circleX = 0; int circleY = 0;
int circleRadius = 20;
circle = new Circle(circleX, circleY, circleRadius);
// create an immediate layer and add to root layer
ImmediateLayer circleLayer = graphics().createImmediateLayer(new ImmediateLayer.Renderer() {
public void render (Surface surf) {
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circle.radius, circle.radius, circle.radius);
surf.drawImage(circleImage, circle.x, circle.y);
}
});
rootLayer.add(circleLayer);
}
#Override
public void paint(float alpha) {
}
#Override
public void update(float delta) {
// move circle
int newX = circle.x + 4; int newY = circle.y + 4;
circle.setPoint(newX, newY);
}
#Override
public int updateRate() {
return 25;
}
}
This successfully moves the circle diagonally down the screen from left to right. A couple questions:
Is this implemented properly?
In the case of multiple animated circles, is the idea with ImmediateLayer that you would create a circle image for each circle within the Renderer callback? Or would you perhaps create an Immediate Layer for each circle and add those to the root layer?

I would not use ImmediateLayer wiht render (Surface surf) adapter. Here u have, inside the render method creation of an image
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
just put this in the paint method
surf.drawImage(circleImage, circle.x, circle.y);
using the normal layer and u should be fine
Painting is done in paint method, and do not put calculations there
Update is for calculations, and physics oriented stuff

I discovered a detailed practical example of ImmediateLayer usage in the Cute Game source within the PlayN Samples:
CuteGame.java (code.google.com)

Related

Preventing overlapping shapes while dragging on a Pane

I've looked at similar questions but they all are concerned with collision detection rather than preventing overlap. I've gotten most of it to work with the below code:
private final EventHandler<MouseEvent> onPress = mouseEvent -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : getAllShapes()) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
However, the problem is, once there is a tiniest bit of overlap, the Shape is no longer draggable at all. Meaning, if I drag a shape to another, once they become essentially tangent, neither of them are draggable anymore. What I want to happen is just that, for example, if you try to drag a circle onto another, the circle won't follow the mouse position as long as the future position of the drag will cause an overlap.
I can't figure out exactly how to accomplish this.
EDIT: Minimum Reproducible Example:
Main.java
public class Main extends Application {
static Circle circle1 = new DraggableCircle(100, 200);
static Circle circle2 = new DraggableCircle(200, 300);
static Circle[] circleList = new Circle[]{circle1, circle2};
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World");
Pane pane = new Pane();
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
pane.getChildren().addAll(circle1, circle2);
}
public static void main(String[] args) {
launch(args);
}
}
DraggableCircle.java
public class DraggableCircle extends Circle {
private double xDrag, yDrag;
public DraggableCircle(double x, double y) {
super(x, y, 30);
this.setFill(Color.WHITE);
this.setStroke(Color.BLACK);
this.setStrokeWidth(1.5);
this.addEventHandler(MouseEvent.MOUSE_PRESSED, onPress);
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDrag);
}
private final EventHandler<MouseEvent> onPress = (mouseEvent) -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
}
This also has an issue where dragging too quickly causes a noticeable overlap between the circles, before the drag detection ends.
A simple (imperfect) solution
The following algorithm will allow a node to continue to be dragged after an intersection has occurred:
Record the current draggable shape position.
Set the new position.
Check the intersection.
If an intersection is detected, reset the position to the original position.
An implementation replaces the drag handler in the supplied minimal example code from the question.
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
this.setCenterX(priorCenterX);
this.setCenterY(priorCenterY);
return;
}
}
}
};
This handler does work better than what you had, it does at least allow you to continue dragging after the intersection.
But, yes, if you drag quickly it will leave a visible space between nodes when it has detected that the drag operation would cause an intersection, which isn't ideal.
Also, the additional requirement you added in your comment about having the dragged shape glide along a border would require a more sophisticated solution.
Other potential solutions
I don't offer code for these more sophisticated solutions here.
One potential brute force solution is to interpolate the prior center with the new center and then, in a loop, slowly move the dragged object along the interpolated line until an intersection is detected, then just back it out to the last interpolated value to prevent the intersection. You can do this by calculating and applying a normalized (1 unit distance) movement vector. That might fix space between intersected nodes.
Similarly to get the gliding, on the intersection, you could just update either the interpolated x or y value rather than both.
There may be more sophisticated methods with geometry math applied, especially if you know shape geometry along with movement vectors and surface normals.
+1 for #jewelsea answer.
On top of #jewelsea answer, I would like to provide a fix for the "space between nodes" issue.
So you might have already observed that when you drag fast, it will not cover each and every pixel in the drag path. It varies with the speed of the drag. So when you decide to move it to the previous recorded point, we will do a quick math, to see if there is any gap between the two nodes, if yes:
We will do a math "to determine a point along a line which is at distance d" and move the drag circle to that point. Here..
start point of line is : previous recorded point
end point of line is : the intersected shape center
d is : the gap between the two shapes.
So the updated code to the #jewelsea answer is as below:
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Circle shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
Point2D cx = new Point2D(priorCenterX, priorCenterY);
Point2D px = new Point2D(shape.getCenterX(), shape.getCenterY());
double d = cx.distance(px);
if (d > getRadius() + shape.getRadius()) {
cx = pointAtDistance(cx, px, d - getRadius() - shape.getRadius());
}
this.setCenterX(cx.getX());
this.setCenterY(cx.getY());
return;
}
}
}
};
private Point2D pointAtDistance(Point2D p1, Point2D p2, double distance) {
double lineLength = p1.distance(p2);
double t = distance / lineLength;
double dx = ((1 - t) * p1.getX()) + (t * p2.getX());
double dy = ((1 - t) * p1.getY()) + (t * p2.getY());
return new Point2D(dx, dy);
}

Detecting collision of Node in one StackPane with Node in another StackPane

Just as a preface, I am very new to programming in JavaFX. (We had an introduction to JavaFX in one of my classes last semester, and for the last month or so I've been working on making a simple game in JavaFX.)
An issue I've run into is trying to detect the collision of a Pane within one StackPane with the Pane inside another StackPane. Specifically, I have a "Player" node in the Game class ("Player" extends abstract "Sprite" which extends StackPane) along with some "Asset" nodes ("Asset" being an abstract parent class that like "Sprite" also extends StackPane). Both "Player" and every "Asset" node are comprised of an ImageView and Pane objects that are to be the "boundaries" of the node.
Here's how I'm attempting to track collisions in the Game class, but it's not working:
protected void update() {
// Call playerBoundsHandler in update cycle
for (Asset asset : this.gameArea.getAssets()) {
playerBoundsHandler(asset);
} // for
} // update
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
if (player.getPlayerStandingAreaBox()
.getBoundsInParent()
.intersects(boundary.getBoundsInParent())) {
// do stuff here
} // if
} // for
} // playerBoundsHandler
I'm guessing there's something wrong with using getBoundsInParent() here since I'm trying to track the intersection of child nodes within two separate nodes, but I have no idea what the solution is. Is there something I need to do with getBoundsInLocal or some other method?
Here's the relevant part of the Player class for clarification's sake:
/**
* Player class constructor.
* Player class extends "Sprite" (abstract class)
* which extends StackPane.
*/
public Player(double xSpawn, double ySpawn) {
// Add Player Standing Box (a Pane situated at the feet of the Player sprite)
this.playerStandingAreaBox = new Pane();
// width, height, etc. set here
this.getChildren().add(playerStandingAreaBox);
this.setAlignment(playerStandingAreaBox, Pos.BOTTOM_CENTER);
} // Player constructor
public Pane getPlayerStandingAreaBox() {
return this.playerStandingAreaBox;
} // getPlayerStandingAreaBox
The Asset child classes follow a design almost identical to the Player class here. In case it's also needed for clarification, here's the "Highway" class:
public class Highway extends Asset {
public Highway(double translateX, double translateY) {
// call super here
setAssetBoundaries();
} // Highway constructor
#Override
setAssetBoundaries() {
Pane boundaryOne = new Pane();
// set boundaryOne settings
this.getChildren().add(boundaryOne);
this.assetBoundaries.add(boundaryOne);
Pane boundaryTwo = new Pane();
// set boundaryTwo settings
this.getChildren().add(boundaryTwo);
this.assetBoundaries.add(boundaryTwo);
} // setAssetBoundaries
/**
* assetBoundaries is an ArrayList<Asset> object also inherited.
* getAssetBoundaries() is inherited from the "Asset" class
* which returns assetBoundaries.
*/
The screenshot below shows my Player sprite (don't judge the awful pixel art! I already know the guy's right arm looks janky and the rifle looks ridiculous!) with his standing box highlighted in red, and the boundaries of a "Highway" Asset highlighted in yellow at both the very top and very bottom. I want to register when the Player's box intersects one of the boxes of the Highway.
Player and Highway
Thanks, James_D. The following change does exactly what I want it to do.
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
Bounds boundaryBoundsInScene = boundary.localToScene(boundary.getBoundsInLocal());
Bounds playerStandingBoxBoundsInScene = player.getPlayerStandingBoxArea()
.localToScene(player.getPlayerStandingBoxArea().getBoundsInLocal());
if (boundaryBoundsInScene.intersects(playerStandingBoundsInScene)) {
// do stuff here
} // if
} // for
} // playerBoundsHandler

Button click to execute a code repeatedly in Libgdx

I've developed an Android application using Libgdx in 3D space to render some buildings with interaction buttons to help user navigating in the environment. For example, left and right buttons to move the camera in left and right direction. While pushing a button causes to execute the code once, I've used this trick to keep executing the code as the user holding the button down.
private void createStage() {
stage = new Stage();
intervalTime = 15L;
buttonLeft = new TextButton("", leftStyle);
buttonLeft.addListener(new InputListener() {
// repeat an action with ScheduledExecutorService
final Runnable leftRunnable = new Runnable() {
#Override
public void run() {
Vector3 dir = new Vector3();
dir.fromString(cam.direction.toString()).scl(0.5f);
cam.position.add(dir.z, 0, -dir.x); // camera moves to left
cam.update();
}
};
// add on thread to object
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> future; // future schedule to run and stop task
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
future = executor.scheduleAtFixedRate(leftRunnable, 0L, intervalTime, TimeUnit.MILLISECONDS);
return true;
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
super.touchUp(event, x, y, pointer, button);
//leftFlag = false;
if (future != null) {
future.cancel(true);
}
}
});
This method is invoked in the create() function of AndroidListener and the stage will be drawn in render() function as well. There are about 12 buttons which use the same approach but it gives me some lagging in rendering process while the user holds the buttons down for seconds or pushing two buttons simultaneously. Is there something wrong with the approach or is it an appropriate structure for executing a code frequently?
Camera is not a thread-safe class, so you would need to use synchronization if modifying it from your background thread.
That said, updating a camera is a trivial operation, so multi-threading is adding a lot of needless complexity. You're generating a fair amount of garbage, although I don't know if that's the only reason you're seeing some lagging.
Here's how I'd do it more simply.
stage = new Stage();
float camSpeed = 0.5f / 15; // Units per ms
float camDisp = camSpeed * Gdx.graphics.getDeltaTime();
buttonLeft = new TextButton("", leftStyle){
public void act(float delta){
super.act(delta);
if (isPressed()){
camera.position.add(camera.direction.z * camDisp,
0,
-camera.direction.x * camDisp);
camera.update();
}
}
}
Not quite sure what you're doing with the camera direction, but I tried to copy the same behavior. If I just wanted to pan the camera to the left, I'd do it like this. The temp variable is to avoid instantiating objects and triggering GC.
private static final Vector3 TMP = new Vector3();
//...
TMP.set(camera.direction).crs(camera.up); // right vector of camera
camera.position.add(TMP.scl(-camDisp));

JavaFX 2.X - Animated background and animated controls

A few days ago I started studying JavaFX, and came across the desire to perform 2 experiments. Firstly, I would like to know if it is possible to put an animated background behind an user interface. I've succeeded in creating an animated background, and now I'm having great difficulties to position some controls in the middle of my interface.
I'd like to introduce you 2 pictures of my program. The first demonstrates the undesirable result that I'm getting:
I believe this is my nodes tree:
This is the code of my application:
public class AnimatedBackground extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
private Group root;
private Group grp_hexagons;
private Rectangle rect_background;
private Scene cenario;
// UI
private VBox lay_box_controls;
private Label lab_test;
private TextArea texA_test;
private Button bot_test;
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage stage) throws Exception
{
this.confFX();
cenario = new Scene(this.root , 640 , 480);
this.rect_background.widthProperty().bind(this.cenario.widthProperty());
this.rect_background.heightProperty().bind(this.cenario.heightProperty());
stage.setScene(cenario);
stage.setTitle("Meu programa JavaFX - R.D.S.");
stage.show();
}
protected void confFX()
{
this.root = new Group();
this.grp_hexagons = new Group();
// Initiate the circles and all animation stuff.
for(int cont = 0 ; cont < 15 ; cont++)
{
Circle circle = new Circle();
circle.setFill(Color.WHITE);
circle.setEffect(new GaussianBlur(Math.random() * 8 + 2));
circle.setOpacity(Math.random());
circle.setRadius(20);
this.grp_hexagons.getChildren().add(circle);
double randScale = (Math.random() * 4) + 1;
KeyValue kValueX = new KeyValue(circle.scaleXProperty() , randScale);
KeyValue kValueY = new KeyValue(circle.scaleYProperty() , randScale);
KeyFrame kFrame = new KeyFrame(Duration.millis(5000 + (Math.random() * 5000)) , kValueX , kValueY);
Timeline linhaT = new Timeline();
linhaT.getKeyFrames().add(kFrame);
linhaT.setAutoReverse(true);
linhaT.setCycleCount(Animation.INDEFINITE);
linhaT.play();
}
this.rect_background = new Rectangle();
this.root.getChildren().add(this.rect_background);
this.root.getChildren().add(this.grp_hexagons);
// UI
this.lay_box_controls = new VBox();
this.lay_box_controls.setSpacing(20);
this.lay_box_controls.setAlignment(Pos.CENTER);
this.bot_test = new Button("CHANGE POSITIONS");
this.bot_test.setAlignment(Pos.CENTER);
this.bot_test.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
for(Node hexagono : grp_hexagons.getChildren())
{
hexagono.setTranslateX(Math.random() * cenario.getWidth());
hexagono.setTranslateY(Math.random() * cenario.getHeight());
}
}
});
this.texA_test = new TextArea();
this.texA_test.setText("This is just a test.");
this.lab_test = new Label("This is just a label.");
this.lab_test.setTextFill(Color.WHITE);
this.lab_test.setFont(new Font(32));
this.lay_box_controls.getChildren().add(this.lab_test);
this.lay_box_controls.getChildren().add(this.texA_test);
this.lay_box_controls.getChildren().add(this.bot_test);
this.root.getChildren().add(this.lay_box_controls);
}
}
I've tried to make the use of a StackPane as the root of my scene graph, but also found an undesired result. Despite the controls have stayed in the center of the window, the circles begin to move in as they grow and shrink, making it appear that everything is weird.
The second thing I would like to know is if it is possible to customize the controls so they perform some animation when some event happens. Although we can change the appearance of controls using CSS, it's harder to create something complex. For example, when a control changes its appearance due to a change of state, the transition state change is not made in an animated way, but in an abrupt and static way. Is there a way to animate, for example, a button between its states? This would be done using the JavaFX API? Or would that be using CSS? Or would not be possible in any way?
Thank you for your attention.
after much struggle, I and some users of the Oracle community could resolve this issue. I see no need to repeat here all the resolution made ​​by us, so I'll post the link so you can access the solution of the problem. I hope this benefits us all. Thanks for your attention anyway.
https://community.oracle.com/thread/2620500

Qt painted content goes lost

I am writing an info-screen program. I created a full-screen widget and draw contents onto it.
In order to extend the life cycle of the TFT-display device, I want to implement a pixel-shifting feature. With other words, in every X minutes, I shift the screen to left/right/top/down for Y pixels.
My approach is as follows:
I use two layers (two QWidget).
I paint contents on the top layer.
When a pixel-shifting is performed, I just move the top layer for specified offset.
And then fill a background color to the bottom layer.
However, I found a problem:
If I move up the top layer for 10 pixels, the 10-pixel-content goes out of the screen. But when I move this layer down for 10 pixels. The 10-pixel-content will not be updated, it is gone.
How can I keep these 10-pixel-content? Is there any magic widget flag to solve this problem?
UPDATE 1:
The code is written in language D, but it is easy to understand:
class Canvas: QWidget
{
private QPixmap content;
this(QWidget parent)
{
super(parent);
setAttribute(Qt.WA_OpaquePaintEvent, true);
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.content = content;
update(region);
}
protected override void paintEvent(QPaintEvent event)
{
if (this.content !is null)
{
QPainter painter = new QPainter(this);
painter.setClipping(event.region);
painter.fillRect(event.region.boundingRect, new QColor(0, 0, 0));
painter.drawPixmap(event.region.rect, this.content);
this.content = null;
painter.setClipping(false);
}
}
}
class Screen: QWidget
{
private Canvas canvas;
this()
{
super(); // Top-Level widget
setAutoFillBackground(True);
this.canvas = new Canvas(this);
showFullScreen();
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.canvas.requestForPaint(content, region);
}
private updateBackgroundColor(QColor backgroundColor)
{
QPalette newPalette = palette();
newPalette.setColor(backgroundRole(), backgroundColor);
setPalette(newPalette);
}
public shiftPixels(int dx, int dy)
{
this.canvas.move(dx, dy);
updateBackgroundColor(new QColor(0, 0, 0)); // Just a demo background color
}
}
Screen screen = new Screen;
screen.requestForPaint(some_content, some_region);
screen.shiftPixels(0, -10);
screen.shiftPixels(0, 10);
Looking at the code, my first guess is that your region might be wrong. Try repainting the whole widget each time, and see if that solves the missing 10 pixel problem. If it does, then try working out why your region isn't covering the newly exposed portion.
One possibility along those lines: I notice in your Screen::requestForPaint method that you directly call the Canvas::requestForPaint without doing anything with the region. In Qt, the coordinates for anything like that are often assumed to be local, so if you don't account for the current position of the canvas widget, you might get an incorrect region.
Why not setting the position of the widget directly...? Another options might be using QPainter::translate(-1,-1) or something similar.

Resources