Log In  

Cart #isidore-0 | 2021-04-05 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

Cart #roxana-0 | 2021-04-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
3

Experiments in variable fps animation to simulate drawing / handwriting.

Drawing instructions are encoded and stored as a string in the lua code. Two different encodings ISIDORE and ROXANA were implemented, each with corresponding function used to decode and execute the drawing instructions.

TIP: toggle performance monitor using Ctrl+P to view fps

Motivation

When using animation to simulate something being hand-drawn or handwritten, ability to vary the speed of the "pen stroke" can help make the animation look more realistic and convincing, e.g. faster for straight lines, and slower for tight turns and loops.

How?

With this undocumented function:

_set_fps(rate)

The wiki seems to suggest that 15, 30, or 60 are the only acceptable values, but with a bit of experimentation I found that arbitrary values do work.

There are a few caveats:

  • custom main loop required
  • seems limited to maximum of 60 fps or thereabouts
  • tailoring the fps to whatever is being animated = generally only animating one thing at a time
  • undocumented feature, so no official support or guarantee it will continue to work

Preliminaries

Before doing anything fancy, it is important to note that on a raster display, animating a line or curve as it is extended pixel-by-pixel will cause the perceived speed of drawing to vary depending on direction. We will need to compensate for this when setting the speed at which a "pen stroke" is animated.

(If you understand why, feel free to skip ahead. Otherwise, open hidden block for explanation.)


A 60px horizontal line takes 1 second to animate at 60fps, and appears to be growing at a rate of 60px/second. A 45° diagonal line made up of 60 pixels also animates in the same time, but it will appear to be drawn at ~40% faster speed due to its length of ~60×sqrt(2).

When drawing curves rather than straight lines this phenomenon can cause the perception of variable speed over the course of the animation. Here is a circle outline that is noticeably slower at the top, bottom and sides. It takes about 4×d/sqrt(2)≈2.828×d frames to complete -- significantly faster than the theoretical circumference pi×d:

One approach to counter this while staying at constant fps would be to interpolate distance traveled by the "pen" over time. For instance, drawing a 45° diagonal line would add a pixel in only about 71% of frames (≈1/sqrt(2)). Applying this principle to draw the same circle outline as before produces a more realistic result:

A comparison with both overlaid shows the naive version clearly pulling ahead in the diagonals:

It is important to keep this phenomenon in mind when setting the speed at which a "pen stroke" is animated.

Bonus content for reading the explanation: circle drawn with variable fps


Encoding schemes

ISIDORE

The basic idea is to specify the predominant axis of motion and increment along it at 1px intervals, then provide the offset (along the other axis) where the pen/brush should be placed.

There are two variants:

  • Metavlitos: this was the original version which specified the brush size to use at every increment
  • Konstantinos: added later for more efficient encoding when brush size does not change often (=in most cases)

Details inside spoiler block:

Header

The first one or two characters initialize some settings:

0xFF-- initial speed in fps (1-256)
0x00FF initial brush diameter (1-256) <-- Konstantinos only

Increment

A byte <128 indicates data for the next increment along the current axis of drawing

0x80-- indicates a non-control token
0x7F-- offset (0-127)
0x00FF brush diameter (1-256) <-- Metavlitos only

Control codes

A byte >127 indicates upcoming data to be interpreted as a control code

0xF0    =0xF: invalid <-- deliberately not used
0x0F    unused

0xF000  =0xE: sub-header
0x0400  axis (x/y)
0x0200  direction: forward/reverse
0x0100  movement (0-1) -- allows remaining stationary
0x007F  skip ahead (0-127) increments
0x0880  unused bits

0xF000  =0xD: skip ahead
0x007F  skip ahead (0-127) increments
0x0F80  unused bits

0xF00000    =0xC: change speed
0x00FF00    target speed (1-256) fps -- max 60 in practice
0x0000FF    number of increments (1-256) to take to lerp toward target speed
0x0F0000    unused bits

0xF000  =0xB: change brush size <-- Konstantinos only
0x00FF  brush diameter (1-256)
0x0F00  unused bits

0xF000  =0xA: pause
0x00FF  number of frames (1-256) to advance without drawing (at current fps)
0x0F00  unused bits

0xF000  =0x9: no animate
0x00FF  number of increments (1-256) to draw without advancing frames
0x0F00  unused bits

0xF000  =0x8: repeat
0x007F  number of times (1-128) to repeat the previous increment data
0x0F80  unused bits

ROXANA

The basic idea is to specify the coordinates of a starting point, and subsequently move the pen/brush by small increments, each given as x- and y- offsets from the preceding position.

Like Isidore, Roxana has two variants:

  • Metavlitos: brush size specified at every increment
  • Konstantinos: changes in brush size requires control code

Details inside spoiler block:

Header

The first one or two characters initialize some settings (identical to Isidore):

0xFF-- initial speed in fps (1-256)
0x00FF initial brush diameter (1-256) <-- Konstantinos only

Increment

A byte <128 indicates data for the next increment, moving the pen/brush by an offset from its previous position.

0x80--  indicates a non-control token
0x70--  x-offset (-4-3)
0x07--  y-offset (-4-3)
0x00FF  brush diameter (1-256) <-- Metavlitos only
0x08--  unused bits

Control codes

A byte >127 indicates upcoming data to be interpreted as a control code

0xF0    =0xF: invalid <-- deliberately not used
0x0F    unused

0xF00000--  =0xE: sub-header (reposition)
0X010000--  should initial position be drawn? (false/true)
0x00FF00--  x-position (-64-191)
0x0000FF--  y-position (-64-191)
0x000000FF  brush diameter for initial position (1-256) <-- Metavlitos only
0x0E0000--  unused bits

0xF0    =0xD: invalid <-- "skip" in Isidore, irrelevant for Roxana

The remaining codes function identically as Isidore.
0xC: change speed
0xB: change brush size
0xA: pause
0x9: no animate
0x8: repeat


changelog

2021-04-09 - Roxana v1.0 initial version
2021-04-05 - Isidore v1.0 initial version

P#90023 2021-04-06 13:48 ( Edited 2021-04-09 14:30)

Cursive letter "A" <3

P#90101 2021-04-07 05:00

Added a second encoding scheme.

P#90274 2021-04-09 14:43

[Please log in to post a comment]

Follow Lexaloffle:        
Generated 2021-04-17 07:45 | 0.039s | 4194k | Q:44