Stop Camera from rotating around a rotating parent - objective-c

I have a rolling marble and would like my camera to follow it. My problem is that my camera is also rotating around my object as it is rotating.
Here's my code.
-(void) setupCameraOnMarble:(SCNNode*)marble
{
[cameraNode removeFromParentNode];
[marble addChildNode:cameraNode];
SCNLookAtConstraint *marbleStare = [SCNLookAtConstraint lookAtConstraintWithTarget:marble];
marbleStare.gimbalLockEnabled = YES;
cameraNode.constraints = #[marbleStare];
}
I'm thinking that a transform constraint should work but I don't understand how it works.

Are you sure you need to make cameraNode a child of marble?
Because according to SKNode docs: when a node’s coordinate system is scaled or rotated, this transformation is applied both to the node’s own content and to that of its descendants see SKNode
So i assume you need to un-child camera from marble. And to achive that followint the object behavior from camera, you may update camera coordinates somewhere in the code, that moves the marble

I have un-childed my camera to my sphere and made my class a SCNSceneRendererDelegate protocol and added this to the rendering loop:
-(void) renderer:(id)renderer updateAtTime:(NSTimeInterval)time
{
[cameraNode setPosition: SCNVector3Make(marbleNode.presentationNode.position.x, marbleNode.presentationNode.position.y + 5, marbleNode.presentationNode.position.z + 10)];
}
If I log the coordinates of the marble.presentationNode they are changing but my camera still doesn't move!

Related

How can I ensure I still get correct touch inputs when my scene is offset?

