Fluxus

Deforming1000000000000400000003005071A1A6.png

Deformation in this chapter signifies various operations. It can involve changing the shape of a primitive in a way not possible via a transform (i.e. bending, warping etc) or modifying texture coordinates or colours to achieve a per-vertex effect. Deformation in this way is also the only way to get particle primitives to do anything interesting.

Deforming is all about pdata, so, to deform an entire object, you do something like the following:

(clear)
(hint-none)
(hint-wire)
(line-width 4)

(define myobj (build-sphere 10 10))

(with-primitive myobj
    (pdata-map!
        (lambda (p)
            ; add a small random vector to the original point
            (vadd (vmul (rndvec) 0.1) p))
        "p"))

When deforming geometry, moving the positions of the vertices is not usually enough, the normals will need to be updated for the lighting to work correctly.

(recalc-normals smooth)

Will regenerate the normals for polygon and nurbs primitives based on the vertex positions. Not particularly fast (it is better to deform the normals in your script if you can). If smooth is 1, the face normals are averaged with the coincident face normals to give a smooth appearance.

When working on polygon primitives fluxus will cache certain results, so it will be a lot slower on the first calculation than subsequent calls on the same primitive.

User Pdata 1000000000000125000001344BBA82FF.jpg

As well as the standard information that exists in primitives, fluxus also allows you to add your own per vertex data to any primitive. User pdata can be written or read in the same way as the built in pdata types.

(pdata-add name type)

Where name is a string with the name you wish to call it, and type is a one character string consisting of:

f : float data

v : vector data

c : colour data

m : matrix data

(pdata-copy source destination)

This will copy a array of pdata, or overwrite an existing one with if it already exists. Adding your own storage for data on primitives means you can use it as a fast way of reading and writing data, even if the data doesn’t directly affect the primitive.

An example of a particle explosion:

; setup the scene
(clear)
(show-fps 1)
(point-width 4)
(hint-anti-alias)

; build our particle primitive
(define particles (build-particles 1000))

; set up the particles
(with-primitive particles
    (pdata-add "vel" "v") ; add the velocity user pdata of type vector
    (pdata-map! ; init the velocities
        (lambda (vel)
            (vmul (vsub (vector (flxrnd) (flxrnd) (flxrnd))
                        (vector 0.5 0.5 0.5)) 0.1))
        "vel")
    (pdata-map! ; init the colours
        (lambda (c)
            (vector (flxrnd) (flxrnd) 1))
        "c"))

(blur 0.1)

; a procedure to animate the particles
(define (animate)
    (with-primitive particles
        (pdata-map!
            (lambda (vel)
                (vadd vel (vector 0 -0.001 0)))
            "vel")
        (pdata-map! vadd "p" "vel")))

(every-frame (animate))

Pdata Operations

Pdata Operations are a optimisation which takes advantage of the nature of these storage arrays to allow you to process them with a single call to the scheme interpreter. This makes deforming primitive much faster as looping in the scheme interpreter is slow, and it also simplifies your scheme code.

(pdata-op operation pdata operand)

Where operation is a string identifier for the intended operation (listed below) and pdata is the name of the target pdata to operate on, and operand is either a single data (a scheme number or vector (length 3,4 or 16)) or a name of another pdata array.

If the (update) and (render) functions in the script above are changed to the following:

(define (update)
    ; add this vector to all the velocities
    (pdata-op "+" "vel" (vector 0 -0.002 0))
    ; add all the velocities to all the positions
    (pdata-op "+" "p" "vel"))

(define (render)
    (with-primitive ob
        (update)))

On my machine, this script runs over 6 times faster than the first version.
(pdata-op) can also return information to your script from certain functions called on entire pdata arrays.

Pdata operations

+” : addition

*” : multiplication

sin” : writes the sine of one float pdata array into another “cos” : writes the cosine of one float pdata array into another “closest” : treats the vector pdata as positions, and if given a single vector, returns the closest position to it – or if given a float, uses it as a index into the pdata array, and returns the nearest position.

For most pdata operations, the vast majority of the combinations of input types (scheme number, the vectors or pdata types) will not be supported, you will receive a rather cryptic runtime warning message if this is the case.

Pdata functions

Pdata ops are useful, but I needed to expand the idea into something more complicated to support more interesting deformations like skinning. This area is messy, and somewhat experimental – so bear with me, it should solidify in future.

Pdata functions (pfuncs) range from general purpose to complex and specialised operations which you can run on primitives. All pfuncs share the same interface for controlling and setting them up. The idea is that you make a set of them at startup, then run them on one or many primitives later on per-frame.

(make-pfunc pfunc-name-symbol))

Makes a new pfunc. Takes the symbol of the names below, e.g. (make-pfunc ‘arithmetic)

(pfunc-set! pfuncid-number argument-list)

Sets arguments on a primitive function. The argument list consists of symbols and corresponding values.

(pfunc-run id-number)

Runs a primitive function on the current primitive. Look at the skinning example to see how this works.

Pfunc types

All pfunc types and arguments are as follows:

arithmetic

For applying general arithmetic to any pdata array


operator string : one of: add sub mul div

src string : pdata array name

other string : pdata array name (optional)

constant float : constant value (optional)

dst string : pdata array name

genskinweights

Generates skinweights – adds float pdata called “s1” -> “sn” where n is the number of nodes in the skeleton – 1

skeleton-root primid-number : the root of the bindpose skeleton for skinning sharpness float : a control of how sharp the creasing will be when skinned


skinweights->vertcols

A utility for visualising skinweights for debugging.

No arguments

skinning

Skins a primitive – deforms it to follow a skeleton’s movements. Primitives we want to run this on have to contain extra pdata – copies of the starting vert positions called “pref” and the same for normals, if normals are being skinned, called “nref”.

Skeleton-root primid-number : the root primitive of the animating skeleton bindpose-root primid-number : the root primitive of the bindpose skeleton skin-normals number : whether to skin the normals as well as the positions

Using Pdata to build your own primitives

The function (build_polygons) allows you to build empty primitives which you can use to either build more types of procedural shapes than fluxus supports naively, or for loading model data from disk. Once these primitives have been constructed they can be treated in exactly the same way as any other primitive, ie pdata can be added or modified, and you can use (recalc-normals) etc.