Moving the camera using a touch input Cocos2D android - java

I'm trying to make a camera for an android app that moves by dragging on the touch screen to drag the camera across. I'm using the Cocos2D engine for my development.
The problem is, whenever you moved your finger on the screen, everything on the screen just disappears instead of moving.
My code is below, I hope someone can help me with this :) Thanks for your time.
#Override
public boolean ccTouchesMoved(MotionEvent event)
{
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
CGPoint movement = CGPoint.ccpSub(location, previousLocation);
previousLocation = location;
//Update the camera
float[] x = new float[1];
float[] y = new float[1];
float[] z = new float[1];
this.getCamera().getCenter(x, y, z);
CameraPos.x = x[0];
CameraPos.y = y[0];
this.getCamera().getEye(x, y, z);
movement.x = 2 * movement.x * (1 + (z[0]/832));
movement.y = 2 * movement.y * (1 + (z[0]/832));
CameraPos.x = CameraPos.x - Math.round(movement.x);
CameraPos.y = CameraPos.y - Math.round(movement.y);
this.getCamera().setCenter(CameraPos.x, CameraPos.y, 0);
this.getCamera().setEye(CameraPos.x, CameraPos.y, z[0]);
return true;
}

Its fine I got it working nevertheless.
For any who want to know this, I created a class called CameraControls that controls the basic functions of the camera. It isnt finished yet (I'll probably update the code as I make changes such as zoom functionality) but this one allows me to track the touch input perfectly.
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGSize;
public class CameraControls {
CGSize winSize = CCDirector.sharedDirector().displaySize();
CGPoint CameraPos = CGPoint.ccp(winSize.width, winSize.height);
CGPoint previousLocation;
double minX;
double maxX;
double minY;
double maxY;
public CameraControls(World world)
{
this.loadCamera(world);
}
public void setCameraLimit(float minX, float maxX, float minY, float maxY)
{
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
public void loadCamera(World world)
{
float[] x = new float[1];
float[] y = new float[1];
float[] z = new float[1];
world.getCamera().getCenter(x, y, z);
CameraPos.x = x[0];
CameraPos.y = y[0];
}
public void trackTouchMovement(CGPoint location, World world)
{
if(previousLocation == null)
{
previousLocation = location;
}
CGPoint movement = CGPoint.ccpSub(previousLocation, location);
previousLocation = location;
float[] x1 = new float[1];
float[] y1 = new float[1];
float[] z1 = new float[1];
world.getCamera().getEye(x1, y1, z1);
CameraPos.x = CameraPos.x + movement.x;
CameraPos.y = CameraPos.y + movement.y;
try
{
if(CameraPos.x >= maxX || CameraPos.x <= minX || CameraPos.y >= maxY || CameraPos.y <= minY)
{
CameraPos = CGPoint.ccpSub(CameraPos, movement);
}
}
catch (NullPointerException e)
{
System.out.println("Invalid values for camera Limits. No Limits applied.");
}
world.getCamera().setCenter(CameraPos.x, CameraPos.y, 0);
world.getCamera().setEye(CameraPos.x, CameraPos.y, z1[0]);
}
public void storePositionAsPrevious(CGPoint pos)
{
previousLocation = pos;
}
public void resetPrevious()
{
previousLocation = null;
}
}
Now that I have a class, I simply create an instance of CameraControls in my class and then do the necessary configuration.
CameraControls camera = new CameraControls(this);
In this case, I want the total area my camera can view to be 3 x the width of the camera, and 3 times the height of the camera, so I set the limits of the camera as the negative width of the camera, the width of the camera, the negative height of the camera and the height of the camera, as the camera starts at (0, 0).
camera.setCameraLimit(-winSize.width, winSize.width, -winSize.height, winSize.height);
Finally, I just add the necessary method calling in ccTouchesBegan, ccTouchesMoved and ccTouchesEnded
#Override
public boolean ccTouchesMoved(MotionEvent event)
{
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
camera.trackTouchMovement(location, this);
return true;
}
#Override
public boolean ccTouchesEnded(MotionEvent event)
{
camera.resetPrevious();
return true;
}
#Override
public boolean ccTouchesBegan(MotionEvent event)
{
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
camera.storePositionAsPrevious(location);
return true;
}

Related

Strange PolyBatch Positioning in libGDX

Excuse the basic question, just getting into the guts of LibGDX
I'm creating a radial bar to show my countdown timer
I've found some code that does what I need it to, the problem is the radial sprite's positioning. I can't seem to get it to center in the Image object (Since it seems to be ignoring the Image's local coordinates and is defaulting to the stage's) so 0,0 places it close to the bottom left of my screen.
I've tried using a localtoStage and vice versa to calculate the correct positions, but that doesn't seem to give me the right values either.
Please advise
package com.goplayplay.klpoker.CSS.Classes;
import com.badlogic.gdx.graphics.g2d.*;
import com.badlogic.gdx.math.EarClippingTriangulator;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.utils.ShortArray;
public class ProgressCircle extends Image {
TextureRegion texture;
PolygonSpriteBatch polyBatch;
Vector2 center;
Vector2 centerTop;
Vector2 leftTop;
Vector2 leftBottom;
Vector2 rightBottom;
Vector2 rightTop;
Vector2 progressPoint;
float[] fv;
IntersectAt intersectAt;
public ProgressCircle(TextureRegion region, PolygonSpriteBatch polyBatch) {
super(region);
this.texture = region;
this.polyBatch = polyBatch;
center = new Vector2(this.getWidth() / 2, this.getHeight() / 2);
centerTop = new Vector2(this.getWidth() / 2, this.getHeight());
leftTop = new Vector2(0, this.getHeight());
leftBottom = new Vector2(0, 0);
rightBottom = new Vector2(this.getWidth(), 0);
rightTop = new Vector2(this.getWidth(), this.getHeight());
progressPoint = new Vector2(this.getWidth() / 2, this.getHeight() / 2);
setPercentage(0);
}
private Vector2 IntersectPoint(Vector2 line) {
Vector2 v = new Vector2();
boolean isIntersect;
//check top
isIntersect = Intersector.intersectSegments(leftTop, rightTop, center, line, v);
//check bottom
if (isIntersect) {
intersectAt = IntersectAt.TOP;
return v;
} else isIntersect = Intersector.intersectSegments(leftBottom, rightBottom, center, line, v);
//check left
if (isIntersect) {
intersectAt = IntersectAt.BOTTOM;
return v;
} else isIntersect = Intersector.intersectSegments(leftTop, leftBottom, center, line, v);
//check bottom
if (isIntersect) {
intersectAt = IntersectAt.LEFT;
return v;
} else isIntersect = Intersector.intersectSegments(rightTop, rightBottom, center, line, v);
if (isIntersect) {
intersectAt = IntersectAt.RIGHT;
return v;
} else {
intersectAt = IntersectAt.NONE;
return null;
}
}
public void setPercentage(float percent) {
//100 % = 360 degree
//==> percent % => (percent * 360 / 100) degree
float angle = convertToRadians(90); //percent = 0 => angle = -90
angle -= convertToRadians(percent * 360 / 100);
float len = this.getWidth() > this.getHeight() ? this.getWidth() : this.getHeight();
float dy = (float) (Math.sin(angle) * len);
float dx = (float) (Math.cos(angle) * len);
Vector2 line = new Vector2(center.x + dx, center.y + dy);
Vector2 v = IntersectPoint(line);
if (intersectAt == IntersectAt.TOP) {
if (v.x >= this.getWidth() / 2)
{
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
rightBottom.x,
rightBottom.y,
rightTop.x,
rightTop.y,
v.x,
v.y
};
} else {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
v.x,
v.y
};
}
} else if (intersectAt == IntersectAt.BOTTOM) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
v.x,
v.y
};
} else if (intersectAt == IntersectAt.LEFT) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
v.x,
v.y
};
} else if (intersectAt == IntersectAt.RIGHT) {
fv = new float[]{
center.x,
center.y,
centerTop.x,
centerTop.y,
leftTop.x,
leftTop.y,
leftBottom.x,
leftBottom.y,
rightBottom.x,
rightBottom.y,
v.x,
v.y
};
} else // if (intersectAt == IntersectAt.NONE)
{
fv = null;
}
}
//
#Override
public void draw(Batch batch, float parentAlpha) {
// super.draw(batch, parentAlpha);
if (fv == null) return;
batch.end();
drawMe();
batch.begin();
}
public void drawMe() {
Vector2 acc = new Vector2();
acc.set(getWidth() / 2, getHeight() / 2);
localToStageCoordinates(acc);
EarClippingTriangulator e = new EarClippingTriangulator();
ShortArray sv = e.computeTriangles(fv);
PolygonRegion polyReg = new PolygonRegion(texture, fv, sv.toArray());
PolygonSprite poly = new PolygonSprite(polyReg);
poly.setOrigin(this.getOriginX(), this.getOriginY());
poly.setPosition(this.getX(), this.getY());
// poly.setPosition(acc.x, acc.y); //Attempting to calculate correct positioning - Doesnt work
poly.setRotation(this.getRotation());
poly.setColor(this.getColor());
polyBatch.begin();
poly.draw(polyBatch);
polyBatch.end();
}
float convertToDegrees(float angleInRadians) {
float angleInDegrees = angleInRadians * 57.2957795f;
return angleInDegrees;
}
//-----------------------------------------------------------------
float convertToRadians(float angleInDegrees) {
float angleInRadians = angleInDegrees * 0.0174532925f;
return angleInRadians;
}
public enum IntersectAt {
NONE, TOP, BOTTOM, LEFT, RIGHT
}
}
You forgot to set the camera's projection matrix on the polygon batch. You can get a copy of it from the Batch that's passed in:
public void draw(Batch batch, float parentAlpha) {
// super.draw(batch, parentAlpha);
if (fv == null) return;
batch.end();
drawMe(batch.getProjectionMatrix());
batch.begin();
}
public void drawMe(Matrix4 projection) {
polyBatch.setProjectionMatrix(projection);
//...
}
Or more simply, you can use a PolygonBatch as your Stage's batch, so you don't have to be swapping batches:
stage = new Stage(myViewport, new PolygonBatch());
//...
public void draw(Batch batch, float parentAlpha) {
// super.draw(batch, parentAlpha);
if (fv == null) return;
//don't need to call begin and end on the batch
drawMe((PolygonBatch)batch);
}
public void drawMe(PolygonBatch polyBatch) {
//...
//don't need to call begin or end on the batch
}
By the way, your drawMe method instantiates quite a few objects, some large. You should avoid this if you have more than a few actors that do this, or you'll get stutters from the GC. Try to instantiate objects only once in the constructor and reuse them.

