Animation and physics

Czech technical University in Prague
Faculty of Information Technology
Department of Software Engineering
© Adam Vesecký, MI-APH, 2019

Math and Dynamics

JS Math functions

  • Math.pow(x,y) - returns the value of x to the power of y
  • Math.sqrt(x) - returns the square root of x
  • Math.ceil(x) - returns the value of x rounded up to its nearest integer
  • Math.floor(x) - returns the value of x rounded down to its nearest integer
  • Math.trunc(x) - returns the integer portion of a number
  • ~a - inverts the bits of its operand
  • a << b - shifts a in b bits to the left
  • a >> b - shifts a in b bits to the right
  • a >>> b - shifts a in b bits to the right, shifting in 0s from the left
  • Math.atan(x) - returns the arctangent (in radians) of a number
  • Math.atan2(y, x) - returns the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (x, y)
  • Math.random()- generates a random number in range (0, 1)
  • min + Math.floor((max - min + 1) * Math.random())- generates a random integer in range [min, max] exclusively
  • Math.random() > (1 - probability) - checks for an occurrence of an event at given probability
  • for each bitwise operations, the operators are converted into 32-bit integers

Math functions

  • if we work with positive numbers, ~~v is a common choice to get the integer part
  • floor, ceil and round are conceptually different from trunc
truncfloorceilround~~vv | 0v << 0v >> 0v >>> 0
3.83344 33333
3.2334333333
-3.2-3-4-3-3-3-3-3-34294967293
-3.8-3-4-3-4-3-3-3-34294967293

Math support library

  • located in libs/pixi-math
  • contains random generator, steering behavior basic algorithms, quad-tree and matterJS-to-pixi binding components
  • new Random().normal(min, max, scale) generates a random number from normal distribution

Uniform distribution

Somehow normal distribution

Example: Random spread

  • src/labs/lab04/example-distribution-spread.ts
  • uses Box-Muller Transform and Skew-normal transform to generate normal distribution from the uniform distribution

Uniform distribution

Somehow normal distribution

Euler integration

  • Explicit method

    • adds energy to the system

    Improved method

    Implicit method

    • decreases energy from the system

Euler integration

  • src/labs/lab04/example-dynamics-balistic.ts
  • explicit
  • improved
  • implicit

60 FPS

20 FPS

5 FPS

Euler integration

  • src/labs/lab04/example-dynamics-circle.ts
  • explicit
  • improved
  • implicit

60 FPS

20 FPS

5 FPS

Relaxation

  • src/labs/lab04/example-verlet.ts
  • simple relaxation technique
  • gravity tries to move all particles downward
  • PinConstraint holds the topmost particle steady
  • DistanceConstraint tries to keep a constant distance between two particles

Sprite animations

Pixi animations

  • detailed tutorial
  • needs a spritesheet and metadata (e.g. from OpenGameArt)
  • metadata contains all sprites, offsets, orders and other information needed to compose animations
  • sprites can be exported to pixi format via TexturePacker

    ...

    "warrior07": {

      "frame": {

        "x"1584"y"540,

        "w"528"h"540

      }

    }

  },

  "animations": {

    "warrior": ["warrior01","warrior02","warrior03",

      "warrior04","warrior05","warrior06","warrior07"]

  },

  "meta": {

    "image""warrior.png""format""RGBA8888",

    "size": {"w":2033,"h":2030}, "scale""1"

  }

}

Pixi animations

  • Attributes
    • onFrameChange - function to call when an AnimatedSprite changes which texture is being rendered
    • play(): void; - plays the animated sprite
    • gotoAndPlay(frameNumber: number): void;- goes to a specific frame and begins playing the AnimatedSprite.
    • gotoAndStop(frameNumber: number): void; - stops the AnimatedSprite and goes to a specific frame.

      let sheet = this.engine.app.loader.resources['./assets/lab02/warrior.json'].spritesheet;

      let animation = new PIXI.AnimatedSprite(sheet.animations['warrior']);

      animation.animationSpeed = 0.167;

      animation.loop = true;

      animation.play();

Import

  • if metadata is available, we can use TexturePacker to export it into PIXI format
  • if metadata isn't available, we need to specify it manually
    • we can use Shoebox to extract sprites from spritesheets
    • pivots need to be specified manually

Json structure

{

  "frames": {

    "background.png":

    {

      "frame": {"x":1,"y":1,"w":1920,"h":1080},

      "rotated"false,

      "spriteSourceSize": {"x":0,"y":0,"w":1920,"h":1080},

      "sourceSize": {"w":1920,"h":1080},

      "anchor": {"x":0,"y":0}

    },

    "frame01.png":

    {

      "frame": {"x":1106,"y":1379,"w":158,"h":316},

      "rotated"true,

      "trimmed"true,

      "spriteSourceSize": {"x":15,"y":3,"w":158,"h":316},

      "sourceSize": {"w":187,"h":324},

      "anchor": {"x":0.5,"y":1}

    }

  },

  "animations": {

    "anim01": ["frame01","frame02.png","frame03.png"],

    "anim02": ["frame65","frame66","frame67"]

  },

  "meta": {

    "image""spritesheet.png",

    "format""RGBA8888",

    "size": {"w":2033,"h":2030},

  }

}

  • frames - all frames inside the spritesheet
  • animations - frame sequences grouped into named animations
  • meta - link to the spritesheet file, color format and its size

Manual handling

  • src/labs/lab04/example-sprite-custom.ts
  • for simple animations, we can define a custom format
  • we only need to modify frame variable in BaseTexture

let texture = this.engine.app.loader.resources[SPRITESHEET].texture;

