I am making an application and need a ball to bounce under gravity. The ball bounces fine but it never stops.
I tried printing the coordinate of the points where it stopped and what was the velocity. This is the output of one of the cases:
Found it... line 24. (0.00)i + (-0.01)j {14.14(0.79)} (-1.79, 651.57)
So in the ball stopped at height 651.67 while the bound was 600. Here's another case:
Found it... line 24. (0.00)i + (-0.01)j {14.14(0.79)} (-1.79, 1624.58)
Here's the code:
GUI.java
import java.util.Timer;
import java.util.TimerTask;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.stage.Stage;
public class GUI extends Application {
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Bouncy Ball");
Group root = new Group();
Scene theScene = new Scene(root);
theStage.setScene(theScene);
Canvas canvas = new Canvas(750, 600);
root.getChildren().add(canvas);
CircleSprite sprite = new CircleSprite(30, new Point(50, 50));
root.getChildren().add(sprite.image);
sprite.setVelocity(new Vector(new Point(10, 10)));
theStage.show();
theStage.setOnCloseRequest(e -> {
System.exit(0);
});
AnimationTimer gameLoop = new AnimationTimer() {
#Override
public void handle(long now) {
if (sprite.update(new Bounds(700, 600))) {
this.stop();
}
}
};
gameLoop.start();
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Vector velocity = sprite.getVelocity();
velocity.setYComponent(velocity.getYComponent() + 1);
if (Math.abs(sprite.getVelocity().getYComponent()) <= 0.01
&& sprite.getCentre().getY() + 2 * sprite.getRadius() >= 500) {
System.out.println("Found it... line 55");
cancel();
}
}
}, 0, 100);
}
public static void main(String[] args) {
launch(args);
}
}
CircleSprite.java
public class CircleSprite extends Circle {
public javafx.scene.shape.Circle image;
public CircleSprite(long radius, Point centre) {
super(radius, centre);
image = new javafx.scene.shape.Circle(centre.getX(), centre.getY(), radius);
}
public boolean update(Bounds bounds) {
Point pos = this.getCentre();
Vector velocity = this.getVelocity();
Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
image.setLayoutX(finalPos.getX());
image.setLayoutY(finalPos.getY());
setCentre(finalPos);
if (finalPos.getX() <= 0 || finalPos.getX() + 2 * getRadius() >= bounds.maxX) {
velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
}
if (finalPos.getY() <= 0 || finalPos.getY() + 2 * getRadius() >= bounds.maxY) {
velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
}
if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
System.out.println("Found it... line 24" + velocity + " " + getCentre());
return true;
}
return false;
}
}
Bounds.java
public class Bounds {
public double maxX;
public double maxY;
public Bounds(double maxX, double maxY) {
this.maxX = maxX;
this.maxY = maxY;
}
}
Circle.java
public class Circle extends Shape {
private long radius;
private Point centre;
public Circle(long radius, Point centre) {
this.radius = radius;
this.centre = centre;
this.setVelocity(new Vector(new Point(0.0d, 0.0d), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setAcceleration(new Vector(new Point(0, 0), new Point(0, 0)));
}
/*
* Testing for whether or not two circles intersect is very simple: take the
* radii of the two circles and add them together, then check to see if this sum
* is greater than the distance between the two circles.
*/
public boolean isColliding(Circle a, Circle b) {
long dist = a.getRadius() + b.getRadius();
// In general multiplication is a much cheaper operation than taking the square
// root of a value.
return ((dist * dist) < (a.getCentre().getX() - b.getCentre().getX())
* (a.getCentre().getX() - b.getCentre().getX())
+ (a.getCentre().getY() - b.getCentre().getY()) * (a.getCentre().getY() - b.getCentre().getY()));
}
public long getRadius() {
return this.radius;
}
public void setRadius(long radius) {
this.radius = radius;
}
public Point getCentre() {
return this.centre;
}
public void setCentre(Point centre) {
this.centre = centre;
}
}
Shape.java
public class Shape {
private Vector velocity;
private Vector acceleration;
private long mass;
private double invMass;
private float restitution;
public Vector getAcceleration() {
return this.acceleration;
}
public void setAcceleration(Vector acceleration) {
this.acceleration = acceleration;
}
public long getMass() {
return this.mass;
}
public void setMass(long mass) {
this.mass = mass;
this.setInvMass(mass);
}
public Vector getVelocity() {
return this.velocity;
}
public void setVelocity(Vector velocity) {
this.velocity = velocity;
}
public double getInvMass() {
return this.invMass;
}
private void setInvMass(long mass) {
if (mass == 0) {
invMass = Long.MAX_VALUE;
return;
}
this.invMass = 1.0d / (double) mass;
}
public float getRestitution() {
return this.restitution;
}
public void setRestitution(float restitution) {
this.restitution = restitution;
}
}
Vector.java
public class Vector {
private Point p1;
private Point p2;
private double xComponent;
private double yComponent;
private double angle;
private double magnitude;
/*
* The constructor makes a vector crossing through two points p1 and p2.
*
* #param p1 The source point(x1, x2)
*/
public Vector(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(Point p2) {
Point p1 = new Point(0, 0);
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(double magnitude, Vector unitVector) {
scaledProduct(magnitude, unitVector);
}
private void scaledProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
new Vector(point);
}
public static Vector scalarProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
return new Vector(point);
}
public static double dotProduct(Vector v1, Vector v2) {
return (v1.xComponent * v2.xComponent + v1.yComponent * v2.yComponent);
}
public static Vector sum(Vector v1, Vector v2) {
return new Vector(new Point(v1.getXComponent() + v2.getXComponent(), v1.getYComponent() + v2.getYComponent()));
}
public static Vector difference(Vector from, Vector vector) {
return new Vector(new Point(from.getXComponent() - vector.getXComponent(),
from.getYComponent() - vector.getYComponent()));
}
public static double angleBetween(Vector v1, Vector v2) {
return Math.acos(Vector.dotProduct(v1, v2) / (v1.getMagnitude() * v2.getMagnitude()));
}
public Point getP1() {
return this.p1;
}
public void setP1(Point p1) {
this.p1 = p1;
}
public Point getP2() {
return this.p2;
}
public void setP2(Point p2) {
this.p2 = p2;
}
public double getXComponent() {
return this.xComponent;
}
public void setXComponent(double d) {
this.xComponent = d;
}
public double getYComponent() {
return this.yComponent;
}
public void setYComponent(double d) {
this.yComponent = d;
}
public double getAngle() {
return this.angle;
}
public void setAngle(double angle) {
this.angle = angle;
}
public double getMagnitude() {
return this.magnitude;
}
public void setMagnitude(double length) {
this.magnitude = length;
}
#Override
public boolean equals(Object v) {
Vector vector = (Vector) v;
return ((this.xComponent == vector.xComponent) && (this.yComponent == vector.yComponent));
}
#Override
public String toString() {
return String.format("(%.2f)i + (%.2f)j {%.2f(%.2f)}", this.xComponent, this.yComponent, this.magnitude,
this.angle);
}
}
Point.java
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public static double distance(Point p1, Point p2) {
return Math.sqrt(
(p1.getX() - p2.getX()) * (p1.getX() - p2.getX()) + (p1.getY() - p2.getY()) * (p1.getY() - p2.getY()));
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
#Override
public String toString() {
return String.format("(%.2f, %.2f)", this.x, this.y);
}
}
What's wrong with the code? I know something is wrong with the exit condition in the GUI.java and CircleSprite.java but can't figure out what. There's two thing that is bothering me:
1. Why is the height out of bound when it stops?
2. Why does the x goes negative?
What happens here is the following (considering x dimension only; same applies to y as well):
The ball passes through the bounds at a speed v. You reverse the speed and decrease its magnitude. This can result in the new velocity not being high enough to get back into the bounds in the next update step and you reverse the velocity again decreasing it even more. This results in the ball moving back and forth in smaller and smaller steps outside of the bounds effectively giving the impression of it stopping.
Example with values:
Frame 1
vx = -16
x = 1
Frame 2
vx = 12
x = -15
Frame 3
vx = -9
x = -3
Frame 4
vx = -6.75
x = -12
...
There are 2 ways of fixing this:
Only update the speed, if the ball is moving in the direction that made it exceede the bounds.
public boolean update(Bounds bounds) {
Point pos = this.getCentre();
Vector velocity = this.getVelocity();
Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
image.setLayoutX(finalPos.getX());
image.setLayoutY(finalPos.getY());
setCentre(finalPos);
if ((finalPos.getX() <= 0 && velocity.getXComponent() < 0) || (finalPos.getX() + 2 * getRadius() >= bounds.maxX && velocity.getXComponent() > 0)) {
velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
}
if ((finalPos.getY() <= 0 && velocity.getYComponent() < 0) || (finalPos.getY() + 2 * getRadius() >= bounds.maxY && velocity.getYComponent() > 0)) {
velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
}
if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
System.out.println("Found it... line 24" + velocity + " " + getCentre());
return true;
}
return false;
}
Prevent the ball from ending up outside of the bounds in the first place
public boolean update(Bounds bounds) {
Point pos = this.getCentre();
Vector velocity = this.getVelocity();
Point finalPos = new Point(pos.getX() + velocity.getXComponent(), pos.getY() + velocity.getYComponent());
boolean invertX = true;
if (finalPos.getX() <= 0) {
// mirror on left
finalPos.setX(-finalPos.getX());
} else if (finalPos.getX() + 2 * getRadius() >= bounds.maxX) {
// mirror on right
finalPos.setX(2 * (bounds.maxX - 2 * getRadius()) - finalPos.getX());
} else {
invertX = false;
}
if (invertX) {
velocity.setXComponent(velocity.getXComponent() * (-1) * 0.75);
}
boolean invertY = true;
if (finalPos.getY() <= 0) {
// mirror on top
finalPos.setY(-finalPos.getY());
} else if (finalPos.getY() + 2 * getRadius() >= bounds.maxY) {
// mirror on bottom
finalPos.setY(2 * (bounds.maxY - 2 * getRadius()) - finalPos.getY());
} else {
invertY = false;
}
if (invertY) {
velocity.setYComponent(velocity.getYComponent() * (-1) * 0.75);
}
setCentre(finalPos);
image.setLayoutX(finalPos.getX());
image.setLayoutY(finalPos.getY());
if (Math.abs(velocity.getYComponent()) <= 0.01 && getCentre().getY() + 2 * getRadius() >= bounds.maxY) {
System.out.println("Found it... line 24" + velocity + " " + getCentre());
return true;
}
return false;
}
Related
!! This is not my code - I am using this to learn !! However, I do not understand what might cause the sprite to stop it's directional movement when colliding with the "STONE" object. The entire 2-D Game will run and the sprite works properly, but when I move close to the end of the screen or too close to the stone, I get stuck in place. I could not find an explanation I understood completely. I apologize in advance if this is a trivial question to anyone.
Basic Solid Tile Class:
public class BasicSolidTile extends BasicTile {
public BasicSolidTile(int id, int x, int y, int tileColour) {
super(id, x, y, tileColour);
this.solid = true;
}
}
Tile Class:
public abstract class Tile {
public static final Tile[] tiles = new Tile[256];
public static final Tile VOID = new BasicSolidTile(0, 0, 0, Colours.get(000, -1, -1, -1));
public static final Tile STONE = new BasicSolidTile(1, 1, 0, Colours.get(-1, 333, -1, -1));
public static final Tile GRASS = new BasicTile(2, 2, 0, Colours.get(-1, 131, 141, -1));
protected byte id;
protected boolean solid;
protected boolean emitter;
public Tile(int id, boolean isSolid, boolean isEmitter) {
this.id = (byte) id;
if (tiles[id] != null) {
throw new RuntimeException("Duplicate tile id on" + id);
}
this.solid = isSolid;
this.emitter = isEmitter;
tiles[id] = this;
}
public byte getId() {
return id;
}
public boolean isSolid() {
return solid;
}
public boolean isEmitter() {
return emitter;
}
public abstract void render(Screen screen, Level level, int x, int y);
}
Mob Class:
public abstract class Mob extends Entity {
protected String name;
protected int speed;
protected int numSteps = 0;
protected boolean isMoving;
protected int movingDir = 1;
protected int scale = 1;
public Mob(Level level, String name, int x, int y, int speed) {
super(level);
this.name = name;
this.x = x;
this.y = y;
this.speed = speed;
}
public void move(int xa, int ya) {
// you want to check if they are not zero
if (xa != 0 && ya != 0) {
move(xa, 0);
move(0, ya);
numSteps--;
return;
}
numSteps++;
if (!hasCollided(x, y)) {
if (ya < 0)
movingDir = 0;
if (ya > 0)
movingDir = 1;
if (xa < 0)
movingDir = 2;
if (xa > 0)
movingDir = 3;
x += xa * speed;
y += ya * speed;
}
}
public abstract boolean hasCollided(int xa, int ya);
protected boolean isSolidTile(int xa, int ya, int x, int y) {
if (level == null) {
return false;
}
Tile lastTile = level.getTile((this.x + x) >> 3, (this.y + y) >> 3);
Tile newTile = level.getTile((this.x + x + xa) >> 3, (this.y + y + ya) >> 3);
if (!lastTile.equals(newTile) && newTile.isSolid()) {
return true;
}
return false;
}
public String getName() {
return name;
}
}
Level Class:
public class Level {
// array of id's
private byte[] tiles;
public int width;
public int height;
public List<Entity> entities = new ArrayList<Entity>();
public Level(int width, int height) {
tiles = new byte[width * height];
this.width = width;
this.height = height;
this.generateLevel();
}
public void generateLevel() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x * y %10 < 7) {
tiles[x + y * width] = Tile.GRASS.getId();
} else {
tiles[x + y * width] = Tile.STONE.getId();
}
}
}
}
public void tick() {
// loop through all the vars if you dont need an index var
for (Entity e : entities) {
e.tick();
}
}
public void renderTiles(Screen screen, int xOffset, int yOffset) {
if (xOffset < 0)
xOffset = 0;
if (xOffset > ((width << 3) - screen.width))
xOffset = ((width << 3) - screen.width);
if (yOffset < 0)
yOffset = 0;
if (yOffset > ((height << 3) - screen.height))
yOffset = ((height << 3) - screen.height);
screen.setOffset(xOffset, yOffset);
for (int y = (yOffset >> 3); y < (yOffset + screen.height >> 3) + 1; y++) {
for (int x = (xOffset >> 3); x < (xOffset + screen.width >> 3) + 1; x++) {
getTile(x, y).render(screen, this, x << 3, y << 3);
}
}
}
public void renderEntities(Screen screen) {
for (Entity e : entities) {
e.render(screen);
}
}
public Tile getTile(int x, int y) {
if (0 > x || x >= width || 0 > y || y >= height)
return Tile.VOID;
return Tile.tiles[tiles[x + y * width]];
}
public void addEntity(Entity entity) {
this.entities.add(entity);
}
}
Player Class:
public class Player extends Mob {
private InputHandler input;
private int colour = Colours.get(-1, 111, 145, 543);
private int scale = 1;
public Player(Level level, int x, int y, InputHandler input) {
super(level, "Player", x, y, 1);
this.input = input;
}
public void tick() {
int xa = 0;
int ya = 0;
if (input.up.isPressed()) {
ya--;
}
if (input.down.isPressed()) {
ya++;
}
if (input.left.isPressed()) {
xa--;
}
if (input.right.isPressed()) {
xa++;
}
if (xa != 0 || ya != 0) {
move(xa, ya);
isMoving = true;
} else {
isMoving = false;
}
this.scale = 1;
}
public void render(Screen screen) {
int xTile = 0;
int yTile = 28;
int walkingSpeed = 4;
int flipTop = (numSteps >> walkingSpeed) & 1;
int flipBottom = (numSteps >> walkingSpeed) & 1;
if (movingDir == 1) {
xTile += 2;
} else if (movingDir > 1) {
xTile += 4 + ((numSteps >> walkingSpeed) & 1) * 2;
flipTop = (movingDir - 1) % 2;
}
int modifier = 8 * scale;
int xOffset = x - modifier / 2;
int yOffset = y - modifier / 2 - 4;
// upper body
screen.render(xOffset + (modifier * flipTop), yOffset, xTile + yTile * 32, colour, flipTop, scale);
screen.render(xOffset + modifier - (modifier * flipTop), yOffset, (xTile + 1) + yTile * 32, colour, flipTop, scale);
// lower body
screen.render(xOffset + (modifier * flipBottom), yOffset + modifier, xTile + (yTile + 1) * 32, colour, flipBottom, scale);
screen.render(xOffset + modifier - (modifier * flipBottom), yOffset + modifier, (xTile + 1) + (yTile + 1) * 32, colour, flipBottom,
scale);
}
public boolean hasCollided(int xa, int ya) {
int xMin = 0;
int xMax = 7;
int yMin = 3;
int yMax = 7;
for (int x = xMin; x < xMax; x++) {
if (isSolidTile(xa, ya, x, yMin)) {
return true;
}
}
for (int x = xMin; x < xMax; x++) {
if (isSolidTile(xa, ya, x, yMax)) {
return true;
}
}
for (int y = yMin; y < yMax; y++) {
if (isSolidTile(xa, ya, xMin, y)) {
return true;
}
}
for (int y = yMin; y < yMax; y++) {
if (isSolidTile(xa, ya, xMax, y)) {
return true;
}
}
return false;
}
}
Okay. It took me FOREVER, but it was a simple fix. All I had to do was change the "(x, y)" to "(xa, ya)"
if (!hasCollided(xa, ya)) {
if (ya < 0)
movingDir = 0;
if (ya > 0)
movingDir = 1;
if (xa < 0)
movingDir = 2;
if (xa > 0)
movingDir = 3;
x += xa * speed;
y += ya * speed;
}
}
I would like to implement pan on my plot without zooming, by setting a origin value smaller than may lower boundaries on domain axis.
My code so far :
this.panZoom = PanZoom.attach(plot);
this.panZoom.setPan(PanZoom.Pan.HORIZONTAL);
this.panZoom.setZoom(null);
this.panZoom.setDelegate(this);
this.plot.setUserDomainOrigin(0);
this.plot.setDomainBoundaries(5, 20);
this.plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 2);
By doing this, my plot starts well at 5 but won't move when I scroll..
I'm migrating my project to androidPlot 1.2.2 and it was working with 0.9.7
Thanks!
This appears to be a limitation in Androidplot 1.2.2 (Will be fixed in the next release - tracked by this bug report.)
For now you can add this implementation of PanZoom in your project:
package com.androidplot.xy;
import android.graphics.RectF;
import android.graphics.PointF;
import android.util.*;
import android.view.*;
import com.androidplot.*;
import java.util.*;
/**
* Enables basic pan/zoom touch behavior for an {#link XYPlot}.
* By default boundaries set on the associated plot will define the scroll/zoom extents as well as
* initial state of the plot's visible area. If you wish to specify a scrollable / zoomable area
* that is greater than or less than the plot's boundaries, use
* {#link #setDomainBoundaries(Number, Number)} and
* {#link #setRangeBoundaries(Number, Number)}
* TODO: zoom using dynamic center point
* TODO: stretch both mode
*/
public class PanZoom implements View.OnTouchListener {
protected static final float MIN_DIST_2_FING = 5f;
protected static final int FIRST_FINGER = 0;
protected static final int SECOND_FINGER = 1;
private XYPlot plot;
private Pan pan;
private Zoom zoom;
private boolean isEnabled = true;
private DragState dragState = DragState.NONE;
RectRegion limits = new RectRegion();
RectRegion previousLimits = new RectRegion();
private PointF firstFingerPos;
// rectangle created by the space between two fingers
private RectF fingersRect;
private View.OnTouchListener delegate;
// Definition of the touch states
protected enum DragState {
NONE,
ONE_FINGER,
TWO_FINGERS
}
public enum Pan {
NONE,
HORIZONTAL,
VERTICAL,
BOTH
}
public enum Zoom {
/**
* Comletely disable panning
*/
NONE,
/**
* Zoom on the horizontal axis only
*/
STRETCH_HORIZONTAL,
/**
* Zoom on the vertical axis only
*/
STRETCH_VERTICAL,
/**
* Zoom on the vertical axis by the vertical distance between each finger, while zooming
* on the horizontal axis by the horizantal distance between each finger.
*/
STRETCH_BOTH,
/**
* Zoom each axis by the same amount, specifically the total distance between each finger.
*/
SCALE
}
protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
this.plot = plot;
this.pan = pan;
this.zoom = zoom;
}
/**
* Convenience method for enabling pan/zoom behavior on an instance of {#link XYPlot}, using
* a default behavior of {#link Pan#BOTH} and {#link Zoom#SCALE}.
* Use {#link PanZoom#attach(XYPlot, Pan, Zoom)} for finer grain control of this behavior.
* #param plot
* #return
*/
public static PanZoom attach(XYPlot plot) {
return attach(plot, Pan.BOTH, Zoom.SCALE);
}
public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
PanZoom pz = new PanZoom(plot, pan, zoom);
plot.setOnTouchListener(pz);
return pz;
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
/**
* Set the boundaries by which domain pan/zoom calculations will abide; differs from an {#link XYPlot}'s boundaries
* in that those boundaries define the plot's starting state.
* #param lowerBoundary
* #param upperBoundary
*/
public void setDomainBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinX(lowerBoundary);
limits.setMaxX(upperBoundary);
}
/**
* Sets the range boundaries by which pan/zoom calculations will abide.
* #param lowerBoundary
* #param upperBoundary
*/
public void setRangeBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinY(lowerBoundary);
limits.setMaxY(upperBoundary);
}
#Override
public boolean onTouch(final View view, final MotionEvent event) {
boolean isConsumed = false;
if (delegate != null) {
isConsumed = delegate.onTouch(view, event);
}
if (isEnabled() && !isConsumed) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // start gesture
firstFingerPos = new PointF(event.getX(), event.getY());
Log.d("PanZoom", "ONE_FINGER set");
dragState = DragState.ONE_FINGER;
break;
case MotionEvent.ACTION_POINTER_DOWN: // second finger
{
fingersRect = fingerDistance(event);
Log.d("PanZoom", "ACTION_POINTER_DOWN - distance: " + fingersRect.width());
// the distance check is done to avoid false alarms
if (fingersRect.width() > MIN_DIST_2_FING || fingersRect.width() < -MIN_DIST_2_FING) {
Log.d("PanZoom", "TWO_FINGERS set");
dragState = DragState.TWO_FINGERS;
}
break;
}
case MotionEvent.ACTION_POINTER_UP: // end zoom
dragState = DragState.NONE;
break;
case MotionEvent.ACTION_MOVE:
if (dragState == DragState.ONE_FINGER) {
Log.d("PanZoom", "ACTION_MOVE - one finger");
pan(event);
} else if (dragState == DragState.TWO_FINGERS) {
Log.d("PanZoom", "ACTION_MOVE - two fingers");
zoom(event);
}
break;
}
}
// we're forced to consume the event here as not consuming it will prevent future calls:
return true;
}
/**
* Calculates the distance between two finger motion events.
* #param firstFingerX
* #param firstFingerY
* #param secondFingerX
* #param secondFingerY
* #return
*/
protected RectF fingerDistance(float firstFingerX, float firstFingerY, float secondFingerX, float secondFingerY) {
final float left = firstFingerX > secondFingerX ? secondFingerX : firstFingerX;
final float right = firstFingerX > secondFingerX ? firstFingerX : secondFingerX;
final float top = firstFingerY > secondFingerY ? secondFingerY : firstFingerY;
final float bottom = firstFingerY > secondFingerY ? firstFingerY : secondFingerY;
return new RectF(left, top, right, bottom);
}
/**
* Calculates the distance between two finger motion events.
* #param evt
* #return
*/
protected RectF fingerDistance(final MotionEvent evt) {
return fingerDistance(
evt.getX(FIRST_FINGER),
evt.getY(FIRST_FINGER),
evt.getX(SECOND_FINGER),
evt.getY(SECOND_FINGER));
}
protected Number getMinXLimit() {
if (limits.getMinX() == null) {
limits.setMinX(plot.getBounds().getMinX().floatValue());
previousLimits.setMinX(limits.getMinX());
}
return limits.getMinX();
}
protected Number getMaxXLimit() {
if (limits.getMaxX() == null) {
limits.setMaxX(plot.getBounds().getMaxX().floatValue());
previousLimits.setMaxX(limits.getMaxX());
}
return limits.getMaxX();
}
protected Number getMinYLimit() {
if (limits.getMinY() == null) {
limits.setMinY(plot.getBounds().getMinY().floatValue());
previousLimits.setMinY(limits.getMinY());
}
return limits.getMinY();
}
protected Number getMaxYLimit() {
if (limits.getMaxY() == null) {
limits.setMaxY(plot.getBounds().getMaxY().floatValue());
previousLimits.setMaxY(limits.getMaxY());
}
return limits.getMaxY();
}
protected Number getLastMinX() {
if (previousLimits.getMinX() == null) {
previousLimits.setMinX(plot.getBounds().getMinX().floatValue());
}
return previousLimits.getMinX();
}
protected Number getLastMaxX() {
if (previousLimits.getMaxX() == null) {
previousLimits.setMaxX(plot.getBounds().getMaxX().floatValue());
}
return previousLimits.getMaxX();
}
protected Number getLastMinY() {
if (previousLimits.getMinY() == null) {
previousLimits.setMinY(plot.getBounds().getMinY().floatValue());
}
return previousLimits.getMinY();
}
private Number getLastMaxY() {
if (previousLimits.getMaxY() == null) {
previousLimits.setMaxY(plot.getBounds().getMaxY().floatValue());
}
return previousLimits.getMaxY();
}
protected void pan(final MotionEvent motionEvent) {
if (pan == Pan.NONE) {
return;
}
final PointF oldFirstFinger = firstFingerPos; //save old position of finger
firstFingerPos = new PointF(motionEvent.getX(), motionEvent.getY()); //update finger position
Region newBounds = new Region();
if (EnumSet.of(Pan.HORIZONTAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, true);
plot.setDomainBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinX(newBounds.getMin());
previousLimits.setMaxX(newBounds.getMax());
}
if (EnumSet.of(Pan.VERTICAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, false);
plot.setRangeBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinY(newBounds.getMin());
previousLimits.setMaxY(newBounds.getMax());
}
plot.redraw();
}
protected void calculatePan(final PointF oldFirstFinger, Region bounds, final boolean horizontal) {
final float offset;
// multiply the absolute finger movement for a factor.
// the factor is dependent on the calculated min and max
if (horizontal) {
bounds.setMin(getLastMinX());
bounds.setMax(getLastMaxX());
offset = (oldFirstFinger.x - firstFingerPos.x) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getWidth());
} else {
bounds.setMin(getLastMinY());
bounds.setMax(getLastMaxY());
offset = -(oldFirstFinger.y - firstFingerPos.y) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getHeight());
}
// move the calculated offset
bounds.setMin(bounds.getMin().floatValue() + offset);
bounds.setMax(bounds.getMax().floatValue() + offset);
//get the distance between max and min
final float diff = bounds.length().floatValue();
//check if we reached the limit of panning
if (horizontal) {
if (bounds.getMin().floatValue() < getMinXLimit().floatValue()) {
bounds.setMin(getMinXLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxXLimit().floatValue()) {
bounds.setMax(getMaxXLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
} else {
if (bounds.getMin().floatValue() < getMinYLimit().floatValue()) {
bounds.setMin(getMinYLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxYLimit().floatValue()) {
bounds.setMax(getMaxYLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
}
}
protected boolean isValidScale(float scale) {
if (Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) {
return false;
}
return true;
}
protected void zoom(final MotionEvent motionEvent) {
if (zoom == Zoom.NONE) {
return;
}
final RectF oldFingersRect = fingersRect;
final RectF newFingersRect = fingerDistance(motionEvent);
fingersRect = newFingersRect;
RectF newRect = new RectF();
float scaleX = 1;
float scaleY = 1;
switch (zoom) {
case STRETCH_HORIZONTAL:
scaleX = oldFingersRect.width() / fingersRect.width();
if (!isValidScale(scaleX)) {
return;
}
break;
case STRETCH_VERTICAL:
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleY)) {
return;
}
break;
case STRETCH_BOTH:
scaleX = oldFingersRect.width() / fingersRect.width();
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
case SCALE:
float sc1 = (float) Math.hypot(oldFingersRect.height(), oldFingersRect.width());
float sc2 = (float) Math.hypot(fingersRect.height(), fingersRect.width());
float sc = sc1 / sc2;
scaleX = sc;
scaleY = sc;
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
}
if (EnumSet.of(
Zoom.STRETCH_HORIZONTAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleX, true);
plot.setDomainBoundaries(newRect.left, newRect.right, BoundaryMode.FIXED);
previousLimits.setMinX(newRect.left);
previousLimits.setMaxX(newRect.right);
}
if (EnumSet.of(
Zoom.STRETCH_VERTICAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleY, false);
plot.setRangeBoundaries(newRect.top, newRect.bottom, BoundaryMode.FIXED);
previousLimits.setMinY(newRect.top);
previousLimits.setMaxY(newRect.bottom);
}
plot.redraw();
}
protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) {
final float calcMax;
final float span;
if (isHorizontal) {
calcMax = getLastMaxX().floatValue();
span = calcMax - getLastMinX().floatValue();
} else {
calcMax = getLastMaxY().floatValue();
span = calcMax - getLastMinY().floatValue();
}
final float midPoint = calcMax - (span / 2.0f);
final float offset = span * scale / 2.0f;
if (isHorizontal) {
newRect.left = midPoint - offset;
newRect.right = midPoint + offset;
if (newRect.left < getMinXLimit().floatValue()) {
newRect.left = getMinXLimit().floatValue();
}
if (newRect.right > getMaxXLimit().floatValue()) {
newRect.right = getMaxXLimit().floatValue();
}
} else {
newRect.top = midPoint - offset;
newRect.bottom = midPoint + offset;
if (newRect.top < getMinYLimit().floatValue()) {
newRect.top = getMinYLimit().floatValue();
}
if (newRect.bottom > getMaxYLimit().floatValue()) {
newRect.bottom = getMaxYLimit().floatValue();
}
}
}
public Pan getPan() {
return pan;
}
public void setPan(Pan pan) {
this.pan = pan;
}
public Zoom getZoom() {
return zoom;
}
public void setZoom(Zoom zoom) {
this.zoom = zoom;
}
public View.OnTouchListener getDelegate() {
return delegate;
}
/**
* Set a delegate to receive onTouch calls before this class does. If the delegate wishes
* to consume the event, it should return true, otherwise it should return false. Returning
* false will not prevent future onTouch events from filtering through the delegate as it normally
* would when attaching directly to an instance of {#link View}.
* #param delegate
*/
public void setDelegate(View.OnTouchListener delegate) {
this.delegate = delegate;
}
public void reset() {
this.previousLimits = new RectRegion();
this.firstFingerPos = null;
this.fingersRect = null;
}
}
Don't forget to change the class name to avoid a collision with the existing PanZoom implementation.
If you want to constrain the starting window bounds to a sub-section of your data, set those boundaries on the XYPlot instance as normal. Then, configure the PanZoom instance with the absolute boundaries for panning and zooming, typically the min/max values of the series attached to your plot.
Also, if you need to change the visible window after your initial setup in onCreate (result of a user pressing a reset button, etc.) you'll also need to invoke PanZoom.reset() to wipe out it's internal state. This step may not be necessary in the official release implementation.
I am working on android studio with OpenCV library. I am dealing with ColorBlobDetectionActivity class so I want to rearrenge its processing. OpenCV processes all screen but I want to process just particular region to make it faster on android camera.Can someone help me?
Here it is ColorBlobDetectionActivity Class Code:
private boolean mIsColorSelected = false;
private Mat mRgba;
private Scalar mBlobColorRgba;
private Scalar mBlobColorHsv;
private ColorBlobDetector mDetector;
private Mat mSpectrum;
private Size SPECTRUM_SIZE;
private Scalar CONTOUR_COLOR;
private CameraBridgeViewBase mOpenCvCameraView;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
mOpenCvCameraView.setOnTouchListener(ColorBlobDetectionActivity.this);
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
public ColorBlobDetectionActivity() {
Log.i(TAG, "Instantiated new " + this.getClass());
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "called onCreate");
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.color_blob_detection_surface_view);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.color_blob_detection_activity_surface_view);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
}
#Override
public void onPause()
{
super.onPause();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
#Override
public void onResume()
{
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
public void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
public void onCameraViewStarted(int width, int height) {
mRgba = new Mat(height, width, CvType.CV_8UC4);
mDetector = new ColorBlobDetector();
mSpectrum = new Mat();
mBlobColorRgba = new Scalar(255);
mBlobColorHsv = new Scalar(255);
SPECTRUM_SIZE = new Size(200, 64);
CONTOUR_COLOR = new Scalar(255,0,0,255);
}
public void onCameraViewStopped() {
mRgba.release();
}
public boolean onTouch(View v, MotionEvent event) {
int cols = mRgba.cols();
int rows = mRgba.rows();
int xOffset = (mOpenCvCameraView.getWidth() - cols) / 2;
int yOffset = (mOpenCvCameraView.getHeight() - rows) / 2;
int x = (int)event.getX() - xOffset;
int y = (int)event.getY() - yOffset;
Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");
if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return false;
Rect touchedRect = new Rect();
touchedRect.x = (x>4) ? x-4 : 0;
touchedRect.y = (y>4) ? y-4 : 0;
touchedRect.width = (x+4 < cols) ? x + 4 - touchedRect.x : cols - touchedRect.x;
touchedRect.height = (y+4 < rows) ? y + 4 - touchedRect.y : rows - touchedRect.y;
Mat touchedRegionRgba = mRgba.submat(touchedRect);
Mat touchedRegionHsv = new Mat();
Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);
// Calculate average color of touched region
mBlobColorHsv = Core.sumElems(touchedRegionHsv);
int pointCount = touchedRect.width*touchedRect.height;
for (int i = 0; i < mBlobColorHsv.val.length; i++)
mBlobColorHsv.val[i] /= pointCount;
mBlobColorRgba = converScalarHsv2Rgba(mBlobColorHsv);
Log.i(TAG, "Touched rgba color: (" + mBlobColorRgba.val[0] + ", " + mBlobColorRgba.val[1] +
", " + mBlobColorRgba.val[2] + ", " + mBlobColorRgba.val[3] + ")");
mDetector.setHsvColor(mBlobColorHsv);
Imgproc.resize(mDetector.getSpectrum(), mSpectrum, SPECTRUM_SIZE);
mIsColorSelected = true;
touchedRegionRgba.release();
touchedRegionHsv.release();
return false; // don't need subsequent touch events
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba = inputFrame.rgba();
if (mIsColorSelected) {
mDetector.process(mRgba);
List<MatOfPoint> contours = mDetector.getContours();
Log.e(TAG, "Contours count: " + contours.size());
Imgproc.drawContours(mRgba, contours, -1, CONTOUR_COLOR);
Mat colorLabel = mRgba.submat(4, 68, 4, 68);
colorLabel.setTo(mBlobColorRgba);
Mat spectrumLabel = mRgba.submat(4, 4 + mSpectrum.rows(), 70, 70 + mSpectrum.cols());
mSpectrum.copyTo(spectrumLabel);
}
return mRgba;
}
private Scalar converScalarHsv2Rgba(Scalar hsvColor) {
Mat pointMatRgba = new Mat();
Mat pointMatHsv = new Mat(1, 1, CvType.CV_8UC3, hsvColor);
Imgproc.cvtColor(pointMatHsv, pointMatRgba, Imgproc.COLOR_HSV2RGB_FULL, 4);
return new Scalar(pointMatRgba.get(0, 0));
}
}
I have ObservableList<LineChart> backCharts. Also I have class StackChartSeries to compare data's scale. Depending on that I add new backchart with new scale or add series to existed backchart.
Applying style to chart must work with every chart of the specified scale. But it works only with the first chart of the specified scale.
Code next:
public class StackChart extends StackPane {
private LineChart baseChart;
private final ObservableList<LineChart> backCharts = FXCollections.observableArrayList();
private final double yAxisWidth = 60;
private final AnchorPane details;
private final double yAxisSeparation = 20;
private double strokeWidth = 0.5;
private int size = 0;
public StackChart(LineChart baseChart) {
this(baseChart, null);
}
public StackChart(LineChart baseChart, Double strokeWidth) {
if (strokeWidth != null) {
this.strokeWidth = strokeWidth;
}
this.baseChart = baseChart;
baseChart.setCreateSymbols(false);
baseChart.setLegendVisible(false);
baseChart.getXAxis().setAutoRanging(false);
baseChart.getXAxis().setAnimated(false);
baseChart.getXAxis().setStyle("-fx-font-size:" + 18);
baseChart.getYAxis().setAnimated(false);
baseChart.getYAxis().setStyle("-fx-font-size:" + 18);
setFixedAxisWidth(baseChart);
setAlignment(Pos.CENTER_LEFT);
backCharts.addListener((Observable observable) -> rebuildChart());
details = new AnchorPane();
bindMouseEvents(baseChart, this.strokeWidth);
rebuildChart();
}
private void bindMouseEvents(LineChart baseChart, Double strokeWidth) {
getChildren().add(details);
details.prefHeightProperty().bind(heightProperty());
details.prefWidthProperty().bind(widthProperty());
details.setMouseTransparent(true);
setOnMouseMoved(null);
setMouseTransparent(false);
final Axis xAxis = baseChart.getXAxis();
final Axis yAxis = baseChart.getYAxis();
final Line xLine = new Line();
final Line yLine = new Line();
yLine.setFill(Color.GRAY);
xLine.setFill(Color.GRAY);
yLine.setStrokeWidth(strokeWidth/2);
xLine.setStrokeWidth(strokeWidth/2);
xLine.setVisible(false);
yLine.setVisible(false);
final Node chartBackground = baseChart.lookup(".chart-plot-background");
for (Node n: chartBackground.getParent().getChildrenUnmodifiable()) {
if (n != chartBackground && n != xAxis && n != yAxis) {
n.setMouseTransparent(true);
}
}
chartBackground.setCursor(Cursor.CROSSHAIR);
chartBackground.setOnMouseEntered((event) -> {
chartBackground.getOnMouseMoved().handle(event);
xLine.setVisible(true);
yLine.setVisible(true);
details.getChildren().addAll(xLine, yLine);
});
chartBackground.setOnMouseExited((event) -> {
xLine.setVisible(false);
yLine.setVisible(false);
details.getChildren().removeAll(xLine, yLine);
});
chartBackground.setOnMouseMoved(event -> {
double x = event.getX() + chartBackground.getLayoutX();
double y = event.getY() + chartBackground.getLayoutY();
xLine.setStartX(65);
xLine.setEndX(details.getWidth()-10);
xLine.setStartY(y+5);
xLine.setEndY(y+5);
yLine.setStartX(x+5);
yLine.setEndX(x+5);
yLine.setStartY(12);
yLine.setEndY(details.getHeight()-28);
});
}
private void setFixedAxisWidth(LineChart chart) {
chart.getYAxis().setPrefWidth(yAxisWidth);
chart.getYAxis().setMaxWidth(yAxisWidth);
}
private void rebuildChart() {
getChildren().clear();
getChildren().add(resizeBaseChart(baseChart));
for (LineChart lineChart : backCharts) {
getChildren().add(resizeBackgroundChart(lineChart));
}
getChildren().add(details);
}
private Node resizeBaseChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backCharts.size()));
return lineChart;
}
private Node resizeBackgroundChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
hBox.setMouseTransparent(true);
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backCharts.size()));
lineChart.translateXProperty().bind(baseChart.getYAxis().widthProperty());
lineChart.getYAxis().setTranslateX((yAxisWidth + yAxisSeparation) * backCharts.indexOf(lineChart));
return hBox;
}
private LineChart prepareLineChart(String seriesName){
NumberAxis yAxis = new NumberAxis();
NumberAxis xAxis = new NumberAxis();
// xAxis
xAxis.setAutoRanging(false);
xAxis.setVisible(false);
xAxis.setOpacity(0.0);
xAxis.lowerBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).lowerBoundProperty());
xAxis.upperBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).upperBoundProperty());
xAxis.tickUnitProperty().bind(((NumberAxis) baseChart.getXAxis()).tickUnitProperty());
// yAxis
yAxis.setSide(Side.RIGHT);
yAxis.setLabel(seriesName);
// create chart
LineChart lineChart = new LineChart(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setLegendVisible(false);
setFixedAxisWidth(lineChart);
return lineChart;
}
public void addSeries(List<StackChartSeries> series){
Collections.sort(series, (x, y) -> (int) (x.getMaxY() - y.getMaxY()));
LineChart lineChart = null;
size = series.size();
for (int i = 0; i < size; i++) {
if (i == 0 || series.get(i).getMaxY() / series.get(i-1).getMaxY() >= 10
|| series.get(i).getMaxY() / series.get(i-1).getMaxY() <= 0.1
|| Math.abs(series.get(i).getMaxY() - series.get(i-1).getMaxY()) >= 900
) {
lineChart = prepareLineChart("Scale" + i);
//new chart with new scale
backCharts.add(lineChart);
}
lineChart.getData().add(series.get(i).getSeries());
}
int j = 0;
for (LineChart chart : backCharts) {
styleBackChart(chart);
//HERE
setLineStyle(chart, j+3);
}
}
private void styleBackChart(LineChart lineChart) {
setStyle(lineChart);
Node contentBackground = lineChart.lookup(".chart-content").lookup(".chart-plot-background");
contentBackground.setStyle("-fx-background-color: transparent;");//
lineChart.setVerticalZeroLineVisible(false);
lineChart.setHorizontalZeroLineVisible(false);
lineChart.setVerticalGridLinesVisible(false);
lineChart.setHorizontalGridLinesVisible(false);
lineChart.setCreateSymbols(false);
lineChart.getXAxis().setStyle("-fx-font-size:" + 18);
lineChart.getYAxis().setStyle("-fx-font-size:" + 18);
}
private void setStyle(LineChart chart) {
chart.getYAxis().lookup(".axis-label").setStyle("-fx-font-size: 24;");
Node seriesLine = chart.lookup(".chart-series-line");
seriesLine.setStyle("-fx-stroke-width: " + strokeWidth + ";");
}
public Node getLegend() {
HBox hbox = new HBox();
final CheckBox baseChartCheckBox = new CheckBox(baseChart.getYAxis().getLabel());
baseChartCheckBox.setSelected(true);
baseChartCheckBox.setDisable(true);
baseChartCheckBox.getStyleClass().add("readonly-checkbox");
baseChartCheckBox.setOnAction(event -> baseChartCheckBox.setSelected(true));
hbox.getChildren().add(baseChartCheckBox);
for (final LineChart lineChart : backCharts) {
CheckBox checkBox = new CheckBox(lineChart.getYAxis().getLabel());
checkBox.setSelected(true);
checkBox.setOnAction(event -> {
if (backCharts.contains(lineChart)) {
backCharts.remove(lineChart);
} else {
backCharts.add(lineChart);
}
});
hbox.getChildren().add(checkBox);
}
hbox.setAlignment(Pos.CENTER);
hbox.setSpacing(20);
hbox.setStyle("-fx-padding: 0 10 20 10");
return hbox;
}
private void setLineStyle (LineChart chart, Integer k) {
Node seriesLine = chart.lookup(".chart-series-line");
seriesLine.setStyle(" -fx-stroke-dash-array: " + k + " 5 15 5;");
}
}
StackChartSeries.java:
import javafx.scene.chart.XYChart;
import org.apache.commons.lang.ArrayUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
public class StackChartSeries {
private XYChart.Series series;
private final double[] xValues;
private final double[] yValues;
private Optional<Double> maxY;
public StackChartSeries(double[] xValues, double[] yValues) {
this.xValues = xValues;
this.yValues = yValues;
}
public XYChart.Series getSeries() {
return series != null ? series : (series = prepareSeries(xValues, yValues));
}
private static XYChart.Series<Number, Number> prepareSeries( double[] xValues, double[] yValues) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
for (int i = 0; i < yValues.length; i++){
series.getData().add(new XYChart.Data(xValues[i], yValues[i]));
}
return series;
}
public double getMaxY(){
if (maxY != null){
return maxY.get();
}
else {
Double d = C?ollections.max(Arrays.asList(ArrayUtils.toObject(yValues)));
maxY = Optional.of(d);
return maxY.get();
}
}
public double[] getXValues() {
return xValues;
}
public double[] getYValues() {
return yValues;
}
}
How do you create a video wall like in the JavaFX 2.0 demo here:
https://www.youtube.com/watch?v=UXSmJYFrulY#t=411
For a start it doesn't have to be videos, it can be images as well. All I'd like to have is to place the nodes like they are in the video, i. e. in a curved shape like the insides of a cylinder or a sphere.
Or is the source of that demo available somewhere?
Thank you very much.
I researched and found a very awesome site with the relevant information:
http://paulbourke.net/geometry/transformationprojection/
The relevant part was the Coordinate System Transformation, in particular the equations for converting between cartesian and spherical coordinates.
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
In my example below y isn't used from the formula, since the image rows are stacked.
Note: By using these formulas in 2 nested for-loops from -Math.PI to Math.PI you can lay out the nodes around a sphere. The difficult part regarding the full sphere was to rotate the nodes towards the center, that one I couldn't figure out.
Since I wasn't familiar with Java3D I also checked out the Building a 3D Sample Application.
In the end I got a video wall, the code is reduced to this:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
private static final double CAMERA_INITIAL_DISTANCE = -850;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
Image[] images = new Image[] {
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
public VideoWall(){
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
* #return
*/
private ImageView createImageView() {
Image image = images[ rand.nextInt(images.length)];
ImageView c = new ImageView( image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
camera = new PerspectiveCamera(true);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
// we display any node (imageview, webview, etc)
Node node;
// create a single webview; we only add it once because we don't want to flood youtube
WebView webView = new WebView();
webView.getEngine().load(
"http://www.youtube.com/embed/utUPth77L_o?autoplay=1"
);
webView.setPrefSize(100, 70);
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int count=0;
for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
{
double angle2 = Math.PI;
for( int i=-3; i <= 3; i++)
{
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
// add 1 webview, the rest imageviews
if( count == 16) {
node = webView;
} else {
node = createImageView();
}
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees( -angle1));
node.getTransforms().addAll(rx);
root.getChildren().add( node);
count++;
}
}
Scene scene = new Scene(root, 1600, 900, Color.BLACK);
primaryStage.setScene( scene);
primaryStage.show();
scene.setCamera(camera);
}
}
You can add whatever node you prefer. I added a youtube webview for testing. It plays, but the video doesn't get loaded, so all you see is static noise (the grey tile in the screenshot). So in theory you could make the nodes all webview with youtube videos, but that would mean flooding youtube. Better use some offline videos.
Here's a screenshot:
I also toyed around with the full 3d example and creating a ring. That's how it looked like (with always the same image) from an outer view:
Having the camera in the center you can nicely scroll the ring.
If someone wants to toy around, here's a quick & dirty gist with the navigable ring. Use left/right/middle mouse buttons for navigation.
And if you'd like to toy around with a full sphere, you may use this:
// full sphere
for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.15) {
for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.15) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
c = createImageView();
c.setTranslateX(x);
c.setTranslateY(y);
c.setTranslateZ(z);
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees(-angle1));
c.getTransforms().addAll(rx);
world.getChildren().add(c);
}
}
Which looks like this:
But as mentioned, I haven't figured out yet how to rotate all tiles so that they look into the center. And they'd need to be equally distributed. But that's just for fun and off-topic.
Since it's part of the video in my question, it was only a matter of keeping a list of parallel transitions to create the "build-up" animation of the tiles. The bottom row has a reflection now.
The extended code:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
private static final double CAMERA_INITIAL_DISTANCE = -850;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
Image[] images = new Image[] {
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
List<ParallelTransition> transitionList = new ArrayList<>();
public VideoWall(){
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
* #return
*/
private ImageView createImageView() {
Image image = images[ rand.nextInt(images.length)];
ImageView c = new ImageView( image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
camera = new PerspectiveCamera(true);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
// we display any node (imageview, webview, etc)
Node node;
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int min = -3;
int max = 3;
for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
{
double angle2 = Math.PI;
for( int i=min; i <= max; i++)
{
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
node = createImageView();
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees( -angle1));
node.getTransforms().addAll(rx);
// reflection on bottom row
if( i==max) {
Reflection refl = new Reflection();
refl.setFraction(0.8f);
node.setEffect(refl);
}
// build the wall using a transition
node.setVisible(false);
transitionList.add( createTransition( node));
root.getChildren().add( node);
}
}
Scene scene = new Scene(root, 1600, 900, Color.BLACK);
primaryStage.setScene( scene);
primaryStage.show();
scene.setCamera(camera);
AnimationTimer timer = createAnimation();
timer.start();
}
private AnimationTimer createAnimation() {
Collections.sort(transitionList, new Comparator<ParallelTransition>() {
#Override
public int compare(ParallelTransition arg0, ParallelTransition arg1) {
// bottom right to top left
Point2D ref = new Point2D(1000,1000);
Point2D pt0 = new Point2D( arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
Point2D pt1 = new Point2D( arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());
return Double.compare(ref.distance(pt0), ref.distance(pt1));
// bottom row first
// return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());
}
});
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
//if( (now - last) > 1_000_000_000)
if( (now - last) > 40_000_000)
{
if( transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if( transitionList.size() == 0) {
stop();
}
}
};
return timer;
}
private ParallelTransition createTransition( final Node node) {
Path path = new Path();
path.getElements().add(new MoveToAbs( node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
path.getElements().add(new LineToAbs( node, node.getTranslateX(), node.getTranslateY()));
Duration duration = Duration.millis(1500);
PathTransition pt = new PathTransition( duration, path, node);
RotateTransition rt = new RotateTransition( duration, node);
rt.setByAngle(720);
rt.setAutoReverse(true);
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.setNode(node);
parallelTransition.getChildren().addAll(pt, rt);
return parallelTransition;
}
public static class MoveToAbs extends MoveTo {
public MoveToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static class LineToAbs extends LineTo {
public LineToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
}
Well to me it seems to be a matter of creating a Grid, or "Mesh" out of ImageViews.
Then you would Map all the Viewport's to the Image('s) you want to display.
Here for example is a Skybox for 3D implementation using this approach.
Note that this is just a simple Cube.
The image is setup similar to this, though I made it into 6 separate Images.
If you wanted to use video, I recommend you use VLCJ,
They have samples for JavaFX setup Here
For this you would apply the same principals to the WritableImage(s)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
I added a couple things for you to play with ...
/**
* A self initializing First Person Shooter camera
*
* #author Jason Pollastrini aka jdub1581
*/
public class SimpleFPSCamera extends Parent {
public SimpleFPSCamera() {
initialize();
}
private void update() {
updateControls();
}
private void updateControls() {
if (fwd && !back) {
moveForward();
}
if (strafeL) {
strafeLeft();
}
if (strafeR) {
strafeRight();
}
if (back && !fwd) {
moveBack();
}
if (up && !down) {
moveUp();
}
if (down && !up) {
moveDown();
}
}
/*==========================================================================
Initialization
*/
private final Group root = new Group();
private final Affine affine = new Affine();
private final Translate t = new Translate(0, 0, 0);
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS),
rotateY = new Rotate(0, Rotate.Y_AXIS),
rotateZ = new Rotate(0, Rotate.Z_AXIS);
private boolean fwd, strafeL, strafeR, back, up, down, shift;
private double mouseSpeed = 1.0, mouseModifier = 0.1;
private double moveSpeed = 10.0;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
private void initialize() {
getChildren().add(root);
getTransforms().addAll(affine);
initializeCamera();
startUpdateThread();
}
public void loadControlsForSubScene(SubScene scene) {
sceneProperty().addListener(l -> {
if (getScene() != null) {
getScene().addEventHandler(KeyEvent.ANY, ke -> {
if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
switch (ke.getCode()) {
case Q:
up = true;
break;
case E:
down = true;
break;
case W:
fwd = true;
break;
case S:
back = true;
break;
case A:
strafeL = true;
break;
case D:
strafeR = true;
break;
case SHIFT:
shift = true;
moveSpeed = 20;
break;
}
} else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
switch (ke.getCode()) {
case Q:
up = false;
break;
case E:
down = false;
break;
case W:
fwd = false;
break;
case S:
back = false;
break;
case A:
strafeL = false;
break;
case D:
strafeR = false;
break;
case SHIFT:
moveSpeed = 10;
shift = false;
break;
}
}
ke.consume();
});
}
});
scene.addEventHandler(MouseEvent.ANY, me -> {
if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
} else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
mouseSpeed = 1.0;
mouseModifier = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isControlDown()) {
mouseSpeed = 0.1;
}
if (me.isShiftDown()) {
mouseSpeed = 1.0;
}
t.setX(getPosition().getX());
t.setY(getPosition().getY());
t.setZ(getPosition().getZ());
affine.setToIdentity();
rotateY.setAngle(
Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
); // horizontal
rotateX.setAngle(
Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
); // vertical
affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));
} else if (me.isSecondaryButtonDown()) {
/*
init zoom?
*/
} else if (me.isMiddleButtonDown()) {
/*
init panning?
*/
}
}
});
scene.addEventHandler(ScrollEvent.ANY, se -> {
if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {
}
});
}
public void loadControlsForScene(Scene scene) {
scene.addEventHandler(KeyEvent.ANY, ke -> {
if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
switch (ke.getCode()) {
case Q:
up = true;
break;
case E:
down = true;
break;
case W:
fwd = true;
break;
case S:
back = true;
break;
case A:
strafeL = true;
break;
case D:
strafeR = true;
break;
case SHIFT:
shift = true;
moveSpeed = 20;
break;
}
} else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
switch (ke.getCode()) {
case Q:
up = false;
break;
case E:
down = false;
break;
case W:
fwd = false;
break;
case S:
back = false;
break;
case A:
strafeL = false;
break;
case D:
strafeR = false;
break;
case SHIFT:
moveSpeed = 10;
shift = false;
break;
}
}
ke.consume();
});
scene.addEventHandler(MouseEvent.ANY, me -> {
if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
} else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
mouseSpeed = 1.0;
mouseModifier = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isControlDown()) {
mouseSpeed = 0.1;
}
if (me.isShiftDown()) {
mouseSpeed = 1.0;
}
t.setX(getPosition().getX());
t.setY(getPosition().getY());
t.setZ(getPosition().getZ());
affine.setToIdentity();
rotateY.setAngle(
Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
); // horizontal
rotateX.setAngle(
Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
); // vertical
affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));
} else if (me.isSecondaryButtonDown()) {
/*
init zoom?
*/
} else if (me.isMiddleButtonDown()) {
/*
init panning?
*/
}
}
});
scene.addEventHandler(ScrollEvent.ANY, se -> {
if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL)) {
} else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {
}
});
}
private void initializeCamera() {
getCamera().setNearClip(0.1);
getCamera().setFarClip(100000);
getCamera().setFieldOfView(42);
getCamera().setVerticalFieldOfView(true);
//getCamera().getTransforms().add(new Rotate(180, Rotate.Z_AXIS));
root.getChildren().add(getCamera());
}
private void startUpdateThread() {
new AnimationTimer() {
#Override
public void handle(long now) {
update();
}
}.start();
}
/*==========================================================================
Movement
*/
private void moveForward() {
affine.setTx(getPosition().getX() + moveSpeed * getN().getX());
affine.setTy(getPosition().getY() + moveSpeed * getN().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getN().getZ());
}
private void strafeLeft() {
affine.setTx(getPosition().getX() + moveSpeed * -getU().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getU().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getU().getZ());
}
private void strafeRight() {
affine.setTx(getPosition().getX() + moveSpeed * getU().getX());
affine.setTy(getPosition().getY() + moveSpeed * getU().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getU().getZ());
}
private void moveBack() {
affine.setTx(getPosition().getX() + moveSpeed * -getN().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getN().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getN().getZ());
}
private void moveUp() {
affine.setTx(getPosition().getX() + moveSpeed * -getV().getX());
affine.setTy(getPosition().getY() + moveSpeed * -getV().getY());
affine.setTz(getPosition().getZ() + moveSpeed * -getV().getZ());
}
private void moveDown() {
affine.setTx(getPosition().getX() + moveSpeed * getV().getX());
affine.setTy(getPosition().getY() + moveSpeed * getV().getY());
affine.setTz(getPosition().getZ() + moveSpeed * getV().getZ());
}
/*==========================================================================
Properties
*/
private final ReadOnlyObjectWrapper<PerspectiveCamera> camera = new ReadOnlyObjectWrapper<>(this, "camera", new PerspectiveCamera(true));
public final PerspectiveCamera getCamera() {
return camera.get();
}
public ReadOnlyObjectProperty cameraProperty() {
return camera.getReadOnlyProperty();
}
/*==========================================================================
Callbacks
| R | Up| F | | P|
U |mxx|mxy|mxz| |tx|
V |myx|myy|myz| |ty|
N |mzx|mzy|mzz| |tz|
*/
//Forward / look direction
private final Callback<Transform, Point3D> F = (a) -> {
return new Point3D(a.getMzx(), a.getMzy(), a.getMzz());
};
private final Callback<Transform, Point3D> N = (a) -> {
return new Point3D(a.getMxz(), a.getMyz(), a.getMzz());
};
// up direction
private final Callback<Transform, Point3D> UP = (a) -> {
return new Point3D(a.getMyx(), a.getMyy(), a.getMyz());
};
private final Callback<Transform, Point3D> V = (a) -> {
return new Point3D(a.getMxy(), a.getMyy(), a.getMzy());
};
// right direction
private final Callback<Transform, Point3D> R = (a) -> {
return new Point3D(a.getMxx(), a.getMxy(), a.getMxz());
};
private final Callback<Transform, Point3D> U = (a) -> {
return new Point3D(a.getMxx(), a.getMyx(), a.getMzx());
};
//position
private final Callback<Transform, Point3D> P = (a) -> {
return new Point3D(a.getTx(), a.getTy(), a.getTz());
};
private Point3D getF() {
return F.call(getLocalToSceneTransform());
}
public Point3D getLookDirection() {
return getF();
}
private Point3D getN() {
return N.call(getLocalToSceneTransform());
}
public Point3D getLookNormal() {
return getN();
}
private Point3D getR() {
return R.call(getLocalToSceneTransform());
}
private Point3D getU() {
return U.call(getLocalToSceneTransform());
}
private Point3D getUp() {
return UP.call(getLocalToSceneTransform());
}
private Point3D getV() {
return V.call(getLocalToSceneTransform());
}
public final Point3D getPosition() {
return P.call(getLocalToSceneTransform());
}
}
Your Modified code:
public class VideoWall extends Application {
Random rand = new Random();
Group root = new Group();
PerspectiveCamera camera;
SimpleFPSCamera fpsCam;
private static final double CAMERA_INITIAL_DISTANCE = -10000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 100000.0;
Image[] images = new Image[]{
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")
};
List<ParallelTransition> transitionList = new ArrayList<>();
List<ImageView> imageList = new ArrayList<>();
public VideoWall() {
}
public static void main(String[] args) {
launch(args);
}
/**
* Create ImageView with random Image.
*
* #return
*/
private ImageView createImageView() {
Image image = images[rand.nextInt(images.length)];
ImageView c = new ImageView(image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
private BillboardImage createBillboardImage() {
Image image = images[rand.nextInt(images.length)];
BillboardImage c = new BillboardImage(image);
c.setFitWidth(140);
c.setFitWidth(100);
c.setPreserveRatio(true);
return c;
}
#Override
public void start(Stage primaryStage) {
// build camera
//camera = new PerspectiveCamera(true);
//camera.setNearClip(CAMERA_NEAR_CLIP);
//camera.setFarClip(CAMERA_FAR_CLIP);
//camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
fpsCam = new SimpleFPSCamera();
// we display any node (imageview, webview, etc)
Node node;
// wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
double ringBeginDeg = -30;
double ringEndDeg = 38;
double r = 1300;
double yOffset = 80; // offset per image row
double yOffsetInitial = 120; // initial y offset from "floor"
int min = -3;
int max = 3;
/*
for (double angle1 = Math.toRadians(ringBeginDeg); angle1 < Math.toRadians(ringEndDeg); angle1 += 0.08) {
double angle2 = Math.PI;
for (int i = min; i <= max; i++) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
// double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
node = createImageView();
node.setTranslateX(x);
node.setTranslateY(yOffset * i - yOffsetInitial);
node.setTranslateZ(z);
// rotate towards viewer position
Rotate rx = new Rotate();
rx.setAxis(Rotate.Y_AXIS);
rx.setAngle(Math.toDegrees(-angle1));
node.getTransforms().addAll(rx);
// reflection on bottom row
if (i == max) {
Reflection refl = new Reflection();
refl.setFraction(0.8f);
node.setEffect(refl);
}
// build the wall using a transition
node.setVisible(false);
transitionList.add(createTransition(node));
root.getChildren().add(node);
}
}*/
// full sphere
for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.48) {
for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.48) {
double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);
BillboardImage c = createBillboardImage();
c.setTranslateX(x);
c.setTranslateY(y);
c.setTranslateZ(z);
imageList.add(c);
}
}
root.getChildren().add(fpsCam);
root.getChildren().addAll(imageList);
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BLACK);
scene.setCamera(fpsCam.getCamera());
fpsCam.loadControlsForScene(scene);
primaryStage.setScene(scene);
primaryStage.show();
AnimationTimer timer = createBillboardTimer();
timer.start();
}
private AnimationTimer createBillboardTimer() {
return new AnimationTimer() {
#Override
public void handle(long now) {
if(!imageList.isEmpty()){
imageList.stream().forEach(bbi ->{
((BillboardImage)bbi).updateMatrix(bbi, fpsCam);
});
}
}
};
}
private AnimationTimer createAnimation() {
Collections.sort(transitionList, new Comparator<ParallelTransition>() {
#Override
public int compare(ParallelTransition arg0, ParallelTransition arg1) {
// bottom right to top left
Point2D ref = new Point2D(1000, 1000);
Point2D pt0 = new Point2D(arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
Point2D pt1 = new Point2D(arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());
return Double.compare(ref.distance(pt0), ref.distance(pt1));
// bottom row first
// return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());
}
});
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
//if( (now - last) > 1_000_000_000)
if ((now - last) > 40_000_000) {
if (transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if (transitionList.size() == 0) {
stop();
}
}
};
return timer;
}
private ParallelTransition createTransition(final Node node) {
Path path = new Path();
path.getElements().add(new MoveToAbs(node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
path.getElements().add(new LineToAbs(node, node.getTranslateX(), node.getTranslateY()));
Duration duration = Duration.millis(1500);
PathTransition pt = new PathTransition(duration, path, node);
RotateTransition rt = new RotateTransition(duration, node);
rt.setByAngle(720);
rt.setAutoReverse(true);
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.setNode(node);
parallelTransition.getChildren().addAll(pt, rt);
return parallelTransition;
}
public static class MoveToAbs extends MoveTo {
public MoveToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static class LineToAbs extends LineTo {
public LineToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
/*
*/
public enum BillboardMode {
SPHERICAL,
CYLINDRICAL;
}
private class BillboardImage extends ImageView{
// Add transform to Node that needs to look at target..
public Affine affine = new Affine();
public BillboardImage() {
this.getTransforms().add(affine);
}
public BillboardImage(String url) {
super(url);
this.getTransforms().add(affine);
}
public BillboardImage(Image image) {
super(image);
this.getTransforms().add(affine);
}
// set up to look at camera, can change to any other Node
protected void updateMatrix(Node billBoardNode, Node other) {
Transform self = billBoardNode.getLocalToSceneTransform(),
oth = other.getLocalToSceneTransform();
Bounds b;
double cX, cY, cZ;
if (!(billBoardNode instanceof Shape3D)) {
b = billBoardNode.getBoundsInLocal();
cX = b.getWidth() / 2;
cY = b.getHeight() / 2;
cZ = b.getDepth() / 2;
} else {
cX = self.getTx();
cY = self.getTy();
cZ = self.getTz();
}
Point3D otherPos = Point3D.ZERO.add(oth.getTx(), oth.getTy(), oth.getTz());
Point3D selfPos = new Point3D(cX, cY, cZ);
Point3D up = Point3D.ZERO.add(0, -1, 0),
forward = new Point3D(
(selfPos.getX()) - otherPos.getX(),
(selfPos.getY()) - otherPos.getY(),
(selfPos.getZ()) - otherPos.getZ()
).normalize(),
right = up.crossProduct(forward).normalize();
up = forward.crossProduct(right).normalize();
switch (getBillboardMode()) {
case SPHERICAL:
affine.setMxx(right.getX()); affine.setMxy(up.getX());affine.setMzx(forward.getX());
affine.setMyx(right.getY());affine.setMyy(up.getY()); affine.setMzy(forward.getY());
affine.setMzx(right.getZ());affine.setMzy(up.getZ());affine.setMzz(forward.getZ());
affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
break;
case CYLINDRICAL:
affine.setMxx(right.getX());affine.setMxy(0);affine.setMzx(forward.getX());
affine.setMyx(0);affine.setMyy(1);affine.setMzy(0);
affine.setMzx(right.getZ()); affine.setMzy(0);affine.setMzz(forward.getZ());
affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
break;
}
}
public BillboardMode getBillboardMode() {
return BillboardMode.SPHERICAL;
}
}
}
At least now the Images are billboarded to the Camera..
Feel free to play with my FPSCamera as well ..
Controls are like any standard 1st person shooter
w = forward, s = back,
a = left-strafe, d = right-strafe,
q & e are up and down.