How to make sprite bounce of the sides of the screen

I'm using libgdx in java android studio. i have just started. i'm working on android phone. i not using any cameras. all i want is a sprite bounce of all four sides of the screen without tapping. i tried many codes i thought would work but nope. i hope u guys can help me. I'm expecting an answer soon as possible. Thanks
this is what i have:
SpriteBatch batch;
Texture background;
Sprite backgroundsprite;
Sprite ballsprite;
Texture line;
Texture ballimg;
BitmapFont credits;
BitmapFont input;
BitmapFont play;
float dt;
String string = "";
float ballx;
float bally;
float speedx;
float speedy;
Rectangle screenrect;
Rectangle ballrect;
float screenLeft ;
float screenBottom ;
float screenTop ;
float screenRight ;
#Override
public void create() {
batch = new SpriteBatch();
speedx = 5f * dt;
speedy = 5f * dt;
createsprite();
createbackground();
createtext();
ballx = ballsprite.getX();
bally = ballsprite.getY();
}
#Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
dt = Gdx.graphics.getDeltaTime();
ballsprite.setPosition(ballx + speedx,ballsprite.getY());
ballsprite.translateX(speedx);
float left = ballrect.getX();
float bottom = ballrect.getY();
float top = bottom + ballrect.getHeight();
float right = left + ballrect.getWidth();
if(left < screenLeft) {
string = "left";
speedx = 5f*dt;
}
if(right > screenRight)
{
string = "right";
speedx = -5f*dt;
}
if(bottom < screenBottom)
{
string = "bottom";
}
if(top > screenTop)
{
string = "top";
}
batch.begin();
backgroundsprite.draw(batch);
ballsprite.draw(batch);
rendertext();
batch.end();
}
public void createbackground() {
background = new Texture("images/BackgroundGodwin.jpg");
backgroundsprite = new Sprite(background);
backgroundsprite.setSize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
screenrect = new Rectangle(0,0,Gdx.graphics.getWidth(),Gdx.graphics.getHeight());
screenLeft = screenrect.getX();
screenBottom = screenrect.getY();
screenTop = screenBottom + screenrect.getHeight();
screenRight = screenLeft + screenrect.getWidth();
}
public void createsprite() {
ballimg = new Texture("images/SpriteGodwin.png");
ballsprite = new Sprite(ballimg);
ballsprite.setScale(0.65f);
ballsprite.setPosition(Gdx.graphics.getWidth()/3,Gdx.graphics.getHeight()/2);
ballrect = new Rectangle(ballsprite.getBoundingRectangle());
}
#Override
public void dispose() {
batch.dispose();
ballimg.dispose();
background.dispose();
credits.dispose();
play.dispose();
input.dispose();
line.dispose();
}
public void createtext(){
play = new BitmapFont(Gdx.files.internal("fonts/realfont.fnt"));
play.setColor(com.badlogic.gdx.graphics.Color.GOLD);
credits = new BitmapFont(Gdx.files.internal("fonts/realfont.fnt"));
credits.setColor(com.badlogic.gdx.graphics.Color.GOLD);
input = new BitmapFont(Gdx.files.internal("fonts/realfont.fnt"));
input.setColor(com.badlogic.gdx.graphics.Color.OLIVE);
}
public void rendertext() {
credits.draw(batch, "Maded", Gdx.graphics.getWidth() / 7 - 50, Gdx.graphics.getHeight() / 9);
play.draw(batch, "Touch the Screen to play!!", Gdx.graphics.getWidth() / 2 - 175, Gdx.graphics.getHeight() - 80);
input.draw(batch, string, Gdx.graphics.getWidth() / 2 - 160, Gdx.graphics.getHeight() - 120);
}
}
I made a very simple version of what you want:
public class BouncyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture ball;
float speedX = 3f;
float speedY = 3f;
int x;
int y;
#Override
public void create () {
batch = new SpriteBatch();
ball = new Texture("ball.png");
x = Gdx.graphics.getWidth()/2;
y = Gdx.graphics.getHeight()/2;
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//When the ball's x position is on either side of the screen.
//The width of the sprite is taken into account.
if (x > Gdx.graphics.getWidth() - ball.getWidth()/2 || x < 0 + ball.getWidth()/2) {
//Here we flip the speed, so it bonces the other way.
speedX = -speedX;
}
//Same as above, but with on the y-axis.
if (y > Gdx.graphics.getHeight() - ball.getHeight()/2 || y < 0 + ball.getHeight()/2) {
speedY = -speedY;
}
//Move the ball according to the speed.
x += speedX;
y += speedY;
batch.begin();
//Draw the ball so the center is at x and y. Normally it would be drawn from the lower left corner.
batch.draw(ball, x - ball.getWidth()/2, y - ball.getHeight()/2);
batch.end();
}
}
It will result in the following:
http://gfycat.com/TatteredCarefreeHapuku
There are numerous ways to improve this code, you could for example use vectors, and I wouldn't recommend using it in your final product, but it might help you figure out how to do something like this for your own project.