How can I accept touch input beyond the scene's bounds, so that no matter what I set self.position to, touches can still be detected?
I'm creating a tile based game from Ray Winderlich on Cocos2d version 3.0. I am at the point of setting the view of the screen to a zoomed in state on my tile map. I have successfully been able to do that although now my touches are not responding since I'm out of the coordinate space the touches used to work on.
This method is called to set the zoomed view to the player's position:
-(void)setViewPointCenter:(CGPoint)position{
CGSize winSize = [CCDirector sharedDirector].viewSizeInPixels;
int x = MAX(position.x, winSize.width/2);
int y = MAX(position.y, winSize.height/2);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) - winSize.width / 2);
y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) - winSize.height / 2);
CGPoint actualPosition = ccp(x, y);
CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
NSLog(#"centerOfView%#", NSStringFromCGPoint(centerOfView));
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
NSLog(#"viewPoint%#", NSStringFromCGPoint(viewPoint));
//This changes the position of the helloworld layer/scene so that
//we can see the portion of the tilemap we're interested in.
//That however makes my touchbegan method stop firing
self.position = viewPoint;
}
This is what the NSLog prints from the method:
2014-01-30 07:05:08.725 TestingTouch[593:60b] centerOfView{512, 384}
2014-01-30 07:05:08.727 TestingTouch[593:60b] viewPoint{0, -832}
As you can see the y coordinate is -800. If i comment out the line self.position = viewPoint then the self.position reads {0, 0} and touches are detectable again but then we don't have a zoomed view on the character. Instead it shows the view on the bottom left of the map.
Here's a video demonstration.
How can I fix this?
Update 1
Here is the github page to my repository.
Update 2
Mark has been able to come up with a temporary solution so far by setting the hitAreaExpansion to a large number like so:
self.hitAreaExpansion = 10000000.0f;
This will cause touches to respond again all over! However, if there is a solution that would not require me to set the property with an absolute number then that would be great!
-edit 3-(tldr version):
setting the contentsize of the scene/layer to the size of the tilemap solves this issue:
[self setContentSize: self.tileMap.contentSize];
original replies below:
You would take the touch coordinate and subtract the layer position.
Generally something like:
touchLocation = ccpSub(touchLocation, self.position);
if you were to scale the layer, you would also need appropriate translation for that as well.
-edit 1-:
So, I had a chance to take another look, and it looks like my 'ridiculous' number was not ridiculous enough, or I had made another change. Anyway, if you simply add
self.hitAreaExpansion = 10000000.0f; // I'll let you find a more reasonable number
the touches will now get registered.
As for the underlying issue, I believe it to be one of content scale that is not set correctly, but again, I'll now leave that to you. I did however find out that when looking through some of the tilemap class, that tilesize is said to be in pixels, not points, which I guess is somehow related to this.
-edit 2-:
It bugged me with the sub-optimal answer, so I looked a little further. Forgive me, I hadn't looked at v3 until I saw this question. :p
after inspecting the base class and observing the scene/layer's value of:
- (BOOL)hitTestWithWorldPos:(CGPoint)pos;
it became obvious that the content size of the scene/layer was being set to the current view size, which in the case of an iPad is (1024, 768)
The position of the layer after the setViewPointCenter call is fully above the initial view's position, hence, the touch was being suppressed. by setting the layer/scene contentSize to the size of the tilemap, the touchable area is now expanded over the entire map, which allows the node to process the touch.

Zoom with a drawing view

I'm looking for a solution in order to have a beautiful zoom on a drawing view.
In my app, I have a view with an other UIView (which is used like a drawing view) and when I draw a stroke on it, the stroke is perfect. But when I zoom the view, I have this really ugly effect (a pixelised stroke) :
(source: imagup.com)
url image
Is there a solution in order to have a proper stroke ?
My UIViewController has a hierarchy like that :
UIViewController
ScrollView
View zoomable (defined with the viewForZoomingInScrollView method)
Image view
Drawing view
Thanks a lot !
Regards,
Sébastien ;)
I'm in the process of making a vector drawing application and let me tell you, this is NOT a trivial task to do correctly and requires quite a bit of work.
Some issues to keep in mind:
If you are not using vector graphics (CGPaths, for example, are
vectors) you will NOT be able to remove the pixelation. A UIImage,
for example, only has so much resolution.
In order to get your drawing to not look pixelated, you are going to
have to redraw everything. If you have a lot of drawing, this can be
an expensive task to perform.
Having good resolution WHILE zooming is nearly impossible because it would require an excessively large context and your drawing would likely exceed the capabilities of the device
I use core graphics to do my drawing, so the way I solved this issue was by allocating and managing multiple CGContexts and using them as buffers. I have one context that is ALWAYS kept at my least zoomed level (scale factor of 1). That context is drawn into at all times and makes it so that when unzooming completely, no time is spent redrawing since it is already done. Another context is used soley for drawing when zoomed. When not zoomed, that context is ignored (since it will have to be redrawn based on the new zoom level anyway). A high level algorithm for how I perform my zooming is as follows:
- (IBAction)handlePinchGesture:(UIGestureRecognizer *)sender
{
if(sender.state == UIGestureRecognizerStateBegan)
{
//draw an image from the unzoomedContext into my current view
//set the scale transformation of my current view to be equal to "currentZoom", a property of the view that keeps track of the actual zoom level
}
else if(sender.state == UIGestureRecognizerStateChanged)
{
//determine the new zoom level and transform the current view, keeping track in the currentZoom property
//zooming will be pixelated.
}
else if(sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
{
if(currentZoom == 1.0)
{
//you are done because the unzoomedContext image is already drawn into the view!
}
else
{
//you are zoomed in and will have to do special drawing
//perform drawing into your zoomedContext
//scale the zoomedContext
//set the scale of your current view to be equal to 1.0
//draw the zoomedContext into the current view. It will not be pixelated!
//any drawing done while zoomed needs to be "scaled" based on your current zoom and translation amounts and drawn into both contexts
}
}
}
This gets even more complicated for me because I have additional buffers for the buffers because drawing images of my paths is much faster than drawing paths when there is lots of drawing.
Between managing multiple contexts, tweaking your code to draw efficiently into multiple contexts, following proper OOD, scaling new drawing based on your current zoom and translation, etc, this is a mountain of a task. Hopefully this either motivates you and puts you on the right track, or you decide that getting rid of that pixelation isn't worth the effort :)
I had the same problem and found a solution: tell the view to use a CATiledLayer as backing layer, then tell the view how many levels of zoom it supports. This worked for me, my drawing methods get automatically called when the (parent) view is zoomed.
A short explanation of levelsOfDetail and levelsOfDetailBias:
levelsOfDetail determine how many zooming levels there are in total
levelsOfDetailBias determine how many of those are zooming in.
So in my example I have 4 zooming levels, 3 are zoomed in and 1 is the non-zoomed level, meaning my view only redraws when zooming in.
#imprementation MyZoomableView
+ (Class)layerClass
{
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
((CATiledLayer *)self.layer).levelsOfDetail = 4;
((CATiledLayer *)self.layer).levelsOfDetailBias = 3;
}
return self;
}
#end
Use [self setContentScaleFactor:scale]; in your scrollViewDidEndZooming: delegate method.

UIScrollView - Custom Map - Prevent marker subview on map from scaling with map

