ive been using javafx recently and while i was doing so ive caught a problem in my code but spending a lot of time on it (mostly googling it) i cannot find the solution to my problem.
The problem happens when i try to rotate the camera using the x axis but instead of doing what i was wanting to happen instead it rotates around (0,0,0). Im not sure if it does rotate around (0,0,0) but that was the solution i could figure out. My camera starts staring at a cube but when tun left (adds 2 to the x axis) and the box goes a big circle. When turning right the box moves the other way around the circle. When applying the axis to the box, the box goes around fine.
My code is a bit messy but what i tried is to get the movement of turning and the box should go left if i turn right and right if i turn left as if in real life how it works.
public class javafx extends Application {
int ze = 0;
int ye = 0;
int xe = 0;
PerspectiveCamera cam = new PerspectiveCamera();
//the rotation angles//
Rotate rx = new Rotate();
{ rx.setAxis(Rotate.X_AXIS); }
Rotate ry = new Rotate();
{ ry.setAxis(Rotate.Y_AXIS); }
Rotate rz = new Rotate();
{ rz.setAxis(Rotate.Z_AXIS); }
int xt = 0;
int yt = 0;
int one;
int two;
boolean flip = false;
public static void addRotate(Node node, Point3D rotationAxis,
double angle) {
ObservableList<Transform> transforms = node.getTransforms();
try {
for (Transform t : transforms) {
rotationAxis = t.inverseDeltaTransform(rotationAxis);
}
} catch (NonInvertibleTransformException ex) {
throw new IllegalStateException(ex);
}
transforms.add(new Rotate(angle, rotationAxis));
}
public void start(Stage stage) {
Box cube = new Box();
cam.getTransforms().addAll(rx, rz, ry);
cube.setDepth(100.0);
cube.setHeight(100.0);
cube.setWidth(200.0);
cube.setCullFace(CullFace.BACK);
cube.setDrawMode(DrawMode.FILL);
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.BROWN);
cube.setMaterial(material);
cube.setTranslateX(1500.0);
cube.setTranslateY(500.0);
cube.setTranslateZ(0.0);
cam.setTranslateX(0);
cam.setTranslateY(0);
cam.setTranslateZ(0);
cam.setScaleX(2);
cam.setScaleY(2);
cam.setScaleZ(2);
Group root = new Group(cube, cam);
Dimension dimension =
Toolkit.getDefaultToolkit().getScreenSize();
double screenHeight = dimension.getHeight();
double screenWidth = dimension.getWidth();
Scene scene = new Scene(root, 0,0);
stage.setFullScreen(true);
scene.setCamera(cam);
stage.setTitle("3d space");
stage.setScene(scene);
stage.show();
stage.setOnHiding( event -> { System.exit(0);} );
//to get the input from a mouse vvvv //
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
Runnable toRun;
toRun = new Runnable() {
public void run() {
if(!flip){
one = MouseInfo.getPointerInfo().getLocation().x;
two = MouseInfo.getPointerInfo().getLocation().x;
flip = true;
}else{
flip = false;
if(one > MouseInfo.getPointerInfo().getLocation().x){
xt = xt +2;
ry.setAngle(xt);
System.out.println("left");
}else{
if(one < MouseInfo.getPointerInfo().getLocation().x){
System.out.println("right");
xt = xt -2;
ry.setAngle(xt);
}
}
}
}
};
ScheduledFuture<?> handle = scheduler.scheduleAtFixedRate(toRun, 1, 1, TimeUnit.MILLISECONDS);
}
public static void main(String args[]){
launch(args);
}
}
the answer is cam.set.pivot(222) to the area it circles around, also you can rotate everything around the camera
Related
While this stackoverflow post helps solve the drag issue
How to drag an undecorated window (stage) of JavaFX
It definitly doesn't let window snap work, is there some way i'm supposed to use this to get window snap working?
This is a single Class javafx App you can try . You can drag the stage with the green circle and close App with the red one . when stage.getX==0 is at the left of the screen and the app resize at maxHeight of the screen wich is in Screen.getPrimary().getBounds().getMaxY() and Screen.getPrimary().getBounds().getMaxX() divided by two to get half width of the screen . for the right side is a litte bit tricky when stage.getX() pluss the current width of the window matchs Screen.getPrimary().getBounds().getMaxY() means the right side of the window reaches the right side of the screen , but when the app is rezised in the right its x coordinate change , that will trigger the listener again , to avoid that I put a boolean boolean isInRightSide . You can comment and uncomment that boolean to see that behavior . Any other values of x position of the stage will reset to the original sizes .
public class App extends Application {
boolean isInRightSide = false;
double offsetX;
double offsetY;
int defaultWidth = 640;
int defaultHeight = 480;
#Override
public void start(Stage stage) {
double screenMaxX = Screen.getPrimary().getBounds().getWidth();
System.out.println(Screen.getPrimary().getBounds());
Circle dragCircle = new Circle(30, new Color(0, 1, 0, 1));
Circle closeCircle = new Circle(30, new Color(1, 0, 0, 1));
HBox hBox = new HBox(dragCircle, closeCircle);
closeCircle.setOnMouseClicked(e -> stage.close());
hBox.setAlignment(Pos.TOP_RIGHT);
dragCircle.setOnMousePressed(e -> {
offsetX = e.getSceneX();
offsetY = e.getSceneY();
}
);
dragCircle.setOnMouseDragged(e -> {
stage.setX(e.getScreenX() - offsetX);
stage.setY(e.getScreenY() - offsetY);
e.consume();
});
// sanap to left and right
stage.xProperty().addListener(e -> {
if (stage.getX() + defaultWidth > screenMaxX) {
stage.setX(screenMaxX - defaultWidth);
}
if (stage.getX() <= 0 && !stage.isFullScreen()) {
stage.setHeight(Screen.getPrimary().getBounds().getMaxY());
stage.setWidth(Screen.getPrimary().getBounds().getMaxX() / 2);
} else if (!isInRightSide) {
stage.setHeight(defaultHeight);
stage.setWidth(defaultWidth);
}
if (stage.getX() + defaultWidth == screenMaxX) {
isInRightSide = true;
stage.setHeight(Screen.getPrimary().getBounds().getMaxY());
stage.setWidth(Screen.getPrimary().getBounds().getMaxX() / 2);
} else {
isInRightSide = false;
}
});
stage.initStyle(StageStyle.UNDECORATED);
Scene scene = new Scene(new AnchorPane(hBox), defaultWidth, defaultHeight);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
app start
app snap when reaches right side
I'm teaching myself LibGdx and was following the simple game tutorial, unfortunately majority of the code is in one class. I want to refactor the code so I can use multiple textures for the rain that falls based on a random number.
I'll attach the Code for the main program and then the class I got started on.
So far everything worked except the Rain texture/img does not show on the screen.
public class GameScreen implements Screen {
public static FruitHarvest game;
protected final Texture dropImage;
//protected final Texture dropImage2;
private final Texture bucketImage;
public static Rectangle bucket;
public static Sound dropSound;
//private static Music rainMusic;
private final OrthographicCamera camera;
public static Array<Rectangle> raindrops;
private long lastDropTime;
public static int dropsGathered;
// private int random = MathUtils.random(0,1);
private Drops drop;
//Iterator<Rectangle> iterator = raindrops.iterator();
public GameScreen(final FruitHarvest game) {
this.game = game;
// load the images for the droplet and the bucket, 64x64 pixels each
dropImage = new Texture(Gdx.files.internal("droplet.png"));
//dropImage2 = new Texture(Gdx.files.internal("droplet1.png"));
bucketImage = new Texture(Gdx.files.internal("bucket.png"));
// load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
//rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
//rainMusic.setLooping(true);
// create the camera and the SpriteBatcher
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
// create a Rectangle to logically represent the bucket
bucket = new Rectangle();
bucket.x = 800 / 2 - 64 / 2; // Center the bucket horizontally
bucket.y = 20; // Bottom left corner of the bucket is 20 pixels above the bottom screen edge;
bucket.width = 64;
bucket.height = 64;
// Create the raindrops array and spawn the first raindrop
raindrops = new Array<Rectangle>();
long delta = 0;
drop = new Drops(dropImage, 64, 64, raindrops, delta);
}
#Override
public void render(float delta) {
// clear the screen with a dark blue color. The arguments to glClearColor are the
// red, green, blue, and alpha component in the range [0,1] of the color to be
// used to clear the screen
Gdx.gl.glClearColor(0, 0, .5f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// tell the camera to update its matrices.
camera.update();
// tell the SpriteBatch to render in the coordinate system specified by the camera.
game.batch.setProjectionMatrix(camera.combined);
// begin a new batch and draw the bucket and all drops
game.batch.begin();
game.font.draw(game.batch, "Drops collected: " + dropsGathered, 0, 480);
game.batch.draw(bucketImage, bucket.x, bucket.y, bucket.width, bucket.height);
// Draws the Items Falling
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
game.batch.end();
// process user input
if (Gdx.input.isTouched()) {
Vector3 touchPos = new Vector3();
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
bucket.x = touchPos.x - 64 / 2;
}
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
// make sure the bucket stays within the screen bounds
if (bucket.x < 0) bucket.x = 0;
if (bucket.x > 800 - 64) bucket.x = 800 - 64;
// check if we need to create a new raindrop
if (TimeUtils.nanoTime() - drop.getLastDropTime() > 1000000000) {
drop.spawnRaindrop();
}
// move the raindrops, remove any that are beneath the bottom edge of the screen
// or that hit the bucket. In the later case we increase the value our drops counter
// and add a sound effect.
Iterator<Rectangle> iter = raindrops.iterator();
drop.update(delta);
// while (iter.hasNext()) {
// Rectangle raindrop = iter.next();
// raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
// if (raindrop.y + 64 < 0) iter.remove();
// if (raindrop.overlaps(bucket)) {
// dropsGathered++;
// dropSound.play();
// iter.remove();
// }
// }
}
private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
// public void randomDrop(int value, float dropX, float dropY) {
// switch (value) {
// case 0:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// case 1:
// //game.batch.draw(dropImage2, dropX, dropY);
// break;
// default:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// }
// }
#Override
public void resize(int width, int height) {
}
#Override
public void show() {
// start the playback of the background music when the screen is shown
//rainMusic.play();
}
#Override
public void hide() {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
//rainMusic.dispose();
}
}
Heres my class for the drops
public class Drops {
private Rectangle raindrop;
private int imageHeight, imageWidth, x, y;
private Array<Rectangle> raindrops;
private long lastDropTime;
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
Iterator<Rectangle> iter = GameScreen.raindrops.iterator();
private float runTime = 0;
public Drops(Texture img, int imageHeight, int imageWidth, Array<Rectangle> drop, float delta) {
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
this.raindrops = drop;
this.dropImage = img;
}
public void update(float delta) {
while (iter.equals(true)) {
raindrop = iter.next();
raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
if (raindrop.y + 64 < 0) iter.remove();
onCollision();
}
}
public void onCollision() {
if (raindrop.overlaps(bucket)) {
GameScreen.dropsGathered++;
GameScreen.dropSound.play();
iter.remove();
}
}
public void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = imageWidth;
raindrop.height = imageHeight;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
public long getLastDropTime() {
return lastDropTime;
}
}
By drop.spawnRaindrop(); you add drops to Array<Rectangle> raindrops; in your Drops class but for drawing you use
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
Which will loop trough raindrop array list in your GameScreen which is empty.
So either draw the array list in drops or populate array list in GameScreen.
You need to be more careful as you refactor. You left behind your original Array of drop rectangles in your screen class, and you're drawing that (which is now empty). Then in your Drops class you are referencing the iterator for the now useless array in the screen class. And you're updating that empty array in the screen's render method.
Basically, the drops need to be handled in one place, but you're handling redundant arrays of drops in two different classes and getting them all mixed up.
It's not clear to me why you even have a class called Drops that tries to handle collisions with a bucket. There's no reason to move top-level game logic into a separate class, as that just complicates the code. If you had a more complicated game, it might make sense to have separate classes for tracking and updating various aspects of the game.
Incidentally, you're leaking a texture you load in this line:
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
because you never dispose of it before replacing the reference with another one in the constructor. In LibGDX, any object that implements Disposable must be disposed before its reference is lost, or it will leak native memory.
The straight-forward way to allow multiple drop images:
1) Go back to your original single class with all the game logic in the screen class.
2) Load your drop images into an array for easier access.
private final Array<Texture> dropImages = new Array<Texture>(); // replaces your dropImage declaration
//...
// in constructor:
dropImages.add(new Texture(Gdx.files.internal("droplet.png")));
dropImages.add(new Texture(Gdx.files.internal("droplet1.png")));
// etc. as many variations as you like
// don't forget to dispose of them:
#Override
public void dispose() {
for (Texture dropImage : dropImages) dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
}
3) Create a class Drop that extends Rectangle and has an additional parameter for the image type. You probably also want to make these sortable by image index to avoid swapping between Textures multiple times as you draw them, which causes batch flushes since you're not using a TextureAtlas.
public class Drop extends Rectangle implements Comparable<Drop>{
public int imageIndex;
public Drop (){
super();
}
public int compareTo(Drop otherDrop) {
return (int)Math.signum(imageIndex - otherDrop.imageIndex);
}
}
4) Change your Array<Rectangle> to Array<Drop>. When you spawn a drop, also give it a random image index:
private void spawnRaindrop() {
Drop raindrop = new Drop ();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrop.imageIndex = MathUtils.random(dropImages.size); // <-- HERE
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
5) When drawing your drops, use the drop's imageIndex to pull the correct texture. You can sort them first to avoid swapping the Texture back and forth:
// Draws the Items Falling
raindrops.sort();
for (Drop raindrop : raindrops) {
game.batch.draw(dropImages.get(raindrop.imageIndex), raindrop.x, raindrop.y);
}
My problem is with Z-Buffer in JavaFX 3D, it does not seem to work as intended on my machine.
I'am aware of questions:
Overlapping shapes
and
...Z Order...
However I do have Z-Buffer enabled, and nodes are still being rendered in the order they are added to the scenegraph.
Maybe I'm missing some dependencies or whatsoever?
I'm posting the code, I hope someone can help me. I'm creating a transition that moves the node around another on an elliptic path.
Thank you in advance!
public class OrbitExp extends Application {
Group root = new Group();
Scene scene = new Scene(root, 800, 600, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera();
#Override
public void start(Stage primaryStage) {
root.setDepthTest(DepthTest.ENABLE);
//Tried to set Depthtest explicitly. Assumed maybe it did not inherit:S
System.out.println(
"3D supported? " +
Platform.isSupported(ConditionalFeature.SCENE3D)
); // returns true
System.out.println("root z-buffer: " + root.getDepthTest());
initCamera();
Box
box1 = new Box(50,50,50),
box2 = new Box(10,10,10);
root.setTranslateX(scene.getWidth()/2);
root.setTranslateY(scene.getHeight()/2);
PhongMaterial
pmat = new PhongMaterial(Color.BLUE),
pmat2 = new PhongMaterial(Color.RED);
box1.setMaterial(pmat);
box2.setMaterial(pmat2);
scene.setFill(Color.LIGHTGREEN);
root.getChildren().addAll(box1,box2);
SequentialTransition sqt = orbit(box1, box2, 40, 40, Duration.seconds(3), 360);
sqt.play();
scene.setOnMouseClicked(click->{
Node node = (Node)(click.getPickResult().getIntersectedNode());
System.out.println("Tx: "+node.getTranslateX());
System.out.println("Ty: "+node.getTranslateY());
System.out.println("Tz: "+node.getTranslateZ());
});
// just for debugging, but coords does seem to be alright
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initCamera() {
camera.setTranslateZ(-50);
camera.setTranslateY(20);
camera.setFarClip(5000);
camera.setNearClip(0);
scene.setCamera(camera);
}
SequentialTransition orbit(Node node1, Node node2,double a, double b, Duration totalDuration, int N) {
SequentialTransition sqt = new SequentialTransition();
Duration dur = new Duration(totalDuration.toMillis()*(1.0d/N));
node2.setTranslateX(a+node1.getTranslateX());
node2.setTranslateZ(node1.getTranslateZ());
for (int i = 1; i < N; i++) {
TranslateTransition tt = new TranslateTransition(dur, node2);
double
angle = i*(360.0d/N),
toX = (Math.cos(Math.toRadians(angle))*a)+node1.getTranslateX(),
toZ = (Math.sin(Math.toRadians(angle))*b)+node1.getTranslateZ();
tt.setToX(toX);
tt.setToZ(toZ);
tt.setInterpolator(Interpolator.LINEAR);
sqt.getChildren().add(tt);
System.out.println("angle = " + angle + "\nangle in rads: " + Math.toRadians(angle) + "\ntoX = " + toX + "\ntoZ = " + toZ);
}
sqt.setCycleCount(Timeline.INDEFINITE);
return sqt;
}
}
This was my first post by the way:)
If you check the code on the link you provided using rectangles, depth buffer works fine.
Changing the rectangles to use 3D boxes works as well.
The issue is how you define the rotation of one of the boxes related to the other, so instead of using a RotateTransition or a SequentialTransition of TranslateTransition like you do, I've applied a Rotate transform to the red box setting a pivot in the center of the blue one, and used an AnimationTimer to modify the angle of that rotation to create the 'orbit' effect.
You can even use transparency on the big box (since 8u60) to see the small one beneath it.
private final Group shapes = new Group();
private long lastTimerCall;
private AnimationTimer timeline;
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createRotatingShapes(), 400, 300,
true, SceneAntialiasing.BALANCED);
scene.setFill(Color.LIGHTGREEN);
final PerspectiveCamera camera = new PerspectiveCamera();
camera.setRotationAxis(Rotate.X_AXIS);
camera.setRotate(10);
camera.setTranslateZ(200);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
private Group createRotatingShapes() {
final Box box1 = new Box(50, 50, 50);
// Transparency in box1: last node of the group
box1.setMaterial(new PhongMaterial(Color.web("#0000FF80")));
box1.setTranslateZ(50);
final Box box2 = new Box(10, 10, 10);
box2.setMaterial(new PhongMaterial(Color.RED));
box2.setTranslateZ(-50);
shapes.getChildren().addAll(box2, box1);
shapes.setTranslateX(200);
shapes.setTranslateY(150);
rotateAroundYAxis(box2);
return shapes;
}
private int count = 0;
private void rotateAroundYAxis(Node node) {
Rotate r = new Rotate(0, 0, 0, 100, Rotate.Y_AXIS);
node.getTransforms().add(r);
lastTimerCall = System.nanoTime();
timeline = new AnimationTimer() {
#Override public void handle(long now) {
if (now > lastTimerCall + 100_000_000l) {
r.setAngle((count++)%360);
}
}
};
timeline.start();
}
#Override
public void stop() {
timeline.stop();
}
In front of the box:
Behind the blue box:
EDIT
If you have a look to the Camera JavaDoc for nearClip:
Specifies the distance from the eye of the near clipping plane of
this Camera in the eye coordinate space.
Objects closer to the eye than nearClip are not drawn.
nearClip is specified as a value greater than zero. A value less
than or equal to zero is treated as a very small positive number.
(bold is mine).
So the problem with your code was this line:
camera.setNearClip(0);
Just change it to:
camera.setNearClip(0.01);
and it will work as you expected.
This is my second post on Mandelbrot fractal conversion from Java to C#.
As per my assignment, I need to Draw a mandelbrot fractal on a form, and once it is drawn, allow the user to Zoom in using the mouse, while also drawing a rectangle from the initial click point to the point where the click is released. This is the part of code which i believe is responsible for the rectangle.
private static void Swap<T>(ref T t1, ref T t2)
{
T temp = t1;
t1 = t2;
t2 = t1;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g1 = e.Graphics;
g1.DrawImage(bitmap, 0, 0, x1, y1);
if (action)
{
//g.setColor(Color.White);
if (xe < xs)
{
Swap(ref xs, ref xe);
}
if (ye < ys)
{
Swap(ref ys, ref ye);
}
g1.DrawRectangle(Pens.White, xs, ys, (xe - xs), (ye - ys));
//g1.Dispose();
}
}
//load method here
private void Form1_Load(object sender, EventArgs e)
//while loading
{
init();
start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (action)
{
xe = e.X;
ye = e.Y;
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
action = true;
// e.consume();
if (action)
{
xs = xe = e.X;
ys = ye = e.Y;
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
using (Graphics g = this.CreateGraphics())
{
Pen pen = new Pen(Color.White);
g.DrawRectangle(pen, xs, ys, Math.Abs(xs - xe), Math.Abs(ys - ye));
}
int z, w;
if (xs > xe)
{
z = xs;
xs = xe;
xe = z;
}
if (ys > ye)
{
z = ys;
ys = ye;
ye = z;
}
w = (xe - xs);
z = (ye - ys);
if ((w < 2) && (z < 2)) initvalues();
else
{
if (((float)w > (float)z * xy)) ye = (int)((float)ys + (float)w / xy);
else xe = (int)((float)xs + (float)z * xy);
xende = xstart + xzoom * (double)xe;
yende = ystart + yzoom * (double)ye;
xstart += xzoom * (double)xs;
ystart += yzoom * (double)ys;
}
xzoom = (xende - xstart) / (double)x1;
yzoom = (yende - ystart) / (double)y1;
mandelbrot();
this.Invalidate();
}
What the code does is, draw a rectangle AFTER the dragging is done, and then zoom in with the drawn rectangle still being displayed. What I needed is the rectangle to draw as the mouse is being dragged.
I referred to this question, and solution mentioned there did not help.
Java to C# conversion. How do i draw a rectangle on my bitmap?
Any help would be appreciated.
Drawing the Rectangle
First of all, it appears that the Graphics.DrawRectangle method is unable to draw a rectangle with negative widths or heights. You will therefore have to write a method that will take two points and produce a rectangle meeting the requirements (positive width and height).
private Rectangle CreateRectangle(Point pt1, Point pt2)
{
// we use this method to create the rectangle with positive width and height
int x1 = Math.Min(pt1.X, pt2.X);
int y1 = Math.Min(pt1.Y, pt2.Y);
return new Rectangle(x1, y1, Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
}
Second, in your event handler for the MouseDown event, record the position at which the mouse was held down.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
this.startPoint = e.Location;// record the start position
}
Next, modify your mouse move method to update the variable that holds current location of the mouse. Additionally, make it invalidate the form so that the image is redrawn (along with the rectangle).
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// record the current position as the end point if the left button is down
this.endPoint = e.Location;
// force a redraw
this.Invalidate();
}
}
In the form's Paint event handler, make your code call the CreateRectangle method with the start and end points of the rectangle in order to draw the rectangle on the form.
private void Form1_Paint(object sender, PaintEventArgs e)
{
// draw the cached Mandelbrot image
e.Graphics.DrawImage(mandelbrotCache, new Point(0, 0));
// draw the current rectangle
e.Graphics.DrawRectangle(rectPen, CreateRectangle(startPoint, endPoint));
}
Finally, in order to remove the rectangle when the mouse button is no longer pressed, set startPoint and endPoint to a value that gets drawn outside the image. This should be done in the MouseUp event handler.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// setting the point to -1,-1 makes them get drawn off the screen
startPoint = new Point(-1, -1);
endPoint = new Point(-1, -1);
// force an update so that the rectangle disappears
this.Invalidate();
}
}
Addressing the Flickering Issue
In order to stop the form from flickering while you're drawing to it, you will need to enable double buffering on the form. This is done by setting the DoubleBuffered property of the form to true. You can do this anywhere, but I prefer to do it right after the form is created, as below:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// this reduces the flickering
this.DoubleBuffered = true;
}
}
Complete Code:
Here is the complete code for all the steps I detailed above. You can plug in your methods in order to have a working solution.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Point startPoint;
private Point endPoint;
private Image mandelbrotCache;
private Pen rectPen;
public Form1()
{
InitializeComponent();
// this reduces the flickering
this.DoubleBuffered = true;
// initialize a dummy image. Cache a copy of your Mandelbrot fractal here
mandelbrotCache = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
using (var g = Graphics.FromImage(mandelbrotCache))
{
var imgRect = new Rectangle(0, 0,
mandelbrotCache.Width,
mandelbrotCache.Height);
g.FillRectangle(new HatchBrush(HatchStyle.Cross, Color.DarkBlue,
Color.LightBlue), imgRect);
}
// this is the pen to draw the rectangle with
rectPen = new Pen(Color.Red, 3);
}
private Rectangle CreateRectangle(Point pt1, Point pt2)
{
// we use this method to create a rectangle with positive width and height
int x1 = Math.Min(pt1.X, pt2.X);
int y1 = Math.Min(pt1.Y, pt2.Y);
return new Rectangle(x1, y1, Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
this.startPoint = e.Location;// record the start position
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// record the current position as the end point if the left button is down
this.endPoint = e.Location;
// force a redraw
this.Invalidate();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// setting the point to -1,-1 makes them get drawn off the screen
startPoint = new Point(-1, -1);
endPoint = new Point(-1, -1);
// force an update so that the rectangle disappears
this.Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// draw the cached Mandelbrot image
e.Graphics.DrawImage(mandelbrotCache, new Point(0, 0));
// draw the current rectangle
e.Graphics.DrawRectangle(rectPen, CreateRectangle(startPoint, endPoint));
}
}
}
Here is a screenshot of a rectangle being drawn.
Note: The left mouse button is still held down. The rectangle disappears immediately the button is released.
After a lot a investigations, I don't achieve to find a convenient answer to the following question: how to programatically pan a VisualizationViewer with Jung?
I have a GUI with the list of the vertices of my graph, and I want that a double click on one item of the list (i.e. a node description) perform a "centering action" of my VisualizationViewer for the clicked node.
How to code such a behavior? it seems simple but I found no convenient answer.
If someone could help, thanks!
njames
Here is how to popup a menu after right-clicking on a node in JUNG2 and later choose to center to this node:
graphMouse.add(new MyPopupGraphMousePlugin());
/**
* a GraphMousePlugin that offers popup
* menu support
*/
protected class MyPopupGraphMousePlugin extends AbstractPopupGraphMousePlugin
implements MouseListener {
public MyPopupGraphMousePlugin() {
this(MouseEvent.BUTTON3_MASK);
}
public MyPopupGraphMousePlugin(int modifiers) {
super(modifiers);
}
/**
* If this event is over a node, pop up a menu to
* allow the user to center view to the node
*
* #param e
*/
protected void handlePopup(MouseEvent e) {
final VisualizationViewer<Node,Link> vv =
(VisualizationViewer<Node,Link>)e.getSource();
final Point2D p = e.getPoint();
GraphElementAccessor<Node,Link> pickSupport = vv.getPickSupport();
if(pickSupport != null) {
final Node station = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY());
if(station != null) {
JPopupMenu popup = new JPopupMenu();
String center = "Center to Node";
popup.add(new AbstractAction("<html><center>" + center) {
public void actionPerformed(ActionEvent e) {
MutableTransformer view = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
MutableTransformer layout = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
Point2D ctr = vv.getCenter();
Point2D pnt = view.inverseTransform(ctr);
double scale = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale();
double deltaX = (ctr.getX() - p.getX())*1/scale;
double deltaY = (ctr.getY() - p.getY())*1/scale;
Point2D delta = new Point2D.Double(deltaX, deltaY);
layout.translate(deltaX, deltaY);
}
});
popup.show(vv, e.getX(), e.getY());
} else {
}
}
}
}
Edited: Finally! the correct node-to-center-view with scale factor calculation...
public void centerViewsOnVertex(SynsetVertex v) {
//the following location have sense in the space of the layout
Point2D v_location = layout.transform(v);
Point2D vv1_center_location = vv1.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(Layer.LAYOUT, vv1.getCenter());
double scale = vv1.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale();
vv1.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(
-(v_location.getX() - vv1_center_location.getX()) * 1
/ scale,
-(v_location.getY() - vv1_center_location.getY()) * 1
/ scale);
refreshViews();
}
Since I was just looking for an answer to this and the above worked terribly; here's a code snippet I found in AnimatedPickingGraphMousePlugin that will recenter:
Layout<V,E> layout = vv.getGraphLayout();
Point2D q = layout.transform(vertex);
Point2D lvc =
vv.getRenderContext().getMultiLayerTransformer().inverseTransform(vv.getCenter());
final double dx = (lvc.getX() - q.getX());
final double dy = (lvc.getY() - q.getY());
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx, dy);
Actually, I have found by the next a solution:
//the following location have sense in the space of the layout
Point2D v_location = layout.transform(v);
Point2D vv1_center_location = vv1.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(Layer.LAYOUT, vv1.getCenter());
vv1.getRenderContext()
.getMultiLayerTransformer()
.getTransformer(Layer.LAYOUT)
.translate(-(v_location.getX() - vv1_center_location.getX()),
-(v_location.getY() - vv1_center_location.getY()));
The center on node function is already implemented in the AnimatedPickingGraphMousePlugin
http://sourceforge.net/p/jung/discussion/252062/thread/18b4b941
In picking mode ctrl+click on a vertex to center on it.
#SuppressWarnings("unchecked")
public void mouseReleased(MouseEvent e) {
if (e.getModifiers() == modifiers) {
final VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>) e.getSource();
if (vertex != null) {
Layout<V,E> layout = vv.getGraphLayout();
Point2D q = layout.transform(vertex);
Point2D lvc = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(vv.getCenter());
final double dx = (lvc.getX() - q.getX()) / 10;
final double dy = (lvc.getY() - q.getY()) / 10;
Runnable animator = new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx, dy);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
}
}
};
Thread thread = new Thread(animator);
thread.start();
}
}
}
I was trying to figure out how to center on a given vertex and came across this, but unfortunately it was not particularly helpful and I spent a fair amount of time figuring out how to do it. So, sharing my experience here in case it may be helpful for others.
The application I'm writing has a VisualizationViewer, that is loaded inside a GraphZoomScrollPane. I have a GraphMouseListener that I've added to the VisualizationViewer, which allows a user to right click on a vertex in the viewable area of the scroll pane and in the popup menu they can choose to center on the vertex.
The top voted answer on this thread references usage of a MutableTransformer from the LAYOUT layer and it uses the translate method of that transformer to do the centering action. Unfortunately, if you are using a zoom/scroll then you don't really know the size and positioning of the layout layer in relation to the view layer without doing a bunch of extra math.
When using zoom/scroll pane, I'd recommend finding the location of the vertex in the viewable area of the graph as represented by the pane, and then adjusting where the view pane is at.
Here is a snippet of the code I worked out:
void center(MouseEvent me, GraphZoomScrollPane gzsp) {
VisualizationViewer<V,E> vv =
(VisualizationViewer<V,E>)me.getSource();
MutableTransformer viewTransformer =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
double scaleFromViewTransformer = viewTransformer.getScale();
Dimension paneSize = gzsp.getSize();
Point2D positionOfVertexInPane = me.getPoint();
double[] centerOfPane = new double[] {
paneSize.getWidth()/2d,
paneSize.getHeight()/2d
};
double[] amountToMovePane = new double[] {
(centerOfPane[0]-positionOfVertexInPane.getX())/scaleFromViewTransformer,
(centerOfPane[1]-positionOfVertexInPane.getY())/scaleFromViewTransformer
};
viewTransformer.translate(amountToMovePane[0], amountToMovePane[1]);
}