SCNNode geometry material with CALayer - objective-c

I have a very basic test scene set up: one camera, default lighting and one slowly rotating cube.
I am attempting to add a video as the texture of the cube and have been following the example here to do so.
Here is the specific block of code that is supposed to add an AVPlayerLayer as the material for a node's geometry:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"testVid" withExtension:#"mp4"];
AVAsset *asset = [AVAsset assetWithURL:url];
if (!asset.playable) {
NSLog(#"Err 1");
return;
}
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset];
AVPlayerLayer *layer = [[AVPlayerLayer alloc] init];
layer.player = [AVPlayer playerWithPlayerItem:item];
layer.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[layer.player play];
layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
layer.frame = CGRectMake(0, 0, 600, 800);
CALayer *backLayer = [CALayer layer];
backLayer.backgroundColor = [[UIColor blackColor] CGColor];
backLayer.frame = CGRectMake(0, 0, 600, 800);
[backLayer addSublayer:layer];
self.cubeNode.geometry.firstMaterial.diffuse.contents = backLayer;
//[self.sceneView.layer addSublayer:backLayer];
If I add the layer to a UIView (see commented out line) then I get nice playback fine. However, if I add it to the node I just get a black spinning cube (i.e. the backLayer shows up but not the video).
Is there something I'm missing? Or is this actually not possible?

Related

How to use an additive animation on AVPlayerLayer bounds?