let frames = 5, frameCounter = 0;

 

new ECSA.Builder(this.engine.scene)

  .asSprite(texture)

  .withParent(this.engine.scene.stage)

  .anchor(0.5)

  .withComponent(new ECSA.GenericComponent('animator')

    .setFrequency(10)

    .doOnUpdate((cmpdeltaabsolute=> {

      cmp.owner.asSprite().texture.frame = new PIXI.Rectangle(

        (texture.width / frames) * (frameCounter++ % frames), 

        0, texture.width / frames, texture.height);

    }))

  .build();

Example: Pacman

  • LINK
  • source code: src/games/pacman
  • 512x512 spritesheet

Skeletal 2D animations

Skeletal animations

  • characters are represented as a hierarchical set of interconnected bones
  • instead of animating the character, we animate the bones
  • for 2D animations, the texture is warped along the modified skeleton
  • two major applications for 2D animations:

Dragonbones

  • load some of the built-in examples

Dragonbones

  • all animations can be easily modified in the timeline

Dragonbones

  • export as DragonBones JSON format 5.5
  • the export will create two JSON structures and one spritesheet

Dragonbones

  • src/labs/lab04/example-dragonbones.ts
  • Dragonbones-Pixi library is in libs/dragonbones
  • parsing the data takes a lot of time, it should be handled asynchronously

const factory = PixiFactory.factory;

factory.parseDragonBonesData(this.engine.app.loader.resources['Demon_ske.json'].data);

factory.parseTextureAtlasData(this.engine.app.loader.resources['Demon_tex.json'].data,

  this.engine.app.loader.resources['Demon_tex.png'].texture);

 

const armatureDisplay = factory.buildArmatureDisplay('Demon''Demon');

armatureDisplay.animation.play('normalAttack'1);

Steering behaviors

Steering behaviors

  • set of algorithms and principles that help autonomous agents move in a realistic manner
    by using simple forces

Seek

  • src/labs/lab04/example-steering-seek.ts
  • the agent goes toward a target

Pursuit

  • src/labs/lab04/example-steering-pursuit.ts

Pursuit + Evade

  • src/labs/lab04/example-steering-evade.ts

Wander

  • src/labs/lab04/example-steering-wander.ts
  • 3 attributes: radius, distance and jittering

Multiple behaviors

  • src/labs/lab04/example-steering-multiple.ts
  • the algorithms can pile up, calculating a weight-sum for each force

QuadTree

QuadTree

  • src/labs/lab04/example-quadtree.ts
  • each inner node has 4 children
  • overlapping objects are put into all children they touch
  • only objects in the same leaf can be in collision

QuadTree

20 objects, 5 for each cell

50 objects, 10 for each cell

100 objects, 2 for each cell

MatterJS

MatterJS

  • https://brm.io/matter-js/
  • 2D physics engine
  • rigid bodies
  • compound bodies
  • composite bodies
  • concave and convex hulls
  • restitution
  • momentum
  • friction
  • events
  • constraints
  • gravity
  • sleeping and static bodies

Architecture

  • Body - contains methods for creating and manipulating body models
  • IBodyDefinition - a physical object, contains all necessary attributes
  • all manipulations are handled by static methods in Body class

Architecture

  • ICompositeDefinition - contains composition of bodies
  • Composite - contains methods to manipulate with composite objects, consisting of many bodies

Architecture

  • IPair - contains attributes for a colliding pair of two bodies
  • IConstraintDefinition - contains attributes for a constraint that connects bodies together in order to simulate interaction
  • Events:
    • sleepStart, sleepEnd, beforeAdd, afterAdd, beforeRemove, afterRemove, afterUpdate, beforeRender, afterRender, beforeUpdate, collisionActive, collisionEnd, collisionStart, beforeTick, tick, afterTick, beforeRender, afterRender, mousedown, mousemove, mouseup

Architecture

  • Bodies - static methods for creating of new simple bodies
  • Composites - static methods for creating of complex objects
    • car - creates a composite with simple car setup of bodies and constraints
    • chain - chains all bodies in the given composite together using constraints
    • mesh - connects bodies in the composite with constraints in a grid pattern
    • softBody - creates a simple soft body like object
  • Bounds static methods for defining outer bounds of the scene

Simple setup

let engine = Matter.Engine.create();

let world = engine.world;

let render = Matter.Render.create({

          element: document.body,

          engine: engine });

Render.run(render);

 

// insert objects

Matter.World.add(world, Matter.Composites.car(1501001503030));

 

let runner = Matter.Runner.create();

Matter.Runner.run(runner, engine);

Matter.Render.lookAt(render, {

  min: { x: 0, y: 0 },

  max: { x: 800, y: 600 }

});

Matter and PIXI

  • MatterJS contains its own renderer
  • in order to render objects in PIXI, we need to create a copy of every MatterJS body and synchronize it
  • PIXI renders PIXI.Graphics objects and ECSA library synchronizes their state with MatterJS
  • the update can be handled by MatterJS.Runner or manually by invoking Matter.Runner.tick

Example: Air friction

  • src/labs/lab04/example-matter-airfriction.ts

Example: Bridge

  • src/labs/lab04/example-matter-bridge.ts

Example: Car

  • src/labs/lab04/example-matter-car.ts

Example: Chains

  • src/labs/lab04/example-matter-chains.ts

Example: Friction

  • src/labs/lab04/example-matter-friction.ts

Example: Mixed Shapes

  • src/labs/lab04/example-matter-mixed.shapes.ts

Example: Newton's cradle

  • src/labs/lab04/example-matter-newton-cradle.ts