Cocos2d Scrolling game TileMap Collision - iphone

I have different tile layers that I move at different speeds, my player is fixed in place(horizontally). Now how do I detect that my player is colliding width a tile? How do I know the coordinates of the tiles in the different layers?

I figured this out with the help of this tutorial http://paulsonapps.wordpress.com/2010/03/12/tutorial-1-tilemap-with-collision-game-cocos2d/
-(void)handleCollision
{
for (CCTMXLayer *lr in layers) {
// Determine the four corners of my player
int tlXright = floor(player.playerSprite.position.x+player.playerSprite.contentSize.width-lr.position.x);
int tlXleft = floor(player.playerSprite.position.x-lr.position.x);
int tlYup = floor(player.playerSprite.position.y+player.playerSprite.contentSize.height);
int tlYdown = floor(player.playerSprite.position.y);
//Convert our Map points
CGPoint nodeSpace1 = [tileMap convertToNodeSpace:ccp(tlXright,tlYdown)];
pos1X = floor(nodeSpace1.x / tileMap.tileSize.width);
pos1Y = floor(tileMap.mapSize.height - (nodeSpace1.y / tileMap.tileSize.height));
CGPoint nodeSpace2 = [tileMap convertToNodeSpace:ccp(tlXright,tlYup)];
pos2X = floor(nodeSpace2.x / tileMap.tileSize.width);
pos2Y = floor(tileMap.mapSize.height - (nodeSpace2.y / tileMap.tileSize.height));
CGPoint nodeSpace3 = [tileMap convertToNodeSpace:ccp(tlXleft,tlYdown)];
pos3X = floor(nodeSpace3.x / tileMap.tileSize.width);
pos3Y = floor(tileMap.mapSize.height - (nodeSpace3.y / tileMap.tileSize.height));
CGPoint nodeSpace4 = [tileMap convertToNodeSpace:ccp(tlXleft,tlYup)];
pos4X = floor(nodeSpace4.x / tileMap.tileSize.width);
pos4Y = floor(tileMap.mapSize.height - (nodeSpace4.y / tileMap.tileSize.height));
unsigned int gid5 = [lr tileGIDAt:ccp(pos1X,pos1Y)];
unsigned int gid6 = [lr tileGIDAt:ccp(pos2X,pos2Y)];
unsigned int gid7 = [lr tileGIDAt:ccp(pos3X,pos3Y)];
unsigned int gid8 = [lr tileGIDAt:ccp(pos4X,pos4Y)];
//NSLog(#"gid5:%d gid6:%d gid7:%d gid8:%d",gid5,gid6,gid7,gid8);
/ NSLog(#"pos1x:%d pos1y:%d pos4x:%d pos4y:%d",pos1X ,pos1Y,pos4X,pos4Y);
if (gid5 == 5) {
[lr removeTileAt:ccp(pos1X,pos1Y)];
}
if (gid6 == 5) {
[lr removeTileAt:ccp(pos2X,pos2Y)];
}
if (gid7 == 5) {
[lr removeTileAt:ccp(pos3X,pos3Y)];
}
if (gid8 == 5) {
[lr removeTileAt:ccp(pos4X,pos4Y)];
}
}
}
to know the coordinate of the tile, i subtracted the layer position to the position of the player
int tlXright = floor(player.playerSprite.position.x+player.playerSprite.contentSize.width-lr.position.x);
and converted it like in the tutorial.
CGPoint nodeSpace1 = [tileMap convertToNodeSpace:ccp(tlXright,tlYdown)];
pos1X = floor(nodeSpace1.x / tileMap.tileSize.width);
pos1Y = floor(tileMap.mapSize.height - (nodeSpace1.y / tileMap.tileSize.height));
...

Related

MotionEvent getX() and getY(), getRawX() and getRawY() returns wrong value in a TouchImageView

Adding my TouchImageView class below,
public class TouchImageView extends ImageView {
Matrix matrix;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF last = new PointF();
PointF start = new PointF();
float minScale = -3f;
float maxScale = 3f;
float[] m;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public TouchImageView(Context context) {
super(context);
sharedConstructing(context);
}
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
sharedConstructing(context);
}
private void sharedConstructing(final Context context) {
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float x =event.getRawX();
float y = event.getRawY();
Log.d("X & Y",""+x+" "+y);
TouchImageView view = new TouchImageView(context);
ImagePresize imagePresize = new ImagePresize(context);
imagePresize.getXYCordinates(view,x, y);
last.set(curr);
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true; // indicate event was handled
}
});
}
public void setMaxZoom(float x) {
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
}
}
void fixTrans() {
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
}
float getFixTrans(float trans, float viewSize, float contentSize) {
float minTrans, maxTrans;
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
float getFixDragTrans(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
//Fit to screen.
float scale;
Drawable drawable = getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
setImageMatrix(matrix);
}
fixTrans();
}
}
getX(), getY(), getRawX(), getRawY() of MotionEvent is returning wrong values. (0,0) coordinate is always shifted to left-bottom from the original.

