Physical Simulation (timer-based animation 11.2), Timer 11.2

Source: Internet
Author: User

Physical Simulation (timer-based animation 11.2), Timer 11.2
Physical Simulation

Even if the timer-based animation is used to copy the key frame behavior in chapter 1, there are some essential differences: in the implementation of the key frame, we calculated all frames in advance, but in the new solution, we actually calculate as needed. It means that we can modify the animation logic in real time based on user input, or integrate with other real-time animation systems, such as physical engines.

Chipmunk

We will create a realistic gravity simulation effect based on physics to replace the current buffer-based Elastic animation, but even if the simulation of 2D physical effects is nearly extremely complex, so don't try to implement it, just use the Open Source Physical engine library.

The physical engine we will use is called Chipmunk. The other 2D physical engine can also be used (for example, Box2D), but Chipmunk uses pure C writing instead of C ++. The advantage is that it is easier to integrate with Objective-C Projects. Chipmunk has many versions, including an "indie" version bound to Objective-C. The C language version is free of charge, so we can use it. When writing this book, 6.1.4 is the latest version; you can download it from the http://chipmunk-physics.net.

Chipmunk's complete physical engine is quite complex, but we only use the following classes:

  • cpSpace-This is the container of all physical structures. It has a size and an optional Gravity Vector.
  • cpBody-It is a solid, non-elastic rigid body. It has a coordinate and other physical attributes, such as mass, motion, and friction coefficient.
  • cpShape-It is an abstract geometric shape used to detect collisions. You can add a polygon to the struct andcpShapeThere are various subclass types to represent different shapes.

In this example, we model a wooden box and then fall under the influence of gravity. CreateCrateClass, including the visual effect on the screen (UIImageView) And a physical model (cpBodyAnd onecpPolyShape, OnecpShapeTo represent the rectangular wooden box ).

Using Chipmunk in C brings some challenges because it does not currently support the reference counting model of Objective-C, so we need to create and release objects accurately. To simplify the processcpShapeAndcpBodyLifecycle andCrateClass, and then in the wooden box-initCreated in-dealloc. The configuration of the physical properties of the wooden box is very complicated, so reading the Chipmunk document will make sense.

View Controller for managementcpSpaceAnd the same timer logic as before. In each step, we updatecpSpace(Used for physical computing and replacement of all struct) Then iterate the object, and then update the location of our wooden box view to match the wooden box model (here, there is actually only one struct, but we will add more ).

The Chipmunk uses a coordinate system that is reversed with the UIKit (the Y axis is in the upward and forward directions ). To make the synchronization between physical models and views easier, we need to usegeometryFlippedThe Set coordinates of the property flip container view (as mentioned in chapter 3rd), so both the model and view share the same coordinate system.

For specific code, see list 11.3. Note that we have not released it anywherecpSpaceObject. In this example, the memory space will always exist throughout the app lifecycle, so there is no problem. But in real-world scenarios, we need to manage our space like creating wooden boxes and shapes, encapsulate them in standard Cocoa objects, and then manage the lifecycle of Chipmunk objects. Figure 11.1 shows the dropped wooden case.