Zoom Camera2 Preview using TextureView

i have a Problem with my Preview Zoom for the Camera2 API. I am using a TextureView.
I want to zoom only the preview Stream that was showed in the TextureView.
I want to zoom the Area where i use the Zoom Gesture.
I use the SimpleOnScaleGestureListener!
I added following Code. The zoomingFactor and the x and y Position are right.
private void updateTextureViewSize(float xPosi,float yPosi, float scale){
float scaleX = 1.0f;
float scaleY = 1.0f;
float mVideoWidth = mCamcontrol.getmPreviewSize().getWidth();
float mVideoHeight = mCamcontrol.getmPreviewSize().getHeight();
int rotation = getWindowManager().getDefaultDisplay().getRotation();
RectF viewRect = new RectF(0, 0, 1440, 2560);
RectF bufferRect = new RectF(0, 0, mVideoHeight, mVideoWidth);
bufferRect.offset(xPosi - bufferRect.centerX(), yPosi - bufferRect.centerY());
//16:9 faktor
scaleX = ((mScale * scale) / 9f) * 16f;
scaleY = ((mScale * scale) / 16f) * 9f;
Matrix matrix = new Matrix();
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.FILL);
scalefactorView.setText(String.valueOf(xPosi) + " " + String.valueOf(yPosi));
matrix.setScale(scaleY, scaleX, xPosi, yPosi);
matrix.postRotate(90 * (rotation - 2), xPosi, yPosi);
mTextureView.setTransform(matrix);
}
Zooming is Right, but not the Position where i Zoom. For Example! When i zoom on the position right/middle i see only the left/top rectangle of the Stream.
I added the following pictures to unterstand the problem.
Android Camera2 api : Pinch Zoom In/Out
Use this sample code for Camera2Basic from google developers. https://github.com/googlesamples/android-Camera2Basic
Now declare two class variables –
public float finger_spacing = 0;
public int zoom_level = 1;
and update the given onTouch() method.
public boolean onTouch(View v, MotionEvent event) {
try {
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
float maxzoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM))*10;
Rect m = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
int action = event.getAction();
float current_finger_spacing;
if (event.getPointerCount() > 1) {
// Multi touch logic
current_finger_spacing = getFingerSpacing(event);
if(finger_spacing != 0){
if(current_finger_spacing > finger_spacing && maxzoom > zoom_level){
zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1){
zoom_level--;
}
int minW = (int) (m.width() / maxzoom);
int minH = (int) (m.height() / maxzoom);
int difW = m.width() - minW;
int difH = m.height() - minH;
int cropW = difW /100 *(int)zoom_level;
int cropH = difH /100 *(int)zoom_level;
cropW -= cropW & 3;
cropH -= cropH & 3;
Rect zoom = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
finger_spacing = current_finger_spacing;
} else{
if (action == MotionEvent.ACTION_UP) {
//single touch logic
}
}
try {
mCaptureSession
.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException ex) {
ex.printStackTrace();
}
} catch (CameraAccessException e) {
throw new RuntimeException("can not access camera.", e);
}
return true;
}
//Determine the space between the first two fingers
#SuppressWarnings("deprecation")
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Thanks to #arin 's answer, I made an improved version.
His code is basically working, but there are 2 problems:
1) Readability - actually I don't know what is going on calculating the Rect zoom
2) In my Android 7.1.1 device, the preview will freeze if the zoom is big to a certain extent. Since I solved this problem with the code below, I am pretty sure it is because the original code allowed over-zooming beyond camera's maximum zoom ratio.
(In fact, I don't know why he needs to apply *10 on the ratio returned by CameraCharacteristics)
Below are my codes: (I do this all inside my custom TextureView, which also stores my Camera2 objects and logics):
Related Member variables:
protected CameraCharacteristics cameraCharacteristics;
protected CameraCaptureSession captureSession;
protected CaptureRequest.Builder previewRequestBuilder;
//Zooming
protected float fingerSpacing = 0;
protected float zoomLevel = 1f;
protected float maximumZoomLevel;
protected Rect zoom;
Right after you get CameraCharacteristics from CameraManager, probably in some initial setup:
maximumZoomLevel = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
override onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
try {
Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (rect == null) return false;
float currentFingerSpacing;
if (event.getPointerCount() == 2) { //Multi touch.
currentFingerSpacing = getFingerSpacing(event);
float delta = 0.05f; //Control this value to control the zooming sensibility
if (fingerSpacing != 0) {
if (currentFingerSpacing > fingerSpacing) { //Don't over zoom-in
if ((maximumZoomLevel - zoomLevel) <= delta) {
delta = maximumZoomLevel - zoomLevel;
}
zoomLevel = zoomLevel + delta;
} else if (currentFingerSpacing < fingerSpacing){ //Don't over zoom-out
if ((zoomLevel - delta) < 1f) {
delta = zoomLevel - 1f;
}
zoomLevel = zoomLevel - delta;
}
float ratio = (float) 1 / zoomLevel; //This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect
//croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped
int croppedWidth = rect.width() - Math.round((float)rect.width() * ratio);
int croppedHeight = rect.height() - Math.round((float)rect.height() * ratio);
//Finally, zoom represents the zoomed visible area
zoom = new Rect(croppedWidth/2, croppedHeight/2,
rect.width() - croppedWidth/2, rect.height() - croppedHeight/2);
previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
fingerSpacing = currentFingerSpacing;
} else { //Single touch point, needs to return true in order to detect one more touch point
return true;
}
captureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallback, null);
return true;
} catch (final Exception e) {
//Error handling up to you
return true;
}
}
And the getFingerSpacing method:
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Finally don't forget to set the crop region when you actually take the photo. My code is base on this Camera2Basic, I do this inside the captureStillPicture() method:
//Zoom
if (zoom != null) {
captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
#arin Answer is working thank #arin just one thing zoom sensitivity too high.
To control this i make some changes in might be useful to you.
Change zoom_level data type to double
public int zoom_level = 1; to public double zoom_level = 1;
Then increase or decrease zoom_level with low value i use 0.4
if (current_finger_spacing > finger_spacing && maxzoom > zoom_level) {
zoom_level = zoom_level + .4;
//zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1) {
zoom_level = zoom_level - .4;
//zoom_level--;
}
Here is a Pan and Zoom object from Camera2 that I made to work using the OnScaleGestureListener and SimpleOnGestureListener-onScroll outputs. This will only work as expected if you have a camera with support level > LEGACY, as LEGACY only supports crop to center.
Two caveats:
One is that this is currently NOT set up to output to JPEG output, as rectangles for JPEG outputs must have dimensions which are multiple of 16 (See why here). The second is that I've locked my screen to landscape mode, and my camera is locked to landscape as well, but it should be possible to deal with screen rotations after a few tweaks.
You'll need to pass in the screen dimensions
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) mView.getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
the Maximum Camera Digital Zoom
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
float maxZoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
} catch (CameraAccessException e) {
e.printStackTrace();
}
the Camera Sensor's Active Array Size
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
Rect rectInit = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Here is my object initialization
mScaler = new CamScaler(maxZoom, rectInit.width(), rectInit.height(), displayMetrics.heightPixels, displayMetrics.widthPixels);
the CamScaler class
public class CamScaler {
private final float ZOOM_MIN = 1.0f;
private final int X_MIN = 0;
private final int Y_MIN = 0;
private int displayWidth;
private int displayHeight;
private Rect current_rect;
private int xCenter;
private int yCenter;
private int xWidth;
private int yHeight;
private int xMax;
private int yMax;
private float zoomMax;
private float zoomCurrent;
public CamScaler(float zoomMax, int xMax, int yMax, int displayHeight, int displayWidth) {
this.xMax = xMax;
this.yMax = yMax;
this.zoomMax = zoomMax;
current_rect = new Rect(X_MIN,Y_MIN, xMax, yMax); //(0,0,xMax,yMax) as the starting rectangle
zoomCurrent = ZOOM_MIN;
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX();
yCenter = current_rect.centerY();
this.displayHeight = displayHeight;
this.displayWidth = displayWidth;
}
public void pan(float distanceX, float distanceY){
//calculate the shift in the we want to take on the camera sensor with respect to the distance moved on the screen
int xShift = Math.round((distanceX/displayWidth)*xWidth); //scales down to a percentage of the current view width->converts to a pixel shift
int yShift = Math.round((distanceY/displayHeight)*yHeight); //scales down to a percentage of the current view height->converts to a pixel shift
//check if the shift will push us pass our maximums, this should account for both negative and positive values of xShift and yShift correctly
if ( !((xCenter + Math.round(xWidth/2.0) + xShift < xMax) && (xCenter - Math.round(xWidth/2.0) + xShift > 0))) { //if not within xBounds, set xShift to 0
xShift = 0;
}
if ( !((yCenter + Math.round(yHeight/2) + yShift < yMax) && (yCenter - Math.round(yHeight/2.0) + yShift > 0))) { //if not within yBounds, set yShift to 0
yShift = 0;
}
Log.d("Scaler", "pan: xShift" + xShift + " yShift " + yShift);
current_rect.offset(xShift,yShift);
Log.d("Scaler", "pan: current_rect" + current_rect.toString());
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
}
public void zoom(float scale_change){
if ( (zoomCurrent*scale_change < zoomMax) && (zoomCurrent*scale_change > ZOOM_MIN) ){ //if we are within zoom bounds
zoomCurrent *= scale_change; //update the zoom factor
int newWidthHalf = (int)Math.floor(xMax/zoomCurrent/2.0);
int newHeightHalf = (int)Math.floor(yMax/zoomCurrent/2.0);
int xTempCenter = xCenter;
int yTempCenter = yCenter;
//if at edge we need to shift and scale
if (xCenter + newWidthHalf > xMax) { //if at right edge
xTempCenter = xMax - newWidthHalf; //shift center to the left
} else if (xCenter - newWidthHalf < 0) { //if at left edge
xTempCenter = newWidthHalf; //shift center to the right
}
if (yCenter + newHeightHalf > yMax) { //if at bottom
yTempCenter = yMax - newHeightHalf; //shift center up
} else if (yCenter - newHeightHalf < 0) { //if at top
yTempCenter = newHeightHalf; //shift center down
}
Log.d("Scaler", "zoom: " + zoomCurrent);
Log.d(TAG, "current center(x,y) " + xTempCenter + " " + yTempCenter + "current halfwidths(x,y) " + newWidthHalf + " " + newHeightHalf);
current_rect.set(xTempCenter - newWidthHalf, yTempCenter - newHeightHalf,xTempCenter + newWidthHalf, yTempCenter + newHeightHalf);
Log.d("Scaler", "zoom: current_rect" + current_rect.toString());
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
} //if not in digital zoom bounds, do nothing
}
public Rect getCurrentView() {
return current_rect;
}
}
And how to use it
public void pan(float distanceX, float distanceY){
if (mScaler != null) {
synchronized (mScaler) {
mScaler.pan(distanceX, distanceY);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
public void zoom(float scale_factor) {
if (mScaler!= null) {
synchronized (mScaler) {
mScaler.zoom(scale_factor);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
The inputs to these functions are directly passed through from the gesture listeners
I hope this helps someone!
In addition to arin answer,Need to add captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
To captureStillPicture() method to let zoom take effect on capture

Issue with my game Joystick

I'm working in a spaceship first person view game. I have a joystick, and when i move the joystick i can move all the objects (asteroids) of the screen simulating that the spaceship is being moved with the joystick.
The game works fine, but now i have a problem. If you are pressing the joystick in the max left position and then you do ACTION_UP and then instantly ACTION_DOWN in the joystick again but in the max right position, the spaceship starts moving to the right at max speed. It is hard to explain it. For example, If you press the joystick in max left position the spaceship is moving -20px per frame to the left and if you press the joystick in the max right position, the spaceship moves to the right +20px per frame.
So, now, if i do a fast max left and max right touch on the joystick, the spaceship does this movement: -20....+20
It is not reallistic movement.
I want to get this movement: -20 -17 -14 -9 -5 0 +5 +9 +14 +17 +20.... I mean a more reallistic spaceship movement. But the problem is that i am not a math or physics expert, and i dont have any idea of how to get that kind of functionality in this joystick... any help will be very grateful.
Here you can find a demo project with the joystick: https://mega.co.nz/#!cp5FhYIT!dM88qx_xQdyhED9fX_4xeJ9ciQYJirUlNzEi-KOzU2k
This is the joystick code, i found it in google and works very well except for the non realistic movement that i described before:
public class Joystick extends View {
public static final int INVALID_POINTER = -1;
private JoystickMovedListener moveListener;
//# of pixels movement required between reporting to the listener
private float moveResolution;
//Max range of movement in user coordinate system
private float movementRange;
//Last touch point in view coordinates
private int pointerId = INVALID_POINTER;
private float touchX;
private float touchY;
private float touchXDelayedMovement;
private float touchYDelayedMovement;
//Handle center in view coordinates
private float handleX;
private float handleY;
//Last reported position in view coordinates (allows different reporting sensitivities)
private float reportX;
private float reportY;
//Center of the view in view coordinates
private int cX;
private int cY;
//Size of the view in view coordinates
private int dimX;
private int dimY;
private int innerPadding;
private int bgRadius;
private int handleRadius;
private int movementRadius;
private int handleInnerBoundaries;
//Cartesian coordinates of last touch point - joystick center is (0,0)
private int cartX;
private int cartY;
//User coordinates of last touch point
private int userX;
private int userY;
//Offset co-ordinates (used when touch events are received from parent's coordinate origin)
private int offsetX;
private int offsetY;
private Paint bgPaint;
private Paint handlePaint;
boolean disabled;
Handler handler;
Handler handlerDelayedMovement;
public Joystick(Context context) {
super(context);
initJoystickView();
}
private void initJoystickView() {
setFocusable(true);
handlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
handlePaint.setColor(Color.RED);
handlePaint.setStrokeWidth(1);
handlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgPaint.setColor(Color.DKGRAY);
bgPaint.setStrokeWidth(1);
bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
this.moveResolution = 1.0f;
handler = new Handler();
handlerDelayedMovement = new Handler();
}
public void setMovementRange(float movementRange) {
this.movementRange = movementRange;
}
public void setOnJostickMovedListener(JoystickMovedListener listener) {
this.moveListener = listener;
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int d = Math.min(getMeasuredWidth(), getMeasuredHeight());
dimX = d;
dimY = d;
cX = d / 2;
cY = d / 2;
bgRadius = dimX/2 - innerPadding;
handleRadius = (int)(d * 0.2);
handleInnerBoundaries = handleRadius;
movementRadius = Math.min(cX, cY) - handleInnerBoundaries;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Here we make sure that we have a perfect circle
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
}
private int measure(int measureSpec) {
int result = 0;
// Decode the measurement specifications.
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.UNSPECIFIED) {
result = 200; // Return a default size of 200 if no bounds are specified.
} else {
result = specSize; // As you want to fill the available space always return the full available bounds.
}
return result;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
// Draw the background
canvas.drawCircle(cX, cY, bgRadius, bgPaint);
// Draw the handle
handleX = touchX + cX;
handleY = touchY + cY;
canvas.drawCircle(handleX, handleY, handleRadius, handlePaint);
canvas.restore();
}
public void setPointerId(int id) {
this.pointerId = id;
}
public int getPointerId() {
return pointerId;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
if (disabled==true)
break;
return processMoveEvent(ev);
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if ( pointerId != INVALID_POINTER ) {
returnHandleToCenter();
returnHandleToCenterDelayedMovement();
setPointerId(INVALID_POINTER);
}
break;
}
case MotionEvent.ACTION_POINTER_UP: {
if ( pointerId != INVALID_POINTER ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if ( pointerId == this.pointerId ) {
returnHandleToCenter();
returnHandleToCenterDelayedMovement();
setPointerId(INVALID_POINTER);
return true;
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
handlerDelayedMovement.removeCallbacksAndMessages(null);
if ( pointerId == INVALID_POINTER ) {
int x = (int) ev.getX();
if ( x >= offsetX && x < offsetX + dimX ) {
setPointerId(ev.getPointerId(0));
if (disabled==true){
return true;
}
return processMoveEvent(ev);
}
}
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
if ( pointerId == INVALID_POINTER ) {
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
int x = (int) ev.getX(pointerId);
if ( x >= offsetX && x < offsetX + dimX ) {
setPointerId(pointerId);
return true;
}
}
break;
}
}
return false;
}
private boolean processMoveEvent(MotionEvent ev) {
if ( pointerId != INVALID_POINTER ) {
final int pointerIndex = ev.findPointerIndex(pointerId);
// Translate touch position to center of view
float x = ev.getX(pointerIndex);
touchX = x - cX - offsetX;
float y = ev.getY(pointerIndex);
touchY = y - cY - offsetY;
reportOnMoved();
invalidate();
return true;
}
return false;
}
private void reportOnMoved() {
//constraint circle
float diffX = touchX;
float diffY = touchY;
double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
if ( radial > movementRadius ) {
touchX = (int)((diffX / radial) * movementRadius);
touchY = (int)((diffY / radial) * movementRadius);
}
//We calc user coordinates
//First convert to cartesian coordinates
cartX = (int)(touchX / movementRadius * movementRange);
cartY = (int)(touchY / movementRadius * movementRange);
//Cartesian Coordinates
userX = cartX;
userY = cartY;
if (moveListener != null) {
boolean rx = Math.abs(touchX - reportX) >= moveResolution;
boolean ry = Math.abs(touchY - reportY) >= moveResolution;
if (rx || ry) {
this.reportX = touchX;
this.reportY = touchY;
moveListener.OnMoved(userX, userY);
}
}
}
private void reportOnMovedDelayedMovement() {
//constraint circle
float diffX = touchXDelayedMovement;
float diffY = touchYDelayedMovement;
double radial = Math.sqrt((diffX*diffX) + (diffY*diffY));
if ( radial > movementRadius ) {
touchXDelayedMovement = (int)((diffX / radial) * movementRadius);
touchYDelayedMovement = (int)((diffY / radial) * movementRadius);
}
//We calc user coordinates
//First convert to cartesian coordinates
cartX = (int)(touchXDelayedMovement / movementRadius * movementRange);
cartY = (int)(touchYDelayedMovement / movementRadius * movementRange);
//Cartesian Coordinates
userX = cartX;
userY = cartY;
if (moveListener != null) {
boolean rx = Math.abs(touchXDelayedMovement - reportX) >= moveResolution;
boolean ry = Math.abs(touchYDelayedMovement - reportY) >= moveResolution;
if (rx || ry) {
this.reportX = touchXDelayedMovement;
this.reportY = touchYDelayedMovement;
moveListener.OnMoved(userX, userY);
}
}
}
private void returnHandleToCenter() {
final int numberOfFrames = 5;
final double intervalsX = (0 - touchX) / numberOfFrames;
final double intervalsY = (0 - touchY) / numberOfFrames;
handler.removeCallbacksAndMessages(null);
for (int i = 0; i < numberOfFrames; i++) {
final int j = i;
handler.postDelayed(new Runnable() {
#Override
public void run() {
touchX += intervalsX;
touchY += intervalsY;
//reportOnMoved();
invalidate();
if (moveListener != null && j == numberOfFrames - 1) {
moveListener.OnReturnedToCenter();
}
}
}, i * 10);
}
if (moveListener != null) {
moveListener.OnReleased();
}
}
private void returnHandleToCenterDelayedMovement() {
final int numberOfFrames = 25;
touchXDelayedMovement=touchX;
touchYDelayedMovement=touchY;
final double intervalsX = (0 - touchXDelayedMovement) / numberOfFrames;
final double intervalsY = (0 - touchYDelayedMovement) / numberOfFrames;
handlerDelayedMovement.removeCallbacksAndMessages(null);
for (int i = 0; i < numberOfFrames; i++) {
handlerDelayedMovement.postDelayed(new Runnable() {
#Override
public void run() {
touchXDelayedMovement += intervalsX;
touchYDelayedMovement += intervalsY;
reportOnMovedDelayedMovement();
}
}, i * 50);
}
}
public void setInnerPadding(int innerPadding){
this.innerPadding=innerPadding;
}
public void disable(){
disabled=true;
}
public void enable(){
disabled=false;
}
public interface JoystickMovedListener {
public void OnMoved(int pan, int tilt);
public void OnReleased();
public void OnReturnedToCenter();
}
}
You must do this in the class that will use the joystick:
private JoystickMovedListener joystickListener = new JoystickMovedListener() {
#Override
public void OnMoved(int pan, int tilt) {
//here i move the objects in the game
}
}
#Override
public void OnReleased() {}
public void OnReturnedToCenter() {};
};
joystickOnScreen = new Joystick(this);
joystickOnScreen.setMovementRange(screenHeight/50);
joystickOnScreen.setInnerPadding(screenHeight/30);
joystickOnScreen.setOnJostickMovedListener(joystickListener);
RelativeLayout.LayoutParams joystickParams = new RelativeLayout.LayoutParams(sh/3, sh/3);
joystickParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
joystickParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
joystickParams.setMargins(sh/100, 0, 0, sh/100);
joystickOnScreen.setLayoutParams(joystickParams);
joystickOnScreen.setAlpha(0.3f);
I will not implement the changes for you but hopefully this answer can help you towards implementing this on your own.
With your current implementation you are updating the object position (x, y) each frame. To get the more realistic physics that you want, you need to store and update velocity as well (vx, vy).
Add two new variables, vx and vy (with initial values of zero) in the objects that you are currently updating the position for. The joystick should control the change of the velocity instead of the position. Change the code that updates the positions x and y, to update the velocities vx and vy instead. When the joystick is max left, you can for example set vx = vx - 3.
After the velocity is updated, you need to update the position using the velocity variables. For example, set the position x = x + vx. Ideally you want this to happen in a different method that runs even if you don't move the joystick, but to keep it simple you can do this update right after the update of the velocity variables.
With this implementation you will get a more realistic game physics. As a next step you might want to add limits on the velocity to not move too fast. This can be done with an if-statement where you check that the value is not too big before adding more to it, or too smal before subtracting from it. Good luck!

Drag mouse to rotate and scale in Java

I'm trying to create an Illustrator style selection box for geometric objects in java.
When the object is selected a border is drawn and it's possible to drag the little rectangles to re-size the object. I'd also like to be able to rotate the box by dragging.
So far I can scale the box and I can rotate the box but I can't do the two together. Imagine the box is at an angle of 45 degrees. When you drag the corner to enlarge the box in the x direction this will increase both the width and height of the box because of the angle.
I can get it to work by using:
dx = dx*cos(theta) - dy*sin(theta);
dy = dy*cos(theta) + dx*sin(theta);
But this only works when the pivot point is in the top left corner. I want to be able to move the pivot around and then scale and rotate. This problem must have been solved lots of times before. Is there a way I can use an affine transform to convert my mouse draw to the coordinate space of the rotated object? I'd prefer not to have to dig through the trigonometry! Thanks in advance.
You are in luck - Java2D provides an AffineTransform class that should do everything you are looking for.
It can handle rotations, scaling, shears, flips, translations etc.
There is a concatenate function that should enable you to combine multiple transforms (as moonwave99 points out you need to do them in the right order as the combination of affine transformations is not commutative)
I pretty much worked the answer out myself although it's still not possible to move the pivot point around. In case it's helpful here's the full code for a working example using JavaFX 2.2. You can scale and rotate the box by dragging the corners around:
public class SelectionBoxDemo extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage arg0) throws Exception {
Stage stage = new Stage ();
// Root is the base pane in which we put everything
Pane root = new Pane ();
SelectionBox sb = new SelectionBox ();
sb.setSize(100, 100);
root.getChildren().add(sb);
// Create a new scene
Scene scene = new Scene (root);
stage.setScene(scene);
stage.setMinHeight(500);
stage.setMinWidth(500);
stage.show();
}
public static class SelectionBox extends Region {
private enum Position {
TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left;
}
// Create the corners
private Rectangle tr, tl, br, bl;
// Create selection lines
final private Line top, right, bottom, left;
// Size of corner boxes
private double cornerSize = 10;
// Create a new rotate transform
private final Rotate rotate = new Rotate();
{
getTransforms().add(rotate);
rotate.setPivotX(cornerSize);
rotate.setPivotY(cornerSize);
}
// Circle which is dragged to rotate the box
private final Circle rotateCircle;
// Variables to store mouse x and y
private double x, y;
public SelectionBox () {
// Create the circle which can be dragged to rotate the box
rotateCircle = new Circle(5);
rotateCircle.setFill(Color.PINK);
rotateCircle.setStroke(Color.rgb(0,0,0, 0.75));
// Make it draggable
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
setMouse(event.getSceneX(), event.getSceneY());
}
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
// Used to get the scene position of the corner of the box
Transform localToScene = getLocalToSceneTransform();
double x1 = getMouseX();
double y1 = getMouseY();
double x2 = event.getSceneX();
double y2 = event.getSceneY();
double px = rotate.getPivotX() + localToScene.getTx();
double py = rotate.getPivotY() + localToScene.getTy();
// Work out the angle rotated
double th1 = clockAngle(x1, y1, px, py);
double th2 = clockAngle(x2, y2, px, py);
double angle = rotate.getAngle();
angle += th2 - th1;
// Rotate the rectangle
rotate.setAngle(angle);
setMouse(event.getSceneX(), event.getSceneY());
}
});
// Build the corners
tr = buildCorner (0,0, Position.TopRight);
tl = buildCorner (0,0, Position.TopLeft);
br = buildCorner (0,0, Position.BottomRight);
bl = buildCorner (0,0, Position.BottomLeft);
// Build the lines
top = buildLine(0, 100, -100, 0);
bottom = buildLine(0, 0, 0, 0);
left = buildLine(0, 0, 0, 0);
right = buildLine(0, 0, 0, 0);
getChildren().addAll(top, bottom, left, right, tr, tl, br, bl, rotateCircle);
}
// Return the angle from 0 - 360 degrees
public double clockAngle (double x, double y, double px, double py) {
double dx = x - px;
double dy = y - py;
double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));
if(dy < 0) {
angle = 360 - angle;
}
return angle;
}
// Set the size of the selection box
public void setSize (double width, double height) {
tl.setX(0);
tl.setY(0);
tr.setX(width + cornerSize);
tr.setY(0);
bl.setX(0);
bl.setY(height + cornerSize);
br.setX(width + cornerSize);
br.setY(height + cornerSize);
setLine(top, cornerSize, cornerSize, width + cornerSize, cornerSize);
setLine(bottom, cornerSize, height + cornerSize, width + cornerSize, height + cornerSize);
setLine(right, width + cornerSize, cornerSize, width + cornerSize, height + cornerSize);
setLine(left, cornerSize, cornerSize, cornerSize, height + cornerSize);
top.setCursor(Cursor.V_RESIZE);
bottom.setCursor(Cursor.V_RESIZE);
left.setCursor(Cursor.H_RESIZE);
right.setCursor(Cursor.H_RESIZE);
tr.setCursor(Cursor.CROSSHAIR);
tl.setCursor(Cursor.CROSSHAIR);
br.setCursor(Cursor.CROSSHAIR);
bl.setCursor(Cursor.CROSSHAIR);
rotateCircle.setTranslateX(width + 2 * cornerSize + rotateCircle.getRadius());
rotateCircle.setTranslateY(height + 2 * cornerSize + rotateCircle.getRadius());
}
// Set the start and end points of a line
private void setLine (Line l, double x1, double y1, double x2, double y2) {
l.setStartX(x1);
l.setStartY(y1);
l.setEndX(x2);
l.setEndY(y2);
}
// Save mouse coordinates
private void setMouse(double x, double y) {
this.x = x;
this.y = y;
}
private double getMouseX () {
return x;
}
private double getMouseY () {
return y;
}
// Selection box width
public double w () {
return Math.abs(bottom.getEndX() - bottom.getStartX());
}
// Selection box height
public double h () {
return Math.abs(right.getEndY() - right.getStartY());
}
// Build a corner of the rectangle
private Rectangle buildCorner (double x, double y, final Position pos) {
// Create the rectangle
Rectangle r = new Rectangle();
r.setX(x);
r.setY(y);
r.setWidth(cornerSize);
r.setHeight(cornerSize);
r.setStroke(Color.rgb(0,0,0,0.75));
r.setFill(Color.rgb(0, 0, 0, 0.25));
r.setStrokeWidth(1);
r.setStrokeType(StrokeType.INSIDE);
// Make it draggable
r.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
setMouse(event.getSceneX(), event.getSceneY());
}
});
r.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
// Get the mouse deltas
double dx = event.getSceneX() - getMouseX();
double dy = event.getSceneY() - getMouseY();
// Set save the current mouse value
setMouse(event.getSceneX(), event.getSceneY());
// Get the rotation angle in radians
double tau = - Math.toRadians(rotate.getAngle());
// Create variables for the sin and cosine
double sinTau = Math.sin(tau);
double cosTau = Math.cos(tau);
// Perform a rotation on dx and dy to the object co-ordinate frame
double dx_ = dx * cosTau - dy * sinTau;
double dy_ = dy * cosTau + dx * sinTau;
// Create a variable for the change in height of the box
double dh = h();
// Work out the new positions for the resize corners
if(pos == Position.TopLeft) {
// Set the size based on the transformed dx and dy values
setSize(w() - dx_, h() - dy_);
// Move the shape
setTranslateX(getTranslateX() + dx);
setTranslateY(getTranslateY() + dy);
}
else if (pos == Position.TopRight) {
// This comes down to geometry - you need to know the
// amount the height of the shape has increased
setSize(w() + dx_ , h() - dy_);
// Work out the delta height - that is then used to work out
// the correct translations
dh = h() - dh;
setTranslateX (getTranslateX() - dh * sinTau);
setTranslateY (getTranslateY() - dh * cosTau);
}
else if (pos == Position.BottomRight) {
setSize(w() + dx_ , h() + dy_ );
}
else if (pos == Position.BottomLeft) {
setSize(w() - dx_, h() + dy_);
dh = h() - dh;
setTranslateX(getTranslateX() + dx - dh * sinTau );
setTranslateY(getTranslateY() + dy - dh * cosTau);
}
}
});
return r;
}
private Line buildLine (double x1, double y1, double x2, double y2) {
Line l = new Line (x1, y1, x2, y2);
l.setStroke(Color.rgb(0, 0, 0, 0.75));
l.setStrokeWidth (0.5);
return l;
}
}
}

Categories