3D rendering
Canvas offers basic 3D capabilities through the use of AffineTransform
s and Camera
s. This enables View
s to display content in a shared 3D space.
The following examples has a View that draws a 3D cube. The cube looks like a 3D object even though it is flat on the View's surface. The controls (which are overlaid on the View) let you change the starting transform applied to the cube's back face (which changes the cube's overall transform), the folding angle of each face, and the camera used to create the perspective.
- Demo
- Cube.kt
package io.nacular.doodle.docs.utils
import io.nacular.doodle.core.Camera
import io.nacular.doodle.core.View
import io.nacular.doodle.core.renderProperty
import io.nacular.doodle.drawing.AffineTransform
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.Color
import io.nacular.doodle.drawing.Stroke
import io.nacular.doodle.drawing.paint
import io.nacular.doodle.geometry.Point
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.utils.ChangeObservers
import io.nacular.doodle.utils.ChangeObserversImpl
import io.nacular.measured.units.Angle
import io.nacular.measured.units.times
//sampleStart
class Cube: View() {
private val side = 100.0
private val rect = Rectangle(size = Size(side))
private val stroke = Stroke(thickness = 2.0, fill = Color.Darkgray.paint)
var foldAngle by renderProperty(-90 * Angle.degrees) { _,_ -> (changed as ChangeObserversImpl).invoke() }
var canvasCamera by renderProperty(Camera(Point.Origin, 1000.0)) { _,_ -> (changed as ChangeObserversImpl).invoke() }
var canvasTransform by renderProperty(AffineTransform.Identity.translate(z = -side / 2)) { _,_ -> (changed as ChangeObserversImpl).invoke() }
val changed: ChangeObservers<Cube> = ChangeObserversImpl(this)
override fun render(canvas: Canvas) {
var transform = canvasTransform
var faceLocation = Point((width - side) / 2, (height - side) / 2)
drawFace(canvas, faceLocation, transform) // Back
faceLocation += Point(x = side)
transform *= AffineTransform.Identity.rotateY(around = faceLocation, foldAngle)
drawFace(canvas, faceLocation, transform) // Right
faceLocation -= Point(y = side)
drawFace(canvas, faceLocation, transform.rotateX(around = faceLocation + Point(y = side), foldAngle)) // Top
faceLocation += Point(y = 2 * side)
transform *= AffineTransform.Identity.rotateX(around = faceLocation, -foldAngle)
drawFace(canvas, faceLocation, transform) // Bottom
faceLocation += Point(y = side)
transform *= AffineTransform.Identity.rotateX(around = faceLocation, -foldAngle)
drawFace(canvas, faceLocation, transform) // Left
faceLocation += Point(x = side)
transform *= AffineTransform.Identity.rotateY(around = faceLocation, foldAngle)
drawFace(canvas, faceLocation, transform) // Front
}
private fun drawFace(canvas: Canvas, location: Point, transform: AffineTransform) {
canvas.transform(transform, canvasCamera) { rect(rect.at(location), stroke) }
}
}
//sampleEnd