OpenGl rendering in iOS

In my project i have to rotate an OBJ file. i have constant pivot point. i got the horizontal rotation exactly.now i have to rotate my OBJ file as vertically. i concluded that have to change angle itself. give ideas to rotate vertically in that constant Pivot point.
my Pivot point is teapotNode_.rotationAxis = CC3VectorMake(0.1, 1, 0.3);
-(void)rightButtonPressed {
float angle;
teapotNode_.rotationAxis = CC3VectorMake(0.1, 1, 0.3);
angle +=2.0;
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}
-(void)leftButtonPressed {
float angle;
teapotNode_.rotationAxis = CC3VectorMake(0.1, 1, 0.3);
angle -=2.0;
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}
-(void)topButtonPressed {
float angle;
teapotNode_.rotationAxis = CC3VectorMake(0.1, 0, 0); ***//changed the Pivot point.***
angle +=2.0; **//how to calculate the Top Down angle.**
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}
I found answer myself https://github.com/antonholmquist/rend-ios in this rend-ios project.
Actually the problem is while clicking the right and left button the rotation is working properly, like top and bottom rotation is also working properly. but when i click right to top / bottom, the object is rotating but it flickered.
The solution is:
Globally declared:
float x=0.0;
float y=0.0;
float z=0.0;
float angle;
float xangle;
float yangle;
-(void)rightButtonPressed {
x=0; y=1; z=0; //ROTATE OBJECT IN Y-AXIS(Y=1 & X=0)
yAngle+=2.0; //GET ANGLE OF OBJECT ROTATION IN Y-AXIS (ANTI CLOCKWISE)
angle +=2.0;
teapotNode_.rotationAxis = CC3VectorMake(x,y,z);
teapotNode_.rotation = CC3VectorMake(0, yAngle, 0);
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}
-(void)leftButtonPressed {
x=0; y=1; z=0; //ROTATE OBJECT IN Y-AXIS(Y=1 & X=0)
yAngle-=2.0; //GET ANGLE OF OBJECT ROTATION IN Y-AXIS (CLOCKWISE)
angle -=2.0;
teapotNode_.rotationAxis = CC3VectorMake(x,y,z);
teapotNode_.rotation = CC3VectorMake(0, yAngle, 0);
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}
-(void)topButtonPressed {
//Existing
/*float angle;
teapotNode_.rotationAxis = CC3VectorMake(0.1, 0, 0); //Changed the Pivot point.
angle +=2.0; //how to calculate the Top Down angle.
*/
//Modified
x=1; y=0; z=0; //ROTATE OBJECT IN Y-AXIS(Y=0 & X=1)
xAngle+=2.0; //GET ANGLE OF OBJECT ROTATION IN Y-AXIS (Top Down)
angle +=2.0;
teapotNode_.rotationAxis = CC3VectorMake(x,y,z);
teapotNode_.rotation = CC3VectorMake(xangle, 0, 0);
director_.running = YES;//Redirector object
if (director_.frameInterval == 2)
{
director_.running = NO;
}
}

smooth camera rotation

