I have this code into the - (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context method (into a MKOverlayView subclass) to prevent drawing segments that are less than 10 pixels long on a map overlay :
CGPoint origin = [self pointForMapPoint:poly.points[0]];
CGPoint lastPoint = origin;
CGContextMoveToPoint(context, origin.x, origin.y);
for (int i=1; i<poly.pointCount; i++) {
CGPoint point = [self pointForMapPoint:poly.points[i]];
CGFloat xDist = (point.x - lastPoint.x);
CGFloat yDist = (point.y - lastPoint.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist)) * zoomScale;
if (distance >= 10.0) {
lastPoint = point;
CGContextAddLineToPoint(context, point.x, point.y);
}
}
will the test >= 10.0 will take care about the screen resolution, or may I introduce some [UIScreen mainScreen].scale parameter ?
I believe that test >= 10.0 does not take into account the screen resolution. Apple does most of their drawing arithmetic using "points" instead of pixels- that way code does not have to change for a retina display compared to a normal display.
If you want to draw something just 10.0 pixels wide, you will need to take into account the screen resolution; however, if you do this you'll have to write the method to support both retina display and normal display.
It depends on how the graphics context is configured. If this is in UIView drawing code, the view's scale factor (which is set automatically) will take care of this, if you're drawing into a bitmap context, you have to do it manually.
Related
I'm trying to make a cocos2d/box2d game work on iPad, iPhone and iPhone retina.
My problem is, that the fixture and body don't line up on the retina simulator, please click on screenshots below for illustration (as a new stackoverflow member, it won't allow me to post the screenshot here).
screenshot
(please disregard the different shapes, I want the 4 corners to line up)
I've done quite a bit of research on this over the last couple of days, and the closest I found was this:
link
But the solution offered there with PTM_RATIO and CC_CONTENT_SCALE_FACTOR() doesn't seem to work in my case. I think it has to do with the fact that I don't load an image from file into my sprite. Most solutions to this problem are based on loading -hd image files for the retina display, but I don't want to use files in my game at all. I basically want to draw the polygons myself at runtime,
My code looks as follows:
-(CCSprite*)addSprite
{
CGSize contextsize = CGSizeMake(200, 200);
UIGraphicsBeginImageContext(contextsize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextFlush(context);
CGContextSetAllowsAntialiasing(context, true);
CGContextTranslateCTM(context, 0, contextsize.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {0.0, 0.0, 1.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, color);
UIBezierPath* aPath;
aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100)
radius:100
startAngle:0
endAngle:1.57
clockwise:YES];
[aPath addArcWithCenter:CGPointMake(100, 100)
radius:50
startAngle:1.57
endAngle:0
clockwise:NO];
[aPath stroke];
CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);
UIImage *graphImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CCTexture2D *tex = [[[CCTexture2D alloc] initWithImage:graphImage] autorelease];
CCSprite *sprite = [CCSprite spriteWithTexture:tex];
return sprite;
}
-(void) addFixture:(CCSprite *)fixsprite
{
b2Vec2 arcdots[] = {
b2Vec2(50.0f / PTM_RATIO, 0.0f / PTM_RATIO),
b2Vec2(100.0f / PTM_RATIO, 0.0f / PTM_RATIO),
b2Vec2(0.0f / PTM_RATIO, 100.0f / PTM_RATIO),
b2Vec2(0.0f / PTM_RATIO, 50.0f / PTM_RATIO)
};
b2PolygonShape p_shape;
b2FixtureDef fixtureDef;
b2BodyDef bodyDef;
bodyDef.type = b2_kinematicBody;
bodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
bodyDef.userData = fixsprite;
b2Body *body = world->CreateBody(&bodyDef);
p_shape.Set(arcdots, 4);
fixtureDef.shape = &p_shape;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
}
And I call these functions from the main routine as follows:
CCSprite *sprite2 = [self addSprite];
sprite2.position = ccp(0, 0);
[self addChild:sprite2 z:0];
[self addFixture:sprite2];
I have these lines uncommented in the delegate file:
if( ! [director enableRetinaDisplay:YES] )
CCLOG(#"Retina Display Not supported");
Please let me know if further information is required. And please be gentle, I'm only starting to learn this. Thanks for your time.
Unless otherwise mentioned, all coordinates in cocos2d (and most of UIKit) are given in points, not pixels. On a Retina display device you still have a point resolution of 480x320 points (960x640 pixels).
From that follows: when you calculate in actual pixels, multiply or divide by CC_CONTENT_SCALE_FACTOR. If you deal with point coordinates, do nothing. Since you're rendering your own polys I assume you know whether you use actual pixel coordinates or not. If you use OpenGL directly, then you'll be working with pixel coordinates.
I'm not sure if enabling Retina display mode does anything for you if you don't use cocos2d to render your content.
Lastly, a common misunderstanding is that the Box2D world is using point coordinates and must be transformed to pixels or vice versa. Neither is the case. The Box2D world is completely oblivious to a specific coordinate system. The use of PTM_RATIO is done only to ensure that Box2D coordinates are within reasonable ranges for the Box2D engine, since it works best with objects that are 1 meter in size/diameter, and most objects should range from 0.1 to 10 meters in diameter.
I'm creating a drawing app ( text ) for the iPad using OpenGL. I've already had a look at Apple's example GLPaint, and my app is now based on that code. My app should be just for drawing text, not for painting pictures.
Well, my App works, I can write some text. But the writing isn't really good, it doesn't make fun to write. The drawing path isn't smooth, it's angularly because I'm drawing a line from one point to another. And the path has everywhere the same width. My idea is: when you're writing fast the line is thinner than when you're writing slow. It should be the same experience like writing with a real pen.
How can I make the path look much smoother? How can I vary the width of the line depending on the speed of writing?
Here you can see what I mean:
The best way to smooth the drawing is use a bezeir curve. Here is my code. It is a modified version I found on apple's dev site, but I don't remember the original link:
CGPoint drawBezier(CGPoint origin, CGPoint control, CGPoint destination, int segments)
{
CGPoint vertices[segments/2];
CGPoint midPoint;
glDisable(GL_TEXTURE_2D);
float x, y;
float t = 0.0;
for(int i = 0; i < (segments/2); i++)
{
x = pow(1 - t, 2) * origin.x + 2.0 * (1 - t) * t * control.x + t * t * destination.x;
y = pow(1 - t, 2) * origin.y + 2.0 * (1 - t) * t * control.y + t * t * destination.y;
vertices[i] = CGPointMake(x, y);
t += 1.0 / (segments);
}
//windowHeight is the height of you drawing canvas.
midPoint = CGPointMake(x, windowHeight - y);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawArrays(GL_POINTS, 0, segments/2);
return midPoint;
}
That will draw based on three points. The control is the midpoint, which you need to return. The new midpoint will be different than the previous. Also, if you go through the above code, it will only draw half the line. The next stroke will fill it in. This is required. my code for calling this function (the above is in C, this is in Obj-C):
//Invert the Y axis to conform the iPhone top-down approach
invertedYBegCoord = self.bounds.size.height - [[currentStroke objectAtIndex:i] CGPointValue].y;
invertedYEndCoord = self.bounds.size.height - [[currentStroke objectAtIndex:i+1] CGPointValue].y;
invertedYThirdCoord = self.bounds.size.height - [[currentStroke objectAtIndex:i+2] CGPointValue].y;
//Figure our how many dots you need
count = MAX(ceilf(sqrtf(([[currentStroke objectAtIndex:i+2] CGPointValue].x - [[currentStroke objectAtIndex:i] CGPointValue].x)
* ([[currentStroke objectAtIndex:i+2] CGPointValue].x - [[currentStroke objectAtIndex:i] CGPointValue].x)
+ ((invertedYThirdCoord - invertedYBegCoord) * (invertedYThirdCoord - invertedYBegCoord))) / pointCount), 1);
newMidPoint = drawBezier(CGPointMake([[currentStroke objectAtIndex:i] CGPointValue].x, invertedYBegCoord), CGPointMake([[currentStroke objectAtIndex:i+1] CGPointValue].x, invertedYEndCoord), CGPointMake([[currentStroke objectAtIndex:i+2] CGPointValue].x, invertedYThirdCoord), count);
int loc = [currentStroke count]-1;
[currentStroke insertObject:[NSValue valueWithCGPoint:newMidPoint] atIndex:loc];
[currentStroke removeObjectAtIndex:loc-1];
That code will get the mid point based on inverted iPad points, and set the 'control' as the current point.
That will smooth out the edges. Now regarding the line width, you just need to find the speed of that drawing. It is easiest just to find the length of your line. This is easily done using component mathematics. I don't have any code for it, but here is a primer for component mathmatics from a physics site. Or you can simply divide (above) count by some number to find out how thick you need the line to be (count uses component mathematics).
I store point data in an array called currentStroke, in case it wasn't obvious.
That should be all you need.
EDIT:
To store points, you should use touchesBegin and touchesEnd:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
self.currentStroke = [NSMutableArray array];
CGPoint point = [ [touches anyObject] locationInView:self];
[currentStroke addObject:[NSValue valueWithCGPoint:point]];
[self draw];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint point = [ [touches anyObject] locationInView:self];
[currentStroke addObject:[NSValue valueWithCGPoint:point]];
[self draw];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint point = [ [touches anyObject] locationInView:self];
[currentStroke addObject:[NSValue valueWithCGPoint:point]];
[self draw];
}
That is pretty much an entire drawing application there. If you are using GL_Paint, then you are already using point sprites, which this system is build on.
With regards to the second part of your question (how to vary the width of the line depending on the speed of writing), you should be able to achieve this by taking advantage of UITouch's timestamp property in your touchesBegan:withEvent: and touchesMoved:withEvent: method.
You can calculate the time difference between two subsequent touch events by storing the timestamp of the most recent UITouch object and comparing it to the new one. Dividing the distance of the swiping motion by the time difference should give you some measurement of the movement speed.
Now all you need to do is to come up with a way to convert speed of writing into line width, which will probably come down to picking an arbitrary value and adjusting it until you're happy with the result.
I am struggling to get my custom drawing code to render at the proper scale for all iOS devices, i.e., older iPhones, those with retina displays and the iPad.
I have a subclass of UIView that has a custom class that displays a vector graphic. It has a scale property that I can set. I do the scaling in initWithCoder when the UIView loads and I first instantiate the vector graphic. This UIView is shown when the user taps a button on the home screen.
At first I tried this:
screenScaleFactor = 1.0;
if ([UIScreen instancesRespondToSelector:#selector(scale)]) {
screenScaleFactor = [[UIScreen mainScreen] scale];
}
// and then I multiply stuff by screenScale
... which worked for going between normal iPhones and retina iPhones, but chokes on the iPad. As I said, you can get to the UIView at issue by tapping a button on the home screen. When run on the iPad, if you display the UIView when at 1X, it works, but at 2X I get a vector graphic that twice as big as it should be.
So I tried this instead:
UPDATE: This block is the one that's right. (with the corrected spelling, of course!)
screenScaleFactor = 1.0;
if ([self respondsToSelector:#selector(contentScaleFactor)]) { //EDIT: corrected misspellng.
screenScaleFactor = (float)self.contentScaleFactor;
}
// again multiplying stuff by screenScale
Which works at both 1X and 2X on the iPad and on the older iPhones, but on a retina display, the vector graphic is half the size it should be.
In the first case, I query the UIScreen for its scale property and in the second case, I'm asking the parent view of the vector graphic for its contentsScaleFactor. Neither of these seem to get me where I want for all cases.
Any suggestions?
UPDATE:
Here's the method in my subclassed UIView (it's called a GaugeView):
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform t0 = CGContextGetCTM(context);
t0 = CGAffineTransformInvert(t0);
CGContextConcatCTM(context, t0);
[needle updateBox];
[needle draw: context];
}
needle is of class VectorSprite which is a subclass of Sprite which is subclassed from NSObject. These are from a programming book I'm working through. needle has the scale property that I set.
updateBox comes from Sprite and looks like this:
- (void) updateBox {
CGFloat w = width*scale;
CGFloat h = height*scale;
CGFloat w2 = w*0.5;
CGFloat h2 = h*0.5;
CGPoint origin = box.origin;
CGSize bsize = box.size;
CGFloat left = -kScreenHeight*0.5;
CGFloat right = -left;
CGFloat top = kScreenWidth*0.5;
CGFloat bottom = -top;
offScreen = NO;
if (wrap) {
if ((x+w2) < left) x = right + w2;
else if ((x-w2) > right) x = left - w2;
else if ((y+h2) < bottom) y = top + h2;
else if ((y-h2) > top) y = bottom - h2;
}
else {
offScreen =
((x+w2) < left) ||
((x-w2) > right) ||
((y+h2) < bottom) ||
((y-h2) > top);
}
origin.x = x-w2*scale;
origin.y = y-h2*scale;
bsize.width = w;
bsize.height = h;
box.origin = origin;
box.size = bsize;
}
Sprite also has the draw and drawBody methods which are:
- (void) draw: (CGContextRef) context {
CGContextSaveGState(context);
// Position the sprite
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t,x,y);
t = CGAffineTransformRotate(t,rotation);
t = CGAffineTransformScale(t,scale,scale);
CGContextConcatCTM(context, t);
// draw sprite body
[self drawBody: context];
CGContextRestoreGState(context);
}
- (void) drawBody: (CGContextRef) context {
// Draw your sprite here, centered
// on (x,y)
// As an example, we draw a filled white circle
if (alpha < 0.05) return;
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, r,g,b,alpha);
CGContextAddEllipseInRect(context, CGRectMake(-width/2,-height/2,width,height));
CGContextClosePath(context);
CGContextDrawPath(context,kCGPathFill);
}
How, exactly, are you rendering the graphic?
This should be handled automatically in drawRect: (the context you get should be already 2x). This should also be handled automatically with UIGraphicsBeginImageContextWithOptions(size,NO,0); if available (if you need to fall back to UIGraphicsBeginImageContext(), assume a scale of 1). You shouldn't need to worry about it unless you're drawing the bitmap yourself somehow.
You could try something like self.contentScaleFactor = [[UIScreen mainScreen] scale], with appropriate checks first (this might mean if you display it in an iPad at 2x, you'll get high-res graphics).
Fundamentally, there's not much difference between an iPad in 2x mode and a "retina display", except that the iPad can switch between 1x and 2x.
Finally, there's a typo: #selector(contentsScaleFactor) has an extra s.
My application has a UIScrollView with one subview. The subview is an extended UIView which prints a PDF page to itself using layers in the drawLayer event.
Zooming using the built in pinching works great. setZoomScale also works as expected.
I have been struggling with the zoomToRect function. I found an example online which makes a CGRect zoomRect variable from a given CGPoint.
In the touchesEnded function, if there was a double tap and they are all the way zoomed out, I want to zoom in to that PDFUIView I created as though they were pinching out with the center of the pinch where they double tapped.
So assume that I pass the UITouch variable to my function which utilizes zoomToRect if they double tap.
I started with the following function I found on apples site:
http://developer.apple.com/iphone/library/documentation/WindowsViews/Conceptual/UIScrollView_pg/ZoomZoom/ZoomZoom.html
The following is a modified version for my UIScrollView extended class:
- (void)zoomToCenter:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
zoomRect.size.height = self.frame.size.height / scale;
zoomRect.size.width = self.frame.size.width / scale;
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
//return zoomRect;
[self zoomToRect:zoomRect animated:YES];
}
When I do this, the UIScrollView seems to zoom using the bottom right edge of the zoomRect above and not the center.
If I make UIView like this
UIView *v = [[UIView alloc] initWithFrame:zoomRect];
[v setBackgroundColor:[UIView redColor]];
[self addSubview:v];
The red box shows up with the touch point dead in the center.
Please note: I am writing this from my PC, I recall messing around with the divided by two part on my Mac, so just assume that this draws a rect with the touch point in the center. If the UIView drew off center but zoomed to the right spot it would be all good.
However, what happens is when it preforms the zoomToRect it seems to use the bottom right off the zoomRect at the top left of the zoomed in results.
Also, I noticed that depending on where I click on the UIScrollView, it anchors to diffrent spots. It almost seems like there is a cross down the middle and it's reflecting the points somehow as though anywhere left of the middle is a negative reflection and anywhere right of the middle is a positive reflection?
This seems to complicated, shouldn't it just zoom to the rect that was drawn as the UIView was able to draw?
I used a lot of research to figure out how to create a PDF that scales in high quality, so I am assuming that using the CALayer may be throwing off the coordinate system? But to the UIScrollView it should just treat it as a view with 768x985 dimensions.
This is sort of advanced, please assume the code for creating the zoomRect is all good. There is something deeper with the CALayer in the UIView which is in the UIScrollView....
Ok another answer:
The apple supplied routine works for me, but you need to have the gesture recognizer convert the tap point to the imageView coords - not to the scroller.
Apple's example does this, but since our app works differently (we change the UIImageView), so the gestureRecongnizer was set up on the uiscrollview - which works fine, but you need to do this in the handleDoubleTap:
This is loosely based on the apple example code "TaptoZoom", but as I said we needed our gesture recognizer hooked up to the scroll view.
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {
// double tap zooms in
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(handleSingleTap:) object:nil];
float newScale = [imageScrollView zoomScale] * 1.5;
// Note we need to get location of the tap in the imageView coords, not the imageScrollView
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:imageView]];
[imageScrollView zoomToRect:zoomRect animated:YES];
}
Declare BOOL isZoom; in .h
-(void)handleDoubleTap:(UIGestureRecognizer *)recognizer {
if(isZoom){
CGPoint Pointview=[recognizer locationInView:self];
CGFloat newZoomscal=3.0;
newZoomscal=MIN(newZoomscal, self.maximumZoomScale);
CGSize scrollViewSize=self.bounds.size;
CGFloat w=scrollViewSize.width/newZoomscal;
CGFloat h=scrollViewSize.height /newZoomscal;
CGFloat x= Pointview.x-(w/2.0);
CGFloat y = Pointview.y-(h/2.0);
CGRect rectTozoom=CGRectMake(x, y, w, h);
[self zoomToRect:rectTozoom animated:YES];
[self setZoomScale:3.0 animated:YES];
isZoom=NO;
}
else{
[self setZoomScale:1.0 animated:YES];
isZoom=YES;
}
}
I've noticed that the apple you're using doesn't zoom properly if the image is starting at a zoomScale less than 1 because the zoomRect origin is incorrect. I edited it to work correctly. Here's the code:
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [self frame].size.height / scale;
zoomRect.size.width = [self frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = (center.x * (2 - self.minimumZoomScale) - (zoomRect.size.width / 2.0));
zoomRect.origin.y = (center.y * (2 - self.minimumZoomScale) - (zoomRect.size.height / 2.0));
return zoomRect;
}
The key is this part multiplying the center value by (2 - self.minimumZoomScale).
Hope this helps.
In my case it was:
zoomRect.origin.x = center.x / self.zoomScale - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y / self.zoomScale - (zoomRect.size.height / 2.0);
extension UIScrollView {
func getRectForVisibleView() -> CGRect {
var visibleRect: CGRect = .zero
visibleRect.origin = self.contentOffset
visibleRect.size = self.bounds.size
let theScale = 1.0 / self.zoomScale
visibleRect.origin.x *= theScale
visibleRect.origin.y *= theScale
visibleRect.size.width *= theScale
visibleRect.size.height *= theScale
return visibleRect
}
func moveToRect(rect: CGRect) {
let scale = self.bounds.width / rect.width
self.zoomScale = scale
self.contentOffset = .init(x: rect.origin.x * scale, y: rect.origin.y * scale)
}
}
I had something similar and it was because I didn't adjust the center.x and center.y values by dividing them by the scale also (using center.x/scale and center.y/scale). Maybe I'm not reading your code right.
I am having the same behavior and it is quite frustrating... The rectangle being fed to the UIScrollView is perfect.. yet my view, no matter what I do anything that involves changing the zoomScale programmatically always zooms and scales to coordinate 0,0, no matter what.
I have tried just changing the zoomScale, I've tried zoomToRect, I have tried them all, and every one the minute I touch the zoomScale in code, it goes to coordinate 0,0.
I did also have to add and explicit setContentSize to the resized image in the scrollview after a zooming operation, or otherwise I cannot scroll after a zoom or pinch.
Is this a bug in 3.1.3 or what?
I have tried different solutions, but this looks the best resolution
It is really straight forward and conceptional?
CGRect frame = [[UIScreen mainScreen] applicationFrame];
scrollView.contentInset = UIEdgeInsetsMake(frame.size.height/2,
frame.size.width/2,
frame.size.height/2,
frame.size.width/2);
I disagree with one of the comments above saying that you should never multiply the center's coordinates by some factor.
Say that you are currently displaying an entire 400x400px image or PDF file in a 100x100 scroll view and want to allow the users to double the size of the content until it's 1:1.
If you double tap at point (75,75), you expect the zoomed-in rectangle to have origin 100,100 and size 100x100 within the new 200x200 content view. So the original tapping point (75,75) is now (150,150) in the new 200x200 space.
Now, after zoom action #1 has completed, if you again double tap at (75,75) inside the new 100x100 rectangle (which is the bottom-right square of the larger 200x200 rectangle), you expect the user to be shown the bottom-right 100x100 square of the larger image, which would now become zoomed to 400x400 pixels.
In order to calculate the origin of this latest 100x100 rectangle within the larger 400x400 rectangle, you would need to consider the scale and current content offset (since before this last zoom action we were displaying the bottom-right 100x100 rectangle within a 200x200 content rectangle).
So the x coordinate of the final rectangle becomes:
center.x/currentScale - (scrollView.frame.size.width/2) + scrollView.contentOffset.x/currentScale
= 75/.5 - 100/2 + 100/.5 = 150 - 50 + 200 = 300.
In this case, being a square, the calculation for the y coordinate is the same.
And we did indeed zoom in the bottom-right 100x100 rectangle, which, in the larger 400x400 content view has origin 300,300.
So here is how you would calculate the zoom rectangle's size and origin:
zoomRect.size.height = mScrollView.frame.size.height/scale;
zoomRect.size.width = mScrollView.frame.size.width/scale;
zoomRect.origin.x = center.x/currentScale - (mScrollView.frame.size.width/2) + mScrollView.contentOffset.x/currentScale;
zoomRect.origin.y = center.y/currentScale - (mScrollView.frame.size.height/2) + mScrollView.contentOffset.y/currentScale;
Hope this made sense; it's hard to explain it in writing without sketching out the various squares/rectangles.
Cheers,
Raf Colasante
I am trying to change my Sprite anchor point so that I can rotate over a 0.0f,0.0f anchorpoint. At first my object is rotation at the default anchor point (0.5f,0.5f). However later on I need it to rotate over a 0.0,0.0 AnchorPoint.
The problem is I cannot change the anchor point and change the position accordingly, so it stays on the same position, without the object appearing to quickly move and reposition to its original point.
Is there a way I can set the anchor point and the position of my Sprite at once, without it moving at all?. Thank you.
-Oscar
I found a solution to this with a UIView elsewhere, and rewrote it for cocos2d:
- (void)setAnchorPoint:(CGPoint)anchorPoint forSprite:(CCSprite *)sprite
{
CGPoint newPoint = CGPointMake(sprite.contentSize.width * anchorPoint.x, sprite.contentSize.height * anchorPoint.y);
CGPoint oldPoint = CGPointMake(sprite.contentSize.width * sprite.anchorPoint.x, sprite.contentSize.height * sprite.anchorPoint.y);
newPoint = CGPointApplyAffineTransform(newPoint, [sprite nodeToWorldTransform]);
oldPoint = CGPointApplyAffineTransform(oldPoint, [sprite nodeToWorldTransform]);
CGPoint position = sprite.position;
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
sprite.position = position;
sprite.anchorPoint = anchorPoint;
}
This is a good question, and I don't know the full answer yet.
As you may have noticed, the anchorPoint cannot be changed without affecting scale and rotation.
For scaled sprites:
You have to simultaneously change the anchorPoint and position of your sprite. See this question for a hint
For rotated sprites:
Intuition says you would need to simultaneously change anchorPoint, rotation, and position. (I have no idea how to compute this.)
NOTE: I'm still learning graphics programming, so I'm not 100% able to compute this stuff yet.
I've needed this a couple of times and decided to make a extension for CCNode, tested it abit and seems to work fine. Can be really useful to some :)
It's tested with 1.x but It should work fine in 2.x too. Supports transformed nodes and HD.
Just add this to your project and import whenever you need it - It will be added to all classes deriving from CCNode. (CCSprite, CCLayer)
Interface
#import "cocos2d.h"
#interface CCNode (Extensions)
// Returns the parent coordinate for an anchorpoint. Useful for aligning nodes with different anchorpoints for instance
-(CGPoint)positionOfAnchorPoint:(CGPoint)anchor;
// As above but using anchorpoint in points rather than percentage
-(CGPoint)positionOfAnchorPointInPoints:(CGPoint)anchor;
//Sets the anchorpoint, to not move the node set lockPosition to `YES`. Setting it to `NO` is equal to setAnchorPoint, I thought this would be good for readability so you always know what you do when you move the anchorpoint
-(void)setAnchorPoint:(CGPoint)a lockPosition:(BOOL)lockPosition;
#end
Implementation
#import "CCNode+AnchorPos.h"
#implementation CCNode (Extensions)
-(CGPoint)positionOfAnchorPoint:(CGPoint)anchor
{
float x = anchor.x * self.contentSizeInPixels.width;
float y = anchor.y * self.contentSizeInPixels.height;
CGPoint pos = ccp(x,y);
pos = CGPointApplyAffineTransform(pos, [self nodeToParentTransform]);
return ccpMult(pos, 1/CC_CONTENT_SCALE_FACTOR());
}
-(CGPoint)positionOfAnchorPointInPoints:(CGPoint)anchor;
{
CGPoint anchorPointInPercent = ccp(anchor.x/self.contentSize.width, anchor.y/self.contentSize.height);
return [self positionOfAnchorPoint:anchorPointInPercent];
}
-(void)setAnchorPoint:(CGPoint)a lockPosition:(BOOL)lockPosition
{
CGPoint tempPos = [self positionOfAnchorPoint:a];
self.anchorPoint = a;
if(lockPosition)
{
self.position = tempPos;
}
}
#end
Cocos2d-x + Fixed scale
YourClass.h
virtual cocos2d::Vec2 positionFromSprite(cocos2d::Vec2 newAnchorPoint, cocos2d::Sprite *sprite);
YourClass.m
Vec2 YourClass::positionFromSprite(Vec2 newAnchorPoint, cocos2d::Sprite *sprite) {
Rect rect = sprite->getSpriteFrame()->getRect();
Vec2 oldAnchorPoint = sprite->getAnchorPoint();
float scaleX = sprite->getScaleX();
float scaleY = sprite->getScaleY();
Vec2 newPoint = Vec2(rect.size.width * newAnchorPoint.x * scaleX, rect.size.height * newAnchorPoint.y * scaleY);
Vec2 oldPoint = Vec2(rect.size.width * oldAnchorPoint.x * scaleX, rect.size.height * oldAnchorPoint.y * scaleY);
Vec2 position = sprite->getPosition();
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
return position;
}