Setting correct frame of a newly created CAShapeLayer - calayer

In short:
Apple does NOT set the frame or bounds for a CAShapeLayer automatically (and Apple HAS NOT implemented an equivalent of [UIView sizeThatFits])
If you set the frame using the size of the bounding-box of the path ... everything goes wrong. No matter how you try to set it, it screws-up the path
So, what's the correct way to programmatically set the frame of a newly-created CAShapeLayer with a newly-added CGPath ? Apple's docs are silent on the matter.
Things I've tried, that don't work:
Create a CAShapeLayer
Create a CGPath, add it to the layer
Check the layer's frame - it's {{0,0},{0,0}}
Set: layer.frame = CGPathGetBoundingBox( layer.path )
Frame is now correct, but the path is now DOUBLE offset - changing the frame causes the path to effectively be shifted an extra (x,y) pixels
Set: layer.bounds = CGPathGetBoundingBox( layer.path )
...it all goes nuts. Nothing makes sense any more
Try fixing it by doing layer.position = CGPathGetBoundingBox( layer.path ).origin
...no dice; still nuts.
One thing I've tried that DID work, but causes problems elsewhere:
EDIT: this BREAKS as soon as you auto-rotate the screen. My guess: Apple's auto-rotate requires control of the "transform" property.
Create a CAShapeLayer
Create a CGPath, add it to the layer
Check the layer's frame - it's {{0,0},{0,0}}
Set: layer.frame = CGPathGetBoundingBox( layer.path )
Set: layer.transform = CGAffineTransformMakeTranslation( CGPathGetBoundingBox( layer.path ).origin.x * -1, // same for y-coord: set it to "-1 * the path's origin
This works, but ... lots of 3rd party code assumes that the initial transform for a CALayer is Identity.
It shouldn't be this difficult! Surely there's something I'm doing wrong here?
(I've had one suggestion: "every time you add a path, manually run a custom function to shift all the points by -1 * (top-left-point.x, top-left-point.y)". Again, that works - but it's ridiculously complex)

Setting layer.bounds to the path bounds is the right thing to do — you want to make the layer's local coordinate space match the path. But you then also need to set the layer's position property to move it into the right place in its superlayer.
(Setting .frame translates into the framework computing the right values of .bounds and .position for you, but it always leaves bounds.origin untouched, which is not what you want when your path bounds has a non-zero origin.)
So something like this should work, assuming you haven't changed the anchorPoint from its usual (.5, .5) value and want to position the layer flush to the origin of its superlayer:
CGRect r = CGPathGetBoundingBox(layer.path);
layer.bounds = r;
layer.position = CGPointMake(r.size.width*.5, r.size.height*.5);

Related

How to invert the sprite position in GameMaker Studio 2 code?

I put this code for my sprite to reverse the position according to its direction but it reverses the position and it looks skinny. How to fix this?
key_left = keyboard_check(ord("A"))
key_right = keyboard_check(ord("D"))
key_jump = keyboard_check(vk_space)
var move = key_right - key_left
hspd = move * spd;
vspd = vspd + grv;
if (hspd != 0) {
image_xscale = sign(hspd)
}
The code's correct. You must have resized the sprite in the room editor, delete the instance and put it in again and don't resize it, it should work.
Also if you need it a little bigger you can (If 1.5 doesn't satisfy you, feel free to use a bigger number).
image_xscale = sign(hspd) * 1.5;
The code seem to be correct, have you tried setting the origin point at the center? By default the origin point is on the top-left, and once it's set at the center of your sprite, it won't change positions when turning around.
You can set the origin point at the sprite window.

Trouble with LibGDX resetting origin on an already rotated actor

If I do:
actor.setOrigin(0, 0);
actor.setRotation(45);
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
It appears that on the last setOrigin call, the actor gets repositioned to the location it would've been if actor.setRotation(45) would have been called after its latest origin was set.
What do I do to make it so that the latest origin of the actor is only used for future "scale" and "rotation" actions?
Okay so i looked in the source code of libgdx, and i'll tell you the short answer.
Basically when you set the origin or the rotation, you just change a variable named "originx", "originy" and "rotation". So every call to setOrigin will overwrite the values set in previous calls.
And every time you draw the actor, it recalculates the bounds using the current variable.
To be clear, setOrigin looks like this :
public void setOrigin (float originX, float originY) {
this.originX = originX;
this.originY = originY;
}
So the precedent setOrigin is lost.
The reposition of the actor itself in your case does not change, but the position of the displayed sprite or texture will change.
It is calculated in this order:
Position -> Origin -> Scale -> Rotation
See: Sprite.java (method: "getVertices ()")
When you change the Origin point of an already rotated element, the point in the plane around which the rotation occurs changes and the sprite will be drawn in a different place (the actor's position in this case does not change).

Opposite of glscissor in Cocos2D?

I found a class called ClippingNode that I can use on sprites to only display a specified rectangular area: https://github.com/njt1982/ClippingNode
One problem is that I need to do exactly the opposite, meaning I want the inverse of that. I want everything outside of the specified rectangle to be displayed, and everything inside to be taken out.
In my test I'm using a position of a sprite, which will update frame, so that will need to happen to meaning that new clipping rect will be defined.
CGRect menuBoundaryRect = CGRectMake(lightPuffClass.sprite.position.x, lightPuffClass.sprite.position.y, 100, 100);
ClippingNode *clipNode = [ClippingNode clippingNodeWithRect:menuBoundaryRect];
[clipNode addChild:darkMapSprite];
[self addChild:clipNode z:100];
I noticed the ClippingNode class allocs inside but I'm not using ARC (project too big and complex to update to ARC) so I'm wondering what and where I'll need to release too.
I've tried a couple of masking classes but whatever I mask fits over the entire sprite (my sprite covers the entire screen. Additionally the mask will need to move, so I thought glscissor would be a good alternative if I can get it to do the inverse.
You don't need anything out of the box.
You have to define a CCClippingNode with a stencil, and then set it to be inverted, and you're done. I added a carrot sprite to show how to add sprites in the clipping node in order for it to be taken into account.
#implementation ClippingTestScene
{
CCClippingNode *_clip;
}
And the implementation part
_clip = [[CCClippingNode alloc] initWithStencil:[CCSprite spriteWithImageNamed:#"white_board.png"]];
_clip.alphaThreshold = 1.0f;
_clip.inverted = YES;
_clip.position = ccp(self.boundingBox.size.width/2 , self.boundingBox.size.height/2);
[self addChild:_clip];
_img = [CCSprite spriteWithImageNamed:#"carrot.png"];
_img.position = ccp(-10.0f, 0.0f);
[_clip addChild:_img];
You have to set an extra flag for this to work though, but Cocos will spit out what you need to do in the console.
I once used CCScissorNode.m from https://codeload.github.com/NoodlFroot/ClippingNode/zip/master
The implementation (not what you are looking for the inverse) was something :
CGRect innerClippedLayer = CGRectMake(SCREENWIDTH/14, SCREENHEIGHT/6, 275, 325);
CCScissorNode *tmpLayer = [CCScissorNode scissorNodeWithRect:innerClippedLayer];
[self addChild:tmpLayer];
So for you it may be like if you know the area (rectangle area that you dont want to show i.e. inverse off) and you know the screen area then you can deduct the rectangle are from screen area. This would give you the inverse area. I have not did this. May be tomorrow i can post some code.

Convert a given point from the window’s base coordinate system to the screen coordinate system

I am trying to figure out the way to convert a given point from the window’s base coordinate system to the screen coordinate system. I mean something like - (NSPoint)convertBaseToScreen:(NSPoint)point.
But I want it from quartz/carbon.
I have CGContextRef and its Bounds with me. But the bounds are with respect to Window to which CGContextRef belongs. For Example, if window is at location (100, 100, 50, 50) with respect to screen the contextRef for window would be (0,0, 50, 50). i.e. I am at location (0,0) but actually on screen I am at (100,100). I
Any suggestion are appreciated.
Thank you.
The window maintains its own position in global screen space and the compositor knows how to put that window's image at the correct location in screen space. The context itself, however doesn't have a location.
Quartz Compositor knows where the window is positioned on the screen, but Quartz 2D doesn't know anything more than how big the area it is supposed to draw in is. It has no idea where Quartz Compositor is going to put the drawing once it is done.
Similarly, when putting together the contents of a window, the frameworks provide the view system. The view system allows the OS to create contexts for drawing individual parts of a window and manages the placement of the results of drawing in those views, usually by manipulating the context's transform, or by creating temporary offscreen contexts. The context itself, however, doesn't know where the final graphic is going to be rendered.
I'm not sure if you can use directly CGContextRef, you need window or view reference or something like do the conversion.
The code I use does the opposite convert mouse coordinates from global (screen) to view local and it goes something like this:
Point mouseLoc; // point you want to convert to global coordinates
HIPoint where; // final coordinates
PixMapHandle portPixMap;
// portpixmap is needed to get correct offset otherwise y coord off at least by menu bar height
portPixMap = portPixMap = GetPortPixMap( GetWindowPort( GetControlOwner( view ) ) );
QDGlobalToLocalPoint(GetWindowPort( GetControlOwner( view ), &mouseLoc);
where.x = mouseLoc.h - (**portPixMap).bounds.left;
where.y = mouseLoc.v - (**portPixMap).bounds.top;
HIViewConvertPoint( &where, NULL, view );
so I guess the opposite is needed for you (haven't tested if it actually works):
void convert_point_to_screen(HIView view, HIPoint *point)
{
Point point; // used for QD calls
PixMapHandle portPixMap = GetPortPixMap( GetWindowPort( GetControlOwner( view ) ) );
HIViewConvertPoint( &where, view, NULL ); // view local to window local coordtinates
point.h = where->x + (**portPixMap).bounds.left;
point.w = where->y + (**portPixMap).bounds.top;
QDLocalToGlobalPoint(GetWindowPort( GetControlOwner( view ), &point);
// convert Point to HIPoint
where->x = point.h;
where->y = point.v;
}

Time Machine style Navigation

I've been doing some programming for iPhone lately and now I'm venturing into the iPad domain. The concept I want to realise relies on a navigation that is similar to time machine in osx. In short I have a number of views that can be panned and zoomed, as any normal view. However, the views are stacked upon each other using a third dimension (in this case depth). the user will the navigate to any view by, in this case, picking a letter, whereupon the app will fly through the views until it reaches the view of the selected letter.
My question is: can somebody give the complete final code for how to do this? Just kidding. :) What I need is a push in the right direction, since I'm unsure how to even start doing this, and whether it is at all possible using the frameworks available. Any tips are appreciated
Thanks!
Core Animation—or more specifically, the UIView animation model that's built on Core Animation—is your friend. You can make a Time Machine-like interface with your views by positioning them in a vertical line within their parent view (using their center properties), having the ones farther up that line be scaled slightly smaller than the ones below (“in front of”) them (using their transform properties, with the CGAffineTransformMakeScale function), and setting their layers’ z-index (get the layer using the view’s layer property, then set its zPosition) so that the ones farther up the line appear behind the others. Here's some sample code.
// animate an array of views into a stack at an offset position (0 has the first view in the stack at the front; higher values move "into" the stack)
// took the shortcut here of not setting the views' layers' z-indices; this will work if the backmost views are added first, but otherwise you'll need to set the zPosition values before doing this
int offset = 0;
[UIView animateWithDuration:0.3 animations:^{
CGFloat maxScale = 0.8; // frontmost visible view will be at 80% scale
CGFloat minScale = 0.2; // farthest-back view will be at 40% scale
CGFloat centerX = 160; // horizontal center
CGFloat frontCenterY = 280; // vertical center of frontmost visible view
CGFloat backCenterY = 80; // vertical center of farthest-back view
for(int i = 0; i < [viewStack count]; i++)
{
float distance = (float)(i - offset) / [viewStack count];
UIView *v = [viewStack objectAtIndex:i];
v.transform = CGAffineTransformMakeScale(maxScale + (minScale - maxScale) * distance, maxScale + (minScale - maxScale) * distance);
v.alpha = (i - offset > 0) ? (1 - distance) : 0; // views that have disappeared behind the screen get no opacity; views still visible fade as their distance increases
v.center = CGPointMake(centerX, frontCenterY + (backCenterY - frontCenterY) * distance);
}
}];
And here's what it looks like, with a couple of randomly-colored views:
do you mean something like this on the right?
If yes, it should be possible. You would have to arrange the Views like on the image and animate them going forwards and backwards. As far as I know aren't there any frameworks for this.
It's called Cover Flow and is also used in iTunes to view the artwork/albums. Apple appear to have bought the technology from a third party and also to have patented it. However if you google for ios cover flow you will get plenty of hits and code to point you in the right direction.
I have not looked but would think that it was maybe in the iOS library but i do not know for sure.

Resources