Ok im new to javascript and Unity so this is clearly a very noob beginner question.
Basically I have created a script that i place on my camera. Then when i select a model in the game it becomes the target of the camera script and im trying to make the camera smoothly turn to it. below is what im trying
in the update function i use raycast to catch a unit on the mouse click. this becomes the target variable in the script. it sets a bool to true then to follow target in the moveto function. but it only seems to move the camera if i keep the mouse left button pressed down on the unit. Im trying to select a unit then let the camera smoothly rotate to the unit.
Any ideas on this rookie mistake?
#pragma strict
var speed = 5.0;
var freeMovementTurnSpeed = 10.0;
var freeMovementForwardSpeed = 10.0;
var freeMovementVerticalSpeed = 10.0;
var maxHeight = 30;
var minHeight = 2;
var target : Transform;
var distance = 10.0;
var maxDistance = 50;
var minDistance = 10;
var xSpeed = 250.0;
var ySpeed = 120.0;
var yMinLimit = -20;
var yMaxLimit = 80;
private var x = 0.0;
private var y = 0.0;
// The distance in the x-z plane to the target
var followDistance = 30.0;
var maxDistanceDelta = 0.2;
var damping = 3.0;
var followTarget = false;
var smoothRotation ;
function Start () {
var angles = transform.eulerAngles;
x = angles.y;
y = angles.x;
if(target){
transform.LookAt(target);
}
// Make the rigid body not change rotation
if (rigidbody)
rigidbody.freezeRotation = true;
}
function LateUpdate () {
if(target){
if(followTarget){
moveTo();
}
else{
targetSelectedMovement();
}
}
else {
freeMovement();
}
}
function moveTo(){
//transform.LookAt (target);
var currentDistance = Vector3.Distance(transform.position,target.position);
//if (currentDistance<40){
//followTarget = false;
//}
//else{
smoothRotation = Quaternion.LookRotation(target.position - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, smoothRotation, Time.deltaTime * damping);
print(target);
//transform.position = Vector3.MoveTowards(transform.position,target.position,maxDistanceDelta);
//}
}
function freeMovement(){
var zPos = Input.GetAxis("Vertical") * Time.deltaTime * freeMovementForwardSpeed;
var xPos = Input.GetAxis("Horizontal") * Time.deltaTime * freeMovementTurnSpeed;
var strafePos = Input.GetAxis("Strafe")* Time.deltaTime * freeMovementForwardSpeed;
var vertPos = Input.GetAxis("GainVertical")* Time.deltaTime * freeMovementVerticalSpeed;
if (Input.GetButton("Fire2")) {
x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
y = ClampAngle(y, yMinLimit, yMaxLimit);
var rotation2 = Quaternion.Euler(y, x, 0);
transform.rotation = rotation2;
}
else {
transform.Rotate(0, Input.GetAxis("Horizontal")*freeMovementTurnSpeed*Time.deltaTime, 0,Space.World);
}
if (vertPos > 0 && transform.position.y > maxHeight){
vertPos = 0;
}
else if(vertPos < 0 && transform.position.y < minHeight){
vertPos = 0;
}
print(transform.position.y);
transform.Translate(strafePos,vertPos,zPos);
}
function targetSelectedMovement() {
if(Input.GetKey("a") || Input.GetKey("d") ){
x += (-Input.GetAxis("Horizontal")) * xSpeed * 0.02;
var rotation = Quaternion.Euler(y, x, 0);
var currentDistance = Vector3.Distance(transform.position,target.position);
var position = rotation * Vector3(0.0, 0.0, -currentDistance) + target.position;
transform.rotation = rotation;
transform.position = position;
}
var zPos = Input.GetAxis("Vertical") * Time.deltaTime * speed;
currentDistance = Vector3.Distance(transform.position,target.position);
if(currentDistance < maxDistance && zPos < 0){
transform.Translate(0, 0, zPos);
}
else if(currentDistance > minDistance && zPos > 0){
transform.Translate(0, 0, zPos);
}
if (Input.GetButton("Fire2")) {
x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
y = ClampAngle(y, yMinLimit, yMaxLimit);
var rotation2 = Quaternion.Euler(y, x, 0);
var currentDistance2 = Vector3.Distance(transform.position,target.position);
var position2 = rotation2 * Vector3(0.0, 0.0, -currentDistance2) + target.position;
transform.rotation = rotation2;
transform.position = position2;
}
}
static function ClampAngle (angle : float, min : float, max : float) {
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp (angle, min, max);
}
function Update () {
var hit : RaycastHit;
if (Input.GetButton("Fire1")) {
var ray : Ray = Camera.main.ScreenPointToRay (Input.mousePosition);
// Cast a ray forward from our position for the specified distance
if (Physics.Raycast(ray, hit))
{
// Add a specified force to the Rigidbody component to the other GameObject
if(hit.collider.tag == "Unit"){
print("Selected " + hit.collider.gameObject.name);
target = hit.collider.gameObject.transform;
//transform.LookAt(target);
followTarget = true;
}
else {
target = null;
}
}
}
}
:)
Answer pulled from my comments above:
"I'll have to look when I get on my dev. machine but in the mean time try replacing Input.GetButton("Fire1") with Input.GetButtonDown("Fire1"). Just as a test, because looking at your code nothing jumps out at me. GetButton is true as long as the button is held, in the past I have had issues with it incorrectly triggering just as the mouse moved off an object."