I'm trying to do an additive animation on a AVPlayerLayer's bounds. By simply setting additive to YES, it breaks the animation. I need to do it additive so that the width and height can be animated with different animation parameters. Animating the key paths bounds.size.width and bounds.size.height do not work either.
I can animate other CALayer classes fine with the below code, so I'm starting to wonder if AVPlayerLayer has a bug.
Here is the code:
AVPlayerLayer *vl;
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSURL *path = [[NSBundle mainBundle] URLForResource:#"vid" withExtension:#"mp4"]];
AVPlayer *player = [AVPlayer playerWithURL: path];
vl = [AVPlayerLayer playerLayerWithPlayer:player];
vl.videoGravity = AVLayerVideoGravityResize;
// vl = [CALayer layer]; // a normal calayer animates properly
vl.backgroundColor = [NSColor redColor].CGColor;
vl.frame = CGRectMake(200,100,150,100);
[_window.contentView.layer addSublayer:vl];
}
- (IBAction)animate:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithRect:CGRectMake(0,0,0, 0)];
animation.toValue = [NSValue valueWithRect:CGRectMake(0,0,240, 60)];
animation.duration = 4;
animation.additive = YES;
[vl addAnimation:animation forKey:nil];
}
This is what it looks like:
The red area should be the video playing.
You can check out the sample project I created here:
https://www.dropbox.com/s/jxuh69wiqdwnuc6/PlayerLayer%20size%20Animation.zip?dl=1
The frame property of a CALayer is a derived property, dependent on the position, anchorPoint, bounds and transform of the layer. Instead of animating the frame, you should instead animate the position or bounds, depending on what effect you are trying to accomplish.
You may first take a look at this:
Q: When I try to animate the frame of a CALayer nothing happens. Why?
This can also help:
How to animate the frame of an layer with CABasicAnimation?
And those code may be helpul to you:
NSURL *fileUrl = [NSURL fileURLWithPath:#"yourFilePath"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:fileUrl];
CALayer *superlayer = self.view.layer;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];
[player play];
playerLayer.videoGravity = AVLayerVideoGravityResize;
playerLayer.frame = CGRectMake(10, 200, 320, 200);
playerLayer.position = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
CGRect startBounds = CGRectMake(10, 200, 200, 150);
CGRect endBounds = CGRectMake(10, 200, 320, 200);
CABasicAnimation * sizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
sizeAnimation.fromValue = [NSValue valueWithRect:startBounds] ;
sizeAnimation.toValue = [NSValue valueWithRect:endBounds] ;
sizeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
sizeAnimation.duration = 4;
[playerLayer addAnimation:sizeAnimation forKey:#"bounds"];

CGRectContainsPoint using shape opposed to frame

So we have our detection here
-(void)checkInFOVWithPlayer:(Player *)player andEnemy:(Player *)enemy {
SKNode *fovNode = [player childNodeWithName:player.playersFOVName];
SKNode *node = [self childNodeWithName:#"enemy"];
CGPoint newPosition = [self convertPoint:node.position toNode:fovNode.parent];
if (CGRectContainsPoint(fovNode.frame, newPosition)) {
[self playerAimAtEnemy:enemy withPlayer:player];
}
}
And our implementation for the field of vision
SKShapeNode *fov = [SKShapeNode node];
UIBezierPath *fovPath = [[UIBezierPath alloc] init];
[fovPath moveToPoint:CGPointMake(0, 0)];
[fovPath addLineToPoint:CGPointMake(fovOpposite *-1, fovDistance)];
[fovPath addLineToPoint:CGPointMake(fovOpposite, fovDistance)];
[fovPath addLineToPoint:CGPointMake(0, 0)];
fov.path = fovPath.CGPath;
fov.lineWidth = 1.0;
fov.strokeColor = [UIColor clearColor];
fov.antialiased = NO;
fov.fillColor = [UIColor greenColor];
fov.alpha = 0.2;
fov.name = #"playerFOV";
[_playerImage addChild:fov];
Now, this works. However, the detection range for the "field of vision" is not actually the boundaries of the BezierPath, it's in fact the CGRect that creates the image.
So, the detection will run, even if it's outside of the visual field of vision.
I'm curious as to whether there's an easy fix for this, as I don't really want to go down physics body paths if I don't need to.
Finally I figured out what was required.
I had to redraw the path and optimise it for each frame, after that;
if (CGPathContainsPoint(fovPath.CGPath, nil, newPosition, NO)) {}
Ended my problems.

is it possible to sync an SKSpriteNode with a Video like you can with CALayers?

I would like to synchronize the movement of an SKSpriteNode with a video. So as the video seeks forward, seeks backward or just plays the SKSpriteNode moves accordingly.
I know this can be done easily enough with CALayers, but can it be done with SKSpriteNode(s) in place of CALayers?
// 1) Create a sync layer
AVPlayerItem * item = player.currentItem;
AVSynchronizedLayer* syncLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:item];
syncLayer.frame = CGRectMake(10, 220, 300, 10);
syncLayer.backgroundColor = [[UIColor lightGrayColor] CGColor];
[self.view.layer addSublayer:syncLayer];
// 2) Create a sublayer for the synclayer
CALayer *sublayer = [CALayer layer];
sublayer.frame = CGRectMake(0, 0, 200, 50); //our black box
sublayer.backgroundColor = [[UIColor blackColor] CGColor];
[syncLayer addSublayer:sublayer];
// 3) animate the layer!
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(924, 300)];
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 300)];
anim.removedOnCompletion = NO;
anim.beginTime = AVCoreAnimationBeginTimeAtZero;
anim.duration = CMTimeGetSeconds(item.asset.duration);
NSLog(#"%f",anim.duration);
[sublayer addAnimation:anim forKey:nil];

Animate Mask in Objective C

I am making an application where I have to show 3 images in a single screen, and when User touch one image then that Image should be shown by Animating and resizing.
So for 1st Step I did Masking of 3 images with 3 different Transparent Mask Image from
How to Mask an UIImageView
And my method is like below
- (UIImageView*) maskedImage:(UIImage *)image withMasked:(UIImage *)maskImage {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask1 = [CALayer layer];
mask1.contents = (id)[maskImage CGImage];
mask1.frame = CGRectMake(0, 0, 1024, 768);
imageView.layer.mask = mask1;
imageView.layer.masksToBounds = YES;
return imageView;
}
And I am calling this as
self.image2 = [UIImage imageNamed:#"screen2Full.png"];
self.mask2 = [UIImage imageNamed:#"maskImage.png"];
self.imageView2 = [self maskedImage:self.image2 withMasked:self.mask2];
[self.completeSingleView addSubview:self.imageView2];
This is working perfectly.
Now for Step 2. Animate the Mask, so that Full Image can be shown. I have googled a lot about this but didn't get success. So Please help me. I just want to know how can we Animate and Resize the Mask Image So that I can view a full Image. My concept is as below.
So finally i got the solution from multiple sites and some own logic and hard work obviously ;). So here is the solution of this
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.duration = .3;
layer.position = point;
[layer addAnimation:animation forKey:#"position"];
}
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:oldBounds];
animation.toValue = [NSValue valueWithCGRect:newBounds];
animation.duration = .3;
layer.bounds = newBounds;
[layer addAnimation:animation forKey:#"bounds"];
}
- (UIImageView*) animateMaskImage:(UIImage *)image withMask:(UIImage *)maskImage width:(int )wd andHeight:(int )ht andSize:(CGSize)size andPosition:(CGPoint)pt {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask = [CALayer layer];
mask.contents = (id)[maskImage CGImage];
mask.frame = CGRectMake(0, 0, wd, ht);
//This needs to be place before masking. I don't knwo why. But its working :)
[self resizeLayer:mask to:size];
[self moveLayer:mask to:pt];
imageView.layer.mask = mask;
imageView.layer.masksToBounds = YES;
return imageView;
}
You just need to call this as
[self.imageView1 removeFromSuperview];
self.imageView1 = [self animateMaskedImage:self.image1 withMasked:self.mask1 width:1024 andHeight:768 andSize:CGSizeMake(1024*4, 768*4) andPosition:CGPointMake(1024*2, 768*2)];
[self.completeSingleView addSubview:self.imageView1];

Content change when UIWebView load inside ScrollView

I want to make something like Pulse news when user read the selected news.
When image is not loaded, the space for the image is not yet specified. After the image is loaded, the size of the content also change (the text will be pushed down to make space for the image).
How can i do that?
What i do now is using scroll view, with uiwebview inside it.
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
UIView *thisView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
contentTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, 292, 82)];
contentThumbnailWebView = [[UIWebView alloc] initWithFrame:CGRectMake(10, CGRectGetMaxY(contentTitleLabel.frame), 292, 0)];
contentDateLabel = [[UILabel alloc]initWithFrame:CGRectMake(10, CGRectGetMaxY(contentThumbnailWebView.frame), 292, 23)];
playVideoButton = [[UIButton alloc] initWithFrame:CGRectMake(CGRectGetMaxX(contentThumbnailWebView.frame)/2, CGRectGetMaxY(contentThumbnailWebView.frame)/2, 72, 37)];
[playVideoButton setTitle:#"Play" forState:UIControlStateNormal];
playVideoButton.hidden = YES;
contentSummaryLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, CGRectGetMaxY(contentDateLabel.frame), 292, 20)];
contentScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
contentScrollView.backgroundColor = [UIColor whiteColor];
[thisView addSubview:contentScrollView];
[contentScrollView addSubview:contentDateLabel];
[contentScrollView addSubview:contentTitleLabel];
[contentScrollView addSubview:contentThumbnailWebView];
[contentScrollView addSubview:playVideoButton];
[contentScrollView addSubview:contentSummaryLabel];
//[self.view addSubview:thisView];
self.view = thisView;
contentThumbnailWebView.delegate = self;
}
I've read several topics about this but i still cannot solve it.
- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
CGRect frame = aWebView.frame;
frame.size.height = 1;
aWebView.frame = frame;
CGSize fittingSize = [aWebView sizeThatFits:CGSizeZero];
fittingSize.width = aWebView.frame.size.width;
frame.size = fittingSize;
aWebView.frame = frame;
NSLog(#"size: %f, %f", fittingSize.width, fittingSize.height);
[contentDateLabel setFrame:CGRectMake(10, CGRectGetMaxY(aWebView.frame), 292, 23)];
playVideoButton = [[UIButton alloc] initWithFrame:CGRectMake(CGRectGetMaxX(aWebView.frame)/2, CGRectGetMaxY(aWebView.frame)/2, 72, 37)];
[contentSummaryLabel setFrame:CGRectMake(10, CGRectGetMaxY(contentDateLabel.frame), 292, contentSummaryLabel.frame.size.height )];
contentScrollView.contentSize = CGSizeMake(contentScrollView.frame.size.width, CGRectGetMaxX(contentSummaryLabel.frame));
}
I load jpg image link on it. The image is loaded but the height is not resize correctly. and i also cannot scroll again the scrollView when the webview is loaded.
Below is when i call the image to be loaded
- (void) setImageAsThumbnail:(NSString *)imagehumbnailLink
{
NSURL *imageURL = [NSURL URLWithString:imageThumbnailLink];
NSString *cssString = #"<style type='text/css'>img {width: 292px; height: auto;}</style>";
NSString *myHTMLContent = #"<html><head></head><body><img src='%#'></body></html>";
NSString *htmlString = [NSString stringWithFormat:#"%#%#",cssString,myHTMLContent];
NSString *imageHTML = [[NSString alloc] initWithFormat:htmlString, imageURL];
contentThumbnailWebView.scalesPageToFit = YES;
[contentThumbnailWebView loadHTMLString:imageHTML baseURL:nil];
}
Thank you!
I frequently use UIWebView inside UIScrollView, and because loading HTML is done asynchronously you have to resize the web view and the content size of the scroll view when finished.
[webView sizeThatFits:CGSizeZero]
does not work for me either. Instead, in your webViewDidFinishLoad:(UIWebView*)webView you can measure the size of the HTML document by executing a piece of Javascript:
NSString *js = #"document.getElementById('container').offsetHeight;";
NSString *heightString = [webView stringByEvaluatingJavaScriptFromString:js];
int height = [heightString intValue]; // HTML document height in pixels
In this example, you have to assign the ID 'container' to the top-level element in your HTML (make sure there are no margins around it). This approach works very well for me.