Transformations

This section covers the topic of placing items on the screen.

Basic transformation use

Translation

Translation moves points in space with an offset.

In the following example we use Drawer.translate to move a single rectangle over the screen.

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            // set up translation
            drawer.translate(seconds * 100.0, height / 2.0)
            drawer.rectangle(-50.0, -50.0, 100.0, 100.0)
        }
    }
}

Link to the full example

translations (and transformations in general) can be stacked on top of each-other. For example we can express a horizontal and a vertical motion as two separate translations

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            // move the object to the vertical center of the screen
            drawer.translate(0.0, height / 2.0)
            // set up horizontal translation
            drawer.translate(seconds * 100.0, 0.0)
            // set up vertical translation
            drawer.translate(0.0, cos(seconds * Math.PI * 2.0) * 50.00)
            
            drawer.rectangle(-50.0, -50.0, 100.0, 100.00)
        }
    }
}

Link to the full example

Rotations

Rotating transformations are performed using Drawer.rotate(). The rotation is applied by rotating points around the origin of the coordinate system: (0, 0), which lies in the top-left corner of the window.

In the first rotation example we rotate a rectangle that is placed around the origin but later translated to the center of the screen. Here we notice something that may be counter-intuitive at first: the transformations are easiest read from bottom to top: first rotate is applied and only then translate.

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            
            // -- translate
            drawer.translate(width / 2.0, height / 2.0)
            // -- rotate
            drawer.rotate(seconds * 30.0)
            // -- rectangle around the origin
            drawer.rectangle(-50.0, -50.0, 100.0, 100.0)
        }
    }
}

Link to the full example

Scaling

Scaling transformations are performed using Drawer.scale(). Also scaling is applied around the origin of the coordinate system: (0, 0).

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            
            // -- translate to center of screen
            drawer.translate(width / 2.0, height / 2.0)
            // -- scale around origin
            drawer.scale(cos(seconds) + 2.0)
            // -- rectangle around the origin
            drawer.rectangle(-50.0, -50.0, 100.0, 100.00)
        }
    }
}

Link to the full example

Combining transformations

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            
            // -- translate to center of screen
            drawer.translate(width / 2.0, height / 2.0)
            
            drawer.rotate(20.00 + seconds * 60.0)
            // -- rectangle around the origin
            drawer.rectangle(-50.0, -50.0, 100.0, 100.0)
            
            // -- draw a second rectangle, sharing the rotation of the first rectangle but with an offset
            drawer.translate(150.0, 0.0)
            drawer.rectangle(-50.0, -50.0, 100.0, 100.0)
        }
    }
}

Link to the full example

fun main() = application {
    configure {
        width = 770
        height = 578
    }
    program {
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            
            // -- translate to center of screen
            drawer.translate(width / 2.0, height / 2.0)
            
            drawer.rotate(seconds * 60.0)
            // -- rectangle around the origin
            drawer.rectangle(-50.0, -50.0, 100.0, 100.0)
            
            // -- draw a second rectangle, sharing the rotation of the first rectangle but with an offset
            drawer.translate(150.0, 0.0)
            drawer.rotate(seconds * 15.0)
            drawer.rectangle(-50.0, -50.0, 100.0, 100.00)
        }
    }
}

Link to the full example

Transform pipeline

OPENRNDR’s Drawer is build around model-view-projection transform pipeline. That means that three different transformations are applied to determine the screen position.

matrix property pipeline stage
model model transform
view view transform
projection projection transform

Which matrices are affected by which Drawer operations?

operation matrix property
fun rotate(…) model
fun translate(…) model
fun scale(…) model
fun lookAt(…) view
fun ortho(…) projection
fun perspective(…) projection

Projection matrix

The default projection transformation is set to an orthographic projection using ortho(). The origin is in the upper-left corner; positive y points down, positive x points right on the screen.

Perspective projections

override fun draw() {
    drawer.perspective(90.0, width*1.0 / height, 0.1, 100.0)
}

Transforms

In OPENRNDR transforms are represented by Matrix44 instances.

OPENRNDR offers tools to construct Matrix44

Transform builder

Relevant APIs

Matrix44
transform {}

In the snippet below a Matrix44 instance is constructed using the transform {} builder. Note that the application order is from bottom to top.

drawer.model *= transform {
    rotate(32.0)
    rotate(Vector3(1.0, 1.0, 0.0).normalized, 43.0)
    translate(4.0, 2.0)
    scale(2.0)
}

This is equivalent to the following:

drawer.rotate(32.0)
drawer.rotate(Vector3(1.0, 1.0, 0.0).normalized, 43.0)
drawer.translate(4.0, 2.0)
drawer.scale(2.0)

Applying transforms to vectors

    val x = Vector4(1.0, 2.0, 3.0, 1.0)
    val m = transform {
        rotate(Vector3.UNIT_Y, 42.0)
    }
    val transformed = m * x
    val transformedTwice = m * m * x

edit on GitHub