MKMapRect zooms too much

I use this code to show all my annotations on my map:
MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in mapView.annotations)
{
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 1000);
if (MKMapRectIsNull(zoomRect)) {
zoomRect = pointRect;
} else {
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
}
[mapView setVisibleMapRect:zoomRect animated:YES];
But my problem is that when the annotations are close to each other, it zooms too much as the rectangle is small.
Any way to fix this?
In my code, I add extra spacing around, so it will automatically adjust the zoom level in order to fit.
[aMapView setVisibleMapRect:zoomRect edgePadding:UIEdgeInsetsMake(-100, -50, -50, -50) animated:YES];
Ariel's answer didn't work for me, but I made a few small changes to it and it's working great now (especially with maps with a single pin):
double minimumZoom = 6000; // for my purposes the width/height have same min zoom
BOOL needChange = NO;
double x = MKMapRectGetMinX(zoomRect);
double y = MKMapRectGetMinY(zoomRect);
double w = MKMapRectGetWidth(zoomRect);
double h = MKMapRectGetHeight(zoomRect);
double centerX = MKMapRectGetMidX(zoomRect);
double centerY = MKMapRectGetMidY(zoomRect);
if (h < minimumZoom) { // no need to call MKMapRectGetHeight again; we just got its value!
// get the multiplicative factor used to scale old height to new,
// then apply it to the old width to get a proportionate new width
double factor = minimumZoom / h;
h = minimumZoom;
w *= factor;
x = centerX - w/2;
y = centerY - h/2;
needChange = YES;
}
if (w < minimumZoom) {
// since we've already adjusted the width, there's a chance this
// won't even need to execute
double factor = minimumZoom / w;
w = minimumZoom;
h *= factor;
x = centerX - w/2;
y = centerY - h/2;
needChange = YES;
}
if (needChange) {
zoomRect = MKMapRectMake(x, y, w, h);
}
[mapView setVisibleMapRect:zoomRect animated:YES];
Ariel's solution wasn't working for me either but n00neimp0rtant's was. I have rewritten it in Swift as a function rectWithMinimumZoom(_ minimumZoom: Double) in an extension of MKMapRect. In return it the adjusted rect (if adjustment is needed). Also I added a extra safety that minimumZoom cannot be divided by 0.
SWIFT
extension MKMapRect {
func rectWithMinimumZoom(_ minimumZoom: Double = 750) -> MKMapRect{
var needChange = false
var x = MKMapRectGetMinX(self)
var y = MKMapRectGetMinY(self)
var w = MKMapRectGetWidth(self)
var h = MKMapRectGetHeight(self)
let centerX = MKMapRectGetMidX(self)
let centerY = MKMapRectGetMidY(self)
if(h < minimumZoom){
let factor = minimumZoom / max(h,1)
h = minimumZoom
w *= factor;
x = centerX - w / 2
y = centerY - h / 2
needChange = true
}
if(w < minimumZoom){
let factor = minimumZoom / max(w,1)
w = minimumZoom
h *= factor
x = centerX - w / 2
y = centerY - h / 2
needChange = true
}
if(needChange){
return MKMapRectMake(x, y, w, h);
}
return self
}
For those of us who like one liners:
let minRectSize: Double = 5000
zoomRect = MKMapRect(
x: zoomRect.minX - max(0, minRectSize - zoomRect.width) / 2,
y: zoomRect.minY - max(0, minRectSize - zoomRect.height) / 2,
width: max(zoomRect.width, minRectSize),
height: max(zoomRect.height, minRectSize))
Try this code:
//here comes your for loop...
double minMapHeight = 10; //choose some value that fit your needs
double minMapWidth = 10; //the same as above
BOOL needChange = NO;
double x = MKMapRectGetMinX(zoomRect);
double y = MKMapRectGetMinY(zoomRect);
double w = MKMapRectGetWidth(zoomRect);
double h = MKMapRectGetHeight(zoomRect); //here was an error!!
if(MKMapRectGetHeight(zoomRect) < minMapHeight){
x -= minMapWidth/2;
w += minMapWidth/2;
needChange = YES;
}
if(MKMapRectGetWidth(zoomRect) < minMapWidth){
y -= minMapHeight/2;
h += minMapHeight/2;
needChange = YES;
}
if(needChange){
zoomRect = MKMapRectMake(x, y, w, h);
}
[mapView setVisibleMapRect:zoomRect animated:YES];
EDITED ->
double minMapHeight = 250; //choose some value that fit your needs
double minMapWidth = 250; //the same as above
BOOL needChange = NO;
double x = MKMapRectGetMinX(zoomRect);
double y = MKMapRectGetMinY(zoomRect);
double w = MKMapRectGetWidth(zoomRect);
double h = MKMapRectGetHeight(zoomRect);
double centerX = MKMapRectGetMidX(zoomRect);
double centerY = MKMapRectGetMidY(zoomRect);
if(MKMapRectGetHeight(zoomRect) < minMapHeight){
//x -= minMapWidth/2;
//w += minMapWidth/2;
x = centerX - w/2;
needChange = YES;
}
if(MKMapRectGetWidth(zoomRect) < minMapWidth){
//y -= minMapHeight/2;
//h += minMapHeight/2;
y = centerY - h/2;
needChange = YES;
}
if(needChange){
zoomRect = MKMapRectMake(x, y, w, h);
}
[mapView setVisibleMapRect:zoomRect animated:YES];

Finding closest object to CGPoint

I have four UIViews on a UIScrollView (screen divided into quartiles)
On the quartiles, I have a few objects (UIImageViews), on each quartile.
When the user taps the screen, I want to find the closest object to the given CGPoint?
Any ideas?
I have the CGPoint and frame (CGRect) of the objects within each quartile.
UPDATE:
(source: skitch.com)Red Pins are UIImageViews.
// UIScrollView
NSLog(#" UIScrollView: %#", self);
// Here's the tap on the Window in UIScrollView's coordinates
NSLog(#"TapPoint: %3.2f, %3.2f", tapLocation.x, tapLocation.y);
// Find Distance between tap and objects
NSArray *arrayOfCGRrectObjects = [self subviews];
NSEnumerator *enumerator = [arrayOfCGRrectObjects objectEnumerator];
for (UIView *tilesOnScrollView in enumerator) {
// each tile may have 0 or more images
for ( UIView *subview in tilesOnScrollView.subviews ) {
// Is this an UIImageView?
if ( [NSStringFromClass([subview class]) isEqualToString:#"UIImageView"]) {
// Yes, here are the UIImageView details (subView)
NSLog(#"%#", subview);
// Convert CGPoint of UIImageView to CGPoint of UIScrollView for comparison...
// First, Convert CGPoint from UIScrollView to UIImageView's coordinate system for reference
CGPoint found = [subview convertPoint:tapLocation fromView:self];
NSLog(#"Converted Point from ScrollView: %3.2f, %3.2f", found.x, found.y);
// Second, Convert CGPoint from UIScrollView to Window's coordinate system for reference
found = [subview convertPoint:subview.frame.origin toView:nil];
NSLog(#"Converted Point in Window: %3.2f, %3.2f", found.x, found.y);
// Finally, use the object's CGPoint in UIScrollView's coordinates for comparison
found = [subview convertPoint:subview.frame.origin toView:self]; // self is UIScrollView (see above)
NSLog(#"Converted Point: %3.2f, %3.2f", found.x, found.y);
// Determine tap CGPoint in UIImageView's coordinate system
CGPoint localPoint = [touch locationInView:subview];
NSLog(#"LocateInView: %3.2f, %3.2f",localPoint.x, localPoint.y );
//Kalle's code
CGRect newRect = CGRectMake(found.x, found.y, 32, 39);
NSLog(#"Kalle's Distance: %3.2f",[self distanceBetweenRect:newRect andPoint:tapLocation]);
}
Debug Console
Here's the problem. Each Tile is 256x256. The first UIImageView's CGPoint converted to the
UIScrollView's coordinate system (53.25, 399.36) should be dead on with the tapPoint (30,331). Why the difference?? The other point to the right of the tapped point is calculating closer (distance wise)??
<CALayer: 0x706a690>>
[207] TapPoint: 30.00, 331.00
[207] <UIImageView: 0x7073db0; frame = (26.624 71.68; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x70747d0>>
[207] Converted Point from ScrollView: 3.38, 3.32
[207] Converted Point in Window: 53.25, 463.36
[207] Converted Point: 53.25, 399.36 *** Looks way off!
[207] LocateInView: 3.38, 3.32
[207] Kalle's Distance: 72.20 **** THIS IS THE TAPPED POINT
[207] <UIImageView: 0x7074fb0; frame = (41.984 43.008; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x7074fe0>>
[207] Converted Point from ScrollView: -11.98, 31.99
[207] Converted Point in Window: 83.97, 406.02
[207] Converted Point: 83.97, 342.02
[207] LocateInView: -11.98, 31.99
207] Kalle's Distance: 55.08 ***** BUT THIS ONE's CLOSER??????
The following method should do the trick. If you spot anything weird in it feel free to point it out.
- (CGFloat)distanceBetweenRect:(CGRect)rect andPoint:(CGPoint)point
{
// first of all, we check if point is inside rect. If it is, distance is zero
if (CGRectContainsPoint(rect, point)) return 0.f;
// next we see which point in rect is closest to point
CGPoint closest = rect.origin;
if (rect.origin.x + rect.size.width < point.x)
closest.x += rect.size.width; // point is far right of us
else if (point.x > rect.origin.x)
closest.x = point.x; // point above or below us
if (rect.origin.y + rect.size.height < point.y)
closest.y += rect.size.height; // point is far below us
else if (point.y > rect.origin.y)
closest.y = point.y; // point is straight left or right
// we've got a closest point; now pythagorean theorem
// distance^2 = [closest.x,y - closest.x,point.y]^2 + [closest.x,point.y - point.x,y]^2
// i.e. [closest.y-point.y]^2 + [closest.x-point.x]^2
CGFloat a = powf(closest.y-point.y, 2.f);
CGFloat b = powf(closest.x-point.x, 2.f);
return sqrtf(a + b);
}
Example output:
CGPoint p = CGPointMake(12,12);
CGRect a = CGRectMake(5,5,10,10);
CGRect b = CGRectMake(13,11,10,10);
CGRect c = CGRectMake(50,1,10,10);
NSLog(#"distance p->a: %f", [self distanceBetweenRect:a andPoint:p]);
// 2010-08-24 13:36:39.506 app[4388:207] distance p->a: 0.000000
NSLog(#"distance p->b: %f", [self distanceBetweenRect:b andPoint:p]);
// 2010-08-24 13:38:03.149 app[4388:207] distance p->b: 1.000000
NSLog(#"distance p->c: %f", [self distanceBetweenRect:c andPoint:p]);
// 2010-08-24 13:39:52.148 app[4388:207] distance p->c: 38.013157
There might be more optimized versions out there, so might be worth digging more.
The following method determines the distance between two CGPoints.
- (CGFloat)distanceBetweenPoint:(CGPoint)a andPoint:(CGPoint)b
{
CGFloat a2 = powf(a.x-b.x, 2.f);
CGFloat b2 = powf(a.y-b.y, 2.f);
return sqrtf(a2 + b2)
}
Update: removed fabsf(); -x^2 is the same as x^2, so it's unnecessary.
Update 2: added distanceBetweenPoint:andPoint: method too, for completeness.
If you're using Swift, here's how you can calculate the distance between a CGPoint and a CGRect (e.g. an UIView's frame)
private func distanceToRect(rect: CGRect, fromPoint point: CGPoint) -> CGFloat {
// if it's on the left then (rect.minX - point.x) > 0 and (point.x - rect.maxX) < 0
// if it's on the right then (rect.minX - point.x) < 0 and (point.x - rect.maxX) > 0
// if it's inside the rect then both of them < 0.
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
// same as dx
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
// if one of them == 0 then the distance is the other one.
if dx * dy == 0 {
return max(dx, dy)
} else {
// both are > 0 then the distance is the hypotenuse
return hypot(dx, dy)
}
}
Thanks #cristian,
Here's Objective-C version of your answer
- (CGFloat)distanceToRect:(CGRect)rect fromPoint:(CGPoint)point
{
CGFloat dx = MAX(0, MAX(CGRectGetMinX(rect) - point.x, point.x - CGRectGetMaxX(rect)));
CGFloat dy = MAX(0, MAX(CGRectGetMinY(rect) - point.y, point.y - CGRectGetMaxY(rect)));
if (dx * dy == 0)
{
return MAX(dx, dy);
}
else
{
return hypot(dx, dy);
}
}
Shorter #cristian answer:
func distance(from rect: CGRect, to point: CGPoint) -> CGFloat {
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
Personally, I would implement this as a CGPoint extension:
extension CGPoint {
func distance(from rect: CGRect) -> CGFloat {
let dx = max(rect.minX - x, x - rect.maxX, 0)
let dy = max(rect.minY - y, y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}
Alternatively, you can also implement it as a CGRect extension:
extension CGRect {
func distance(from point: CGPoint) -> CGFloat {
let dx = max(minX - point.x, point.x - maxX, 0)
let dy = max(minY - point.y, point.y - maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}