Interpolating the values with a custom function is very easy. But is it bad practice? Should I instead (or in addition) use keyTimes, timingFunctions or timingFunction to explain the animation-curve to the framework? When working with custom animation curves I really don't see why I should use those properties. I want to do this right.
This works just fine. As expected it animates the views-position with a custom cubic-ease-out animation curve:
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position.x"];
anim.duration = 5;
NSUInteger numberOfFrames = anim.duration * 60;
NSMutableArray *values = [NSMutableArray new];
for (int i = 0; i < numberOfFrames; i++)
{
CGFloat linearProgress = (double) i / (double) numberOfFrames;
CGPoint position = view.layer.position;
position.x = 10 + (300 * CubicEaseOut(linearProgress));
[values addObject:[NSValue valueWithCGPoint:position]];
}
anim.values = values;
[view.layer addAnimation:anim forKey:#"position"];
Your method adds 300 keyframes to the animation, for Core Animation to interpolate, linearly. There are two reasons this might not be worse than using fewer keyframes with non-linear interpolation to to get the same result: (1) more data to send to CA, i.e. more data to store and read every animation frame; (2) if you ever wanted to slow down the animation so that more than 300 frames are rendered from it the linear interpolation artifacts may become visible.
If you just have one 3s animation, it's likely neither of those reasons are important, but e.g. if you had 100 10s animations all running at once you may see worse performance than if using fewer keyframes.
Related
I've been working on getting a clean sine wave sound that can change frequencies when different notes are played. From what I've understood, I need to resize the buffer's frameLength relative to the frequency to avoid those popping sounds caused when the frame ends on a sine's peak.
So on every iteration, I set the frameLength and then populate buffer with the signal.
AVAudioPlayerNode *audioPlayer = [[AVAudioPlayerNode alloc] init];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[audioPlayer outputFormatForBus:0] frameCapacity:44100*10];`
while(YES){
AVAudioFrameCount frameCount = ceil(44100.0/osc.frequency);
[buffer setFrameLength:frameCount];
[audioPlayer scheduleBuffer:buffer atTime:0 options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
for(int i = 0; i < [buffer frameLength]; i++){
for (int channelNumber = 0; channelNumber < channelCount ; channelNumber++) {
float * const channelBuffer = floatChannelData[channelNumber];
channelBuffer[i] = [self getSignalOnFrame:i];
}
}
}
where the signal is generated from:
(float)getSignalOnFrame:(int)i {
float sampleRate = 44100.0;
return [osc amplitude] * sinf([osc frequency] * i * 2.0 * M_PI / sampleRate);
}
The starting tone sounds fine and there are no popping sounds when notes change but the notes themselves sound like they're being turned into sawtooth waves or something.
Any ideas on what I might be missing here?
Or should I just create a whole new audioPlayer with a fresh buffer for each note played?
Thanks for any advice!
If the buffers are contiguous, then a better method to not have discontinuities in sine wave generation is to remember the phase of the sinewave at the end of one buffer, and use that phase as the starting point (angle) to generate the next buffer.
If the buffers are not contiguous, then a common way to avoid clicks is to gradually taper the first and last few milliseconds of each buffer from full gain to zero. A linear gain taper will do, but a raised cosine taper is a slightly smoother taper.
Is there a way in AppKit to measure the width of a large number of NSString objects(say a million) really fast? I have tried 3 different ways to do this:
[NSString sizeWithAttributes:]
[NSAttributedString size]
NSLayoutManager (get text width instead of height)
Here are some performance metrics
Count\Mechanism sizeWithAttributes NSAttributedString NSLayoutManager
1000 0.057 0.031 0.007
10000 0.329 0.325 0.064
100000 3.06 3.14 0.689
1000000 29.5 31.3 7.06
NSLayoutManager is clearly the way to go, but the problem being
High memory footprint(more than 1GB according to profiler) because of the creation of heavyweight NSTextStorage objects.
High creation time. All of the time taken is during creation of the above strings, which is a dealbreaker in itself.(subsequently measuring NSTextStorage objects which have glyphs created and laid out only takes about 0.0002 seconds).
7 seconds is still too slow for what I am trying to do. Is there a faster way? To measure a million strings in about a second?
In case you want to play around, Here is the github project.
Here are some ideas I haven't tried.
Use Core Text directly. The other APIs are built on top of it.
Parallelize. All modern Macs (and even all modern iOS devices) have multiple cores. Divide up the string array into several subarrays. For each subarray, submit a block to a global GCD queue. In the block, create the necessary Core Text or NSLayoutManager objects and measure the strings in the subarray. Both APIs can be used safely this way. (Core Text) (NSLayoutManager)
Regarding “High memory footprint”: Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint.
Regarding “All of the time taken is during creation of the above strings, which is a dealbreaker in itself”: Are you saying all the time is spent in these lines:
double random = (double)arc4random_uniform(1000) / 1000;
NSString *randomNumber = [NSString stringWithFormat:#"%f", random];
Formatting a floating-point number is expensive. Is this your real use case? If you just want to format a random rational of the form n/1000 for 0 ≤ n < 1000, there are faster ways. Also, in many fonts, all digits have the same width, so that it's easy to typeset columns of numbers. If you pick such a font, you can avoid measuring the strings in the first place.
UPDATE
Here's the fastest code I've come up with using Core Text. The dispatched version is almost twice as fast as the single-threaded version on my Core i7 MacBook Pro. My fork of your project is here.
static CGFloat maxWidthOfStringsUsingCTFramesetter(
NSArray *strings, NSRange range) {
NSString *bigString =
[[strings subarrayWithRange:range] componentsJoinedByString:#"\n"];
NSAttributedString *richText =
[[NSAttributedString alloc]
initWithString:bigString
attributes:#{ NSFontAttributeName: (__bridge NSFont *)font }];
CGPathRef path =
CGPathCreateWithRect(CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX), NULL);
CGFloat width = 0.0;
CTFramesetterRef setter =
CTFramesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef)richText);
CTFrameRef frame =
CTFramesetterCreateFrame(
setter, CFRangeMake(0, bigString.length), path, NULL);
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
for (id item in lines) {
CTLineRef line = (__bridge CTLineRef)item;
width = MAX(width, CTLineGetTypographicBounds(line, NULL, NULL, NULL));
}
CFRelease(frame);
CFRelease(setter);
CFRelease(path);
return (CGFloat)width;
}
static void test_CTFramesetter() {
runTest(__func__, ^{
return maxWidthOfStringsUsingCTFramesetter(
testStrings, NSMakeRange(0, testStrings.count));
});
}
static void test_CTFramesetter_dispatched() {
runTest(__func__, ^{
dispatch_queue_t gatherQueue = dispatch_queue_create(
"test_CTFramesetter_dispatched result-gathering queue", nil);
dispatch_queue_t runQueue =
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
dispatch_group_t group = dispatch_group_create();
__block CGFloat gatheredWidth = 0.0;
const size_t Parallelism = 16;
const size_t totalCount = testStrings.count;
// Force unsigned long to get 64-bit math to avoid overflow for
// large totalCounts.
for (unsigned long i = 0; i < Parallelism; ++i) {
NSUInteger start = (totalCount * i) / Parallelism;
NSUInteger end = (totalCount * (i + 1)) / Parallelism;
NSRange range = NSMakeRange(start, end - start);
dispatch_group_async(group, runQueue, ^{
double width =
maxWidthOfStringsUsingCTFramesetter(testStrings, range);
dispatch_sync(gatherQueue, ^{
gatheredWidth = MAX(gatheredWidth, width);
});
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return gatheredWidth;
});
}
Like many that post about this topic, I too am busy trying to write myself an accurate simulator for the movement of objects in a 2D gravitation field.
I decided early on that I would settle on Velocity Verlet Integration, as I want my objects to maintain stable orbits and conserve energy even if the timestep is rather large. So, what might the problem be?
Well, so far, everything seems to behave correctly, except for one component. When I try to calculate the correct velocity for a stable orbit at a certain distance, the resulting velocity sends them into odd elliptical orbits that quickly increase in magnitude each time.
So, to begin, here are the following methods that determine an objects next position, velocity, and acceleration in scene: (Objective C)
Acceleration:
-(CGVector)determineAccelerationFor:(SKObject *)object
{ // Ok, let's find Acceleration!
CGVector forceVector = (CGVector){0,0}; // Blank vector that we will add forces to
for (SKObject *i in self.sceneObjects)
{
if (![i isEqual:object]) // Just make sure we're not counting ourselves here
{
CGPoint distance = [self getDistanceBetween:i.position And:object.position];
float hypotenuse = sqrtf(powf(distance.x, 2)+ powf(distance.y, 2));
float force = ((self.gravity * object.mass * i.mass)/powf(hypotenuse, 3));
float xMagnitude = (force * distance.x);
float yMagnitude = (force * distance.y);
forceVector.dx += xMagnitude;
forceVector.dy += yMagnitude;
}
}
CGVector acceleration = (CGVector){forceVector.dx/object.mass, forceVector.dy/object.mass};
return acceleration;
}
Cool, so basically, I just take an object, add all the other forces that each other object imposes on it together then divide the X & Y factor by the mass of the current object to get the acceleration!
Next up is Velocity. Here I use the following equation:
The method for it is pretty straightforward too:
-(CGVector)determineVelocityWithCurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao NewAcceleration:(CGVector)a
{
float xVelocity = (v.dx + ((ao.dx + a.dx)/2) * self.timeStep);
float yVelocity = (v.dy + ((ao.dy + a.dy)/2) * self.timeStep);
CGVector velocity = (CGVector){xVelocity,yVelocity};
return velocity;
}
And finally, position! The equation for this is:
And it is determined with the following method!
-(CGPoint)determinePositionWithCurrentPosition:(CGPoint)x CurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao
{
float xPosition = (x.x + v.dx * self.timeStep + ((ao.dx * powf(self.timeStep, 2))/2));
float yPosition = (x.y + v.dy * self.timeStep + ((ao.dy * powf(self.timeStep, 2))/2));
CGPoint position = (CGPoint){xPosition,yPosition};
return position;
}
This is all called from the below method!!
-(void)refreshPhysics:(SKObject *)object
{
CGPoint position = [self determinePositionWithCurrentPosition:object.position CurrentVelocity:object.velocity OldAcceleration:object.acceleration]; // Determine new Position
SKAction *moveTo = [SKAction moveTo:position duration:0.0];
[object runAction:moveTo]; // Move to new position
CGVector acceleration = [self determineAccelerationFor:object]; // Determine acceleration off new position
CGVector velocity = [self determineVelocityWithCurrentVelocity:object.velocity OldAcceleration:object.acceleration NewAcceleration:acceleration];
NSLog(#"%# Old Velocity: %f, %f",object.name,object.velocity.dx,object.velocity.dy);
NSLog(#"%# New Velocity: %f, %f\n\n",object.name,velocity.dx,velocity.dy);
[object setAcceleration:acceleration];
[object setVelocity:velocity];
}
Okay, so those methods above dictate how objects are moved in scene. Now onto the initial issue, the ever present problem of achieving a stable orbit!
In order to determine what velocity an object should have to maintain an orbit, I use the following equation:
And I implement that as follows:
-(void)setObject:(SKObject *)object ToOrbit:(SKObject *)parent
{
float defaultSeparation = 200;
// Move Object to Position at right of parent
CGPoint defaultOrbitPosition = (CGPoint){parent.position.x + (parent.size.width/2)+ defaultSeparation,parent.position.y};
[object setPosition:defaultOrbitPosition];
// Determine Orbital Velocity
float velocity = sqrtf((self.gravity * parent.mass)/(parent.size.width/2+defaultSeparation));
CGVector vector = (CGVector){0,velocity};
[object setVelocity:vector];
}
And for some reason, despite this, I get abysmal results. Here is some of the output:
Information:
Gravity(constant) = 1000 (For test purposes)
Mass(Parent) = 5000 units
Mass(Satellite) = 1 units
Separation = 224 pixels
It determines that in order for the Satellite to Orbit the Parent, a velocity of:
149.403580 pixels/timeStep
is required. And that checks out on my calculator.
So this has left me a little confused as to what could be going wrong. I log all the output concerning new velocities and positions, and it does use the velocity I set it to, but that just doesn't seem to make a difference. If anyone could possible help spot what's going wrong here I would be immensely grateful.
If anyone believes I have left something out, tell me and I will edit this right away. Thanks!
I have a CADisplayLink running in line with Chipmunk Physics, and I'm getting very slow performance. I put an NSLog in the method that's called on the CADisplayLink update, and it's being called an average of 22 times per second. I was under the impression that that should be nearer 60. I have the frameInterval set to 1, so should it be 60fps, in a perfect world? The delta times are averaging around 0.0167 seconds (and 1 / 60 IS 0.0167, which is confusing me even further).
I just have four walls around the bounds of my screen and just eight circle-shaped bodies on-screen, updating to UIButton instances on each call, so I don't think I'm doing anything that should tax it to this extent on both my 4S and iPad3. I'm applying a random force to each button once every 2.5 seconds in a separate method. Running in the simulator is butter-smooth, so it's a device-only issue. Can anyone help me spot what's causing the slowdown here, and what I can do about it?
Here's the relevant code, first that which sets up the link:
[NSTimer scheduledTimerWithTimeInterval: 2.5f target: self selector: #selector(updateForces) userInfo: nil repeats: YES];
_displayLink = [CADisplayLink displayLinkWithTarget: self selector: #selector(update)];
_displayLink.frameInterval = 1;
[_displayLink addToRunLoop: [NSRunLoop mainRunLoop] forMode: NSRunLoopCommonModes];
Here's the method that should be called (I think!) 60 times per second, but is called only 22 or so:
if (!gameIsPaused) {
cpFloat dt = _displayLink.duration * _displayLink.frameInterval;
cpSpaceStep([[AGChipmunkSpace sharedInstance] space], dt);
for (LCBall *i in balls) {
cpVect pos1 = cpBodyGetPos(i.body);
CGAffineTransform trans1 = CGAffineTransformMakeTranslation(pos1.x, pos1.y);
CGAffineTransform rot1 = CGAffineTransformMakeRotation(cpBodyGetAngle(i.body));
i.button.transform = CGAffineTransformConcat(rot1, trans1);
}
}
And finally, here's the method that's called every 2.5 seconds, to apply the random forces (updateForces):
if (!gameIsPaused) {
for (LCBall *i in balls) {
int randomAngle = arc4random() % 360;
CGPoint point1 = [self getVectorFromAngle: randomAngle AndMagnitude: (arc4random() % 40) + ((arc4random() % 20) + 15)];
i.body -> f = cpv(point1.x, point1.y);
}
}
(Also, here's my method to get a vector from an angle, which I doubt is causing the issue):
angle = (angle / 180.0) * M_PI;
float x = magnitude * cos(angle);
float y = magnitude * sin(angle);
CGPoint point = CGPointMake(x, y);
return point;
Turns out I had a method on a different UIViewController in my storyboard that was firing every 0.1 seconds that hadn't turned off, and combined with the physics processing, was bogging things down.
I am still learning about UIAnimations, just got into it and I have stumbled upon a problem that I am not sure how to solve. I've seen games where you get a new high score and it adds the new high score to the old high score and they make the numbers animate up or down. It looks very cool and visually appeasing.
Can anyone explain to me how this is done? I apologize if this question is easily solved, like I said I am still trying to learn/perfect animations.
Thanks in advance
I took the code from the post sergio suggested you look at, but took note of Anshu's mention that you wanted a moving up and down animation rather then a fade-in/fade-out animation, so I changed the code to fit what you wanted. Here you go:
// Add transition (must be called after myLabel has been displayed)
CATransition *animation = [CATransition animation];
animation.duration = 1.0; //You can change this to any other duration
animation.type = kCATransitionMoveIn; //I would assume this is what you want because you want to "animate up or down"
animation.subtype = kCATransitionFromTop; //You can change this to kCATransitionFromBottom, kCATransitionFromLeft, or kCATransitionFromRight
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[myLabel.layer addAnimation:animation forKey:#"changeTextTransition"];
// Change the text
myLabel.text = newText;
Hope this helps!
People can correct me if I'm wrong here, but I'm pretty sure you have to code this animation manually. You might be able to find an open source version somewhere online if you look hard enough.
It might be possible to take an image of a UILabel and use sizeWithFont: to determine how wide each character is, then cut the image up into sections based on each digit. Alternatively you could just have multiple UILabels for each digit.
Once you have an array of digit images, you'd have to calculate which digits are going to change during the transition and whether they're going to increase or decrease, then transition to the next digit by pushing it in from the top/bottom (I think there's a built in transition to do this, look around in the Core Animation docs).
You would probably want to determine by how much they increase/decrease and use that to figure out how long the animation will take. That way, if you're going from 5 to 900, the last digit would have to be animating very quickly, the second to last would animate 1/10 as quickly, the third would be 1/100, etc.
This does on ok job, using the reveal function. It would be nice to have some vertical motion, but it's either going to be kCATransitionFromBottom or kCATransitionFromTop - and really we'd need kCATransitionFromBottom | kCATransitionToTop, but that's not a thing. Here's the code:
-(void)countUpLabel:(UILabel *)label fromValue:(int)fromValue toValue:(int)toValue withDelay:(float)delay{
int distance = (int)toValue - (int)fromValue;
int absDistance = abs(distance);
float baseDuration = 1.0f;
float totalDuration = absDistance / 100.0f * baseDuration;
float incrementDuration = totalDuration / (float)absDistance;
int direction = (fromValue < toValue) ? 1 : -1;
//NSString * subtype = (direction == 1) ? kCATransitionFromBottom : kCATransitionFromTop;
for (int n = 0; n < absDistance; n++){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CATransition * fade = [CATransition animation];
fade.removedOnCompletion = true;
fade.duration = incrementDuration;
fade.type = kCATransitionReveal;
fade.subtype = kCATransitionMoveIn;
[label.layer addAnimation:fade forKey:#"changeTextTransition"];
int value = fromValue + (n+1) * direction;
label.text = [NSString stringWithFormat:#"%i", value];
});
delay += incrementDuration;
}
}