I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.
I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.
I haven't been able to find any information on this, what would be the best way to go about this?
If there is anything I can provide you with to help the answer, please let me know.
Many thanks!
EDIT -
Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.
EDIT 2-
Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.
I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).
Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:
CGPoint ULPC = userLocationPlaceholder.center;
float zs = scrollView.zoomScale;
CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);
Where the image is 40points wide. This matches the centers perfectly.
The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.
I have tried using a self calling animation as such:
-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
animations:^{
userLocationDotContainer.frame = newFrame;
} completion:^(BOOL finished){
// Call self
[self animateUserLocationAttachment];
}];
}
As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.
Is there any way I can get around this, or an alternative method I can apply?
Many thanks
EDIT 3 -
I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.
The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.
The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.
I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).
I did subclass UIImageView and implement setTransform: method like here:
#import "SLImageView.h"
#implementation SLImageView
- (void)setTransform:(CGAffineTransform)transform
{
[super setTransform:transform];
CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);
for (id obj in self.subviews)
{
if ([obj isKindOfClass:[MKAnnotationView class]])
{
[((UIView *)obj) setTransform:invertedTransform];
}
}
}
#end
This works perfectly!
Mick.
Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.
Relative coordinate systems
Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.
Get the origin of the container view
Get the location of the subview inside of the container view
Get the origin of the subview
Calculate the difference in the positions of the origins
Get the location of the object you want to overlap (relative to the subview)
Calculate the location of the object you want to overlap relative to the container view
Move your crosshair to this position
Swift equivalent for Mick's answer:
class MapContainerView:UIView {
#IBOutlet var nonScalingViews: [UIView]!
override var transform: CGAffineTransform {
didSet {
guard let nonScalingViews = nonScalingViews else {
return
}
let invertedTransform = CGAffineTransformInvert(transform)
for view in nonScalingViews {
view.transform = invertedTransform
}
}
}
}

iOS OpenGL layer and UIScrollView

I'm creating a drawing app on the iPad where the user can draw and scroll through the drawing. (Think of a canvas 4000 pixels wide with a view port width of 1024) At the moment, I'm using OpenGL for the drawing, and with a width of 1024, it works great. When I change the frame size of the UIView to 4000, I get "failed to make complete framebuffer object 8cd6". When I reduced it to a width of 2000, I ended up getting "wacky" results. I know I can manipulate the frame correctly, as having a frame width of 500 creates the correct result.
I was also thinking of leaving the width 1024 and moving the camera of the OpenGL layer, but how would that work with the UIScrollView that I setup? So I'm unsure on what to do at the moment. Any advice?
Thanks in advance
P.S. The code is largely based of GLPaint sample Apple provides here
I think you'd be best off with the scheme you suggest towards the end — keeping the OpenGL view static and outside of the scroll view, and moving its contents so as to match the movement of the scroll view.
Assuming you're using a GLKView, implement your glkView:drawInRect: so that it gets the contentOffset (and, probably, the bounds) properties from your scroll view and draws appropriately. E.g. (pretending you're using GLES 1.0 just because the matrix manipulation methods are so well known):
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// displayArea will be the area the scroll view is
// currently displaying; taking just the bounds would
// likely be fine too
CGRect displayArea;
displayArea.origin = scrollView.contentOffset;
displayArea.size = scrollView.bounds.size;
// assuming (0, 0) in the GL view is in the centre,
// we'll adjust things so that it's in the corner ala
// UIKit
CGPoint centre = CGPointMake(
displayArea.origin.x + displayArea.size.width*0.5f,
displayArea.origin.y + displayArea.size.height*0.5f);
glPushMatrix();
// apply the scroll as per the scroll view
// so that its centre is aligned with our centre
glTranslatef(-centre.x, -centre.y, -1);
/* rest of drawing here */
glPopMatrix();
}
Then connect yourself as a delegate to the scrollview and just perform:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[glView setNeedsDisplay];
}
To ensure the GL redraws whenever the scroll view is scrolled.
I would strongly recommend to do your zooming in panning with your OpenGL camera instead of trying to use it in a UIScrollView. It will be a little more work to set up, but ultimately the way to go IMHO.

Translating clicked NSPoint in NSImageView to correct pixel coordinates in the underlying NSBitmapImageRep

Here is what I'm trying to accomplish: I'm working on an open source TI calculator emulator where I'm currently trying to add skin support. The skin image is just an NSImageView with its image set to the skin image. I override the mouseDown: method and get the location of the mouse in the NSImageView coordinates using convertPointFromBase: with locationInWindow return value as the point to convert.
I then grab the underlying NSBitmapImageRep from the NSImageView using bestRepresentationForDevice: with a nil argument. Finally I call getPixel:atX:y on the NSBitmapImageRep with point.x and point.y for the location.
However, the origin of the NSImageView begins in the bottom left (as usual), whereas the coordinates for the NSBitmapImageRep begin in the top left. I can't seem to find an obvious way to compensate for this, so the hit testing for button clicks doesn't function properly. Any help is greatly appreciated.
I never considered overriding isFlipped to return YES in my NSImageView subclass. That takes care of the problem.
Seems pretty easy to me, just calculate the y coordinate based on the height of the rect:
NSPoint pointInFlippedRect(NSPoint inPoint, NSRect aRect)
{
return NSMakePoint(inPoint.x , NSHeight(aRect) - inPoint.y);
}