Listing 11.3 uses physics to model dropped wooden boxes

  1 #import "ViewController.h"   2 #import   3 #import "chipmunk.h"  4   5 @interface Crate : UIImageView  6   7 @property (nonatomic, assign) cpBody *body;  8 @property (nonatomic, assign) cpShape *shape;  9  10 @end 11  12 @implementation Crate 13  14 #define MASS 100 15  16 - (id)initWithFrame:(CGRect)frame 17 { 18     if ((self = [super initWithFrame:frame])) { 19         //set image 20         self.image = [UIImage imageNamed:@"Crate.png"]; 21         self.contentMode = UIViewContentModeScaleAspectFill; 22         //create the body 23         self.body = cpBodyNew(MASS, cpMomentForBox(MASS, frame.size.width, frame.size.height)); 24         //create the shape 25         cpVect corners[] = { 26             cpv(0, 0), 27             cpv(0, frame.size.height), 28             cpv(frame.size.width, frame.size.height), 29             cpv(frame.size.width, 0), 30         }; 31         self.shape = cpPolyShapeNew(self.body, 4, corners, cpv(-frame.size.width/2, -frame.size.height/2)); 32         //set shape friction & elasticity 33         cpShapeSetFriction(self.shape, 0.5); 34         cpShapeSetElasticity(self.shape, 0.8); 35         //link the crate to the shape 36         //so we can refer to crate from callback later on 37         self.shape->data = (__bridge void *)self; 38         //set the body position to match view 39         cpBodySetPos(self.body, cpv(frame.origin.x + frame.size.width/2, 300 - frame.origin.y - frame.size.height/2)); 40     } 41     return self; 42 } 43  44 - (void)dealloc 45 { 46     //release shape and body 47     cpShapeFree(_shape); 48     cpBodyFree(_body); 49 } 50  51 @end 52  53 @interface ViewController () 54  55 @property (nonatomic, weak) IBOutlet UIView *containerView; 56 @property (nonatomic, assign) cpSpace *space; 57 @property (nonatomic, strong) CADisplayLink *timer; 58 @property (nonatomic, assign) CFTimeInterval lastStep; 59  60 @end 61  62 @implementation ViewController 63  64 #define GRAVITY 1000 65  66 - (void)viewDidLoad 67 { 68     //invert view coordinate system to match physics 69     self.containerView.layer.geometryFlipped = YES; 70     //set up physics space 71     self.space = cpSpaceNew(); 72     cpSpaceSetGravity(self.space, cpv(0, -GRAVITY)); 73     //add a crate 74     Crate *crate = [[Crate alloc] initWithFrame:CGRectMake(100, 0, 100, 100)]; 75     [self.containerView addSubview:crate]; 76     cpSpaceAddBody(self.space, crate.body); 77     cpSpaceAddShape(self.space, crate.shape); 78     //start the timer 79     self.lastStep = CACurrentMediaTime(); 80     self.timer = [CADisplayLink displayLinkWithTarget:self 81                                              selector:@selector(step:)]; 82     [self.timer addToRunLoop:[NSRunLoop mainRunLoop] 83                      forMode:NSDefaultRunLoopMode]; 84 } 85  86 void updateShape(cpShape *shape, void *unused) 87 { 88     //get the crate object associated with the shape 89     Crate *crate = (__bridge Crate *)shape->data; 90     //update crate view position and angle to match physics shape 91     cpBody *body = shape->body; 92     crate.center = cpBodyGetPos(body); 93     crate.transform = CGAffineTransformMakeRotation(cpBodyGetAngle(body)); 94 } 95  96 - (void)step:(CADisplayLink *)timer 97 { 98     //calculate step duration 99     CFTimeInterval thisStep = CACurrentMediaTime();100     CFTimeInterval stepDuration = thisStep - self.lastStep;101     self.lastStep = thisStep;102     //update physics103     cpSpaceStep(self.space, stepDuration);104     //update all the shapes105     cpSpaceEachShape(self.space, &updateShape, NULL);106 }107 108 @end
View Code

Figure 11.1 a wooden box image, falling by the simulated gravity

Add User Interaction

The next step is to add an invisible wall around the view so that the wooden box will not drop out of the screen. Maybe you will use another rectanglecpPolyShapeBut we need to check when the wooden box leaves the view, rather than when the view is collided, so we need a hollow rectangle instead of a solid rectangle.

We cancpSpaceAdd fourcpSegmentShapeObject (cpSegmentShapeRepresents a straight line, so the four are a rectangle ). ThestaticBodyAttribute (a struct not affected by gravity) instead of a new one like a wooden boxcpBodyInstance, because we do not want this border rectangle to slide out of the screen or be hit by a falling wooden box.

You can also add some wooden boxes for interaction. Add an accelerator so that you can adjust the Gravity Vector by tilting the phone (to test, you need to run the program on a real device because the simulator does not support accelerator events, even if you rotate the screen ). Listing 11.4 shows the updated code. The running result is shown in Figure 11.2.

Because the example only supports the Landscape mode, the x and y values of the accelerometer vector are exchanged. If you run the program in the portrait screen, please switch them back, otherwise the gravity direction will be lost. After a try, the wooden box will move along the horizontal direction.

Listing 11.4 updated code using walls and multiple wooden boxes

 1 - (void)addCrateWithFrame:(CGRect)frame 2 { 3     Crate *crate = [[Crate alloc] initWithFrame:frame]; 4     [self.containerView addSubview:crate]; 5     cpSpaceAddBody(self.space, crate.body); 6     cpSpaceAddShape(self.space, crate.shape); 7 } 8  9 - (void)addWallShapeWithStart:(cpVect)start end:(cpVect)end10 {11     cpShape *wall = cpSegmentShapeNew(self.space->staticBody, start, end, 1);12     cpShapeSetCollisionType(wall, 2);13     cpShapeSetFriction(wall, 0.5);14     cpShapeSetElasticity(wall, 0.8);15     cpSpaceAddStaticShape(self.space, wall);16 }17 18 - (void)viewDidLoad19 {20     //invert view coordinate system to match physics21     self.containerView.layer.geometryFlipped = YES;22     //set up physics space23     self.space = cpSpaceNew();24     cpSpaceSetGravity(self.space, cpv(0, -GRAVITY));25     //add wall around edge of view26     [self addWallShapeWithStart:cpv(0, 0) end:cpv(300, 0)];27     [self addWallShapeWithStart:cpv(300, 0) end:cpv(300, 300)];28     [self addWallShapeWithStart:cpv(300, 300) end:cpv(0, 300)];29     [self addWallShapeWithStart:cpv(0, 300) end:cpv(0, 0)];30     //add a crates31     [self addCrateWithFrame:CGRectMake(0, 0, 32, 32)];32     [self addCrateWithFrame:CGRectMake(32, 0, 32, 32)];33     [self addCrateWithFrame:CGRectMake(64, 0, 64, 64)];34     [self addCrateWithFrame:CGRectMake(128, 0, 32, 32)];35     [self addCrateWithFrame:CGRectMake(0, 32, 64, 64)];36     //start the timer37     self.lastStep = CACurrentMediaTime();38     self.timer = [CADisplayLink displayLinkWithTarget:self39                                              selector:@selector(step:)];40     [self.timer addToRunLoop:[NSRunLoop mainRunLoop]41                      forMode:NSDefaultRunLoopMode];42     //update gravity using accelerometer43     [UIAccelerometer sharedAccelerometer].delegate = self;44     [UIAccelerometer sharedAccelerometer].updateInterval = 1/60.0;45 }46 47 - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration48 {49     //update gravity50     cpSpaceSetGravity(self.space, cpv(acceleration.y * GRAVITY, -acceleration.x * GRAVITY));51 }
View Code

Figure 11.1 wooden box interaction under the real gravitational field

Simulation time and fixed time step

Computing the duration of each frame is a good solution for implementing the animation buffering effect, but it is not ideal for simulating the physical effect. A variable time step has two drawbacks:

  • If the time step is not fixed and the exact value, the simulation of the physical effect will be uncertain. This means that even passing in the same input value may have different effects in different scenarios. Sometimes it does not have much impact, but in a physical engine-based game, players will be confused by the same operation behavior resulting in different results. It also makes the test troublesome.

  • Frame loss or call-in interruption may result in incorrect results due to performance. Consider moving an object as quickly as a bullet. Every frame update requires moving a bullet to detect collision. If the time between the two frames is longer, the bullet will move further in this step, passing through the wall or other obstacles, thus losing the collision.

The ideal result we want is to use a fixed time step to calculate the physical effect, however, the view can still be updated synchronously when the screen is re-painted (this may cause unpredictable effects beyond our control ).

Fortunately, because of our model (in this example, It is Chipmunk'scpSpaceIncpBodyView (the wooden box on the screen)UIViewObject), so it is very simple. We only need to track the time step based on the screen refresh time, and then calculate one or more simulated results based on each frame.

We can achieve this through a simple loop. Each timeCADisplayLinkTo notify the screen to be refreshed, and then record the currentCACurrentMediaTime(). We need to repeat the physical simulation in advance in a small increment (Here we use one second from 120) until we catch up with the display time. Then, update our view to match the display position of the current physical structure when the screen is refreshed.

Listing 11.5 shows the fixed-time step version code

Listing 11.5 wooden case simulation with fixed time step

 

Avoid death spiral

When using a fixed simulated time step, you must note that the real-world time used to calculate the physical effect does not accelerate the simulated time step. In our example, we randomly select one second to simulate the physical effect. Chipmunk is very fast, and our example is also very simple, socpSpaceStep()It will be done well and will not delay frame update.

However, if the scenario is complex, for example, if there are hundreds of objects interacting with each other, physical computing will be complex,cpSpaceStep()May also exceed 1/120 seconds. We didn't measure the time of the physical step, because we assume that the frame refresh is not important, but if the simulation step is longer, the frame rate will be delayed.

If the frame refresh time delay is poor, our simulation needs to execute more times to synchronize the real time. These additional steps will continue to delay frame update, and so on. This is the so-called death spiral, because the final result is that the frame rate is getting slower and slower until the last application gets stuck.

We can add some code to calculate the real-world time for physical steps on the device, and then adjust the fixed time step automatically, but it is actually not feasible. In fact, you only need to ensure that you leave enough edge length for fault tolerance, and then test on the slowest device that you expect to support. If physical computing exceeds 50% of the simulation time, you need to increase the simulation time step (or simplify the scenario ). If the simulation time step is increased to more than 1/60 seconds (a complete screen update time), you need to reduce the animation frame rate to 30 frames per second or increaseCADisplayLinkOfframeIntervalTo ensure that frames are not randomly lost, or your animation will not look smooth.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.