Add Orbital

This commit is contained in:
Edwin Jakobs
2019-11-24 12:38:54 +01:00
parent ead9b8a0d3
commit 471b2d5bf7
4 changed files with 393 additions and 338 deletions

View File

@@ -1,51 +1,56 @@
package org.openrndr.extras.camera package org.openrndr.extras.camera
import org.openrndr.Extension import org.openrndr.Extension
import org.openrndr.color.ColorRGBa import org.openrndr.Program
import org.openrndr.draw.* import org.openrndr.color.ColorRGBa
import org.openrndr.math.Vector3 import org.openrndr.draw.*
import org.openrndr.math.Vector3
@Suppress("unused")
class Debug3D(val size: Int = 10, val divisions: Int = 10) : Extension { @Suppress("unused")
override var enabled: Boolean = true class Debug3D(val size: Int = 10, val divisions: Int = 10) : Extension {
private val step = size / divisions.toDouble() override var enabled: Boolean = true
private val step = size / divisions.toDouble()
private val grid = vertexBuffer(
vertexFormat { private val grid = vertexBuffer(
position(3) vertexFormat {
} position(3)
, 4 * (size * divisions + 1)).apply { }
put { , 4 * (size * divisions + 1)).apply {
val halfSize = size / 2.0 put {
var k = -halfSize val halfSize = size / 2.0
var k = -halfSize
for(i in 0 until divisions + 1) {
write(Vector3(-halfSize, 0.0, k)) for(i in 0 until divisions + 1) {
write(Vector3(halfSize, 0.0, k)) write(Vector3(-halfSize, 0.0, k))
write(Vector3(halfSize, 0.0, k))
write(Vector3(k, 0.0, -halfSize))
write(Vector3(k, 0.0, halfSize)) write(Vector3(k, 0.0, -halfSize))
write(Vector3(k, 0.0, halfSize))
k += step
} k += step
} }
} }
}
fun draw(drawer: Drawer) {
drawer.isolated { override fun beforeDraw(drawer: Drawer, program: Program) {
drawer.fill = ColorRGBa.WHITE draw(drawer)
drawer.stroke = ColorRGBa.WHITE }
drawer.vertexBuffer(grid, DrawPrimitive.LINES)
fun draw(drawer: Drawer) {
// Axis cross drawer.isolated {
drawer.stroke = ColorRGBa.RED drawer.fill = ColorRGBa.WHITE
drawer.lineSegment(Vector3.ZERO, Vector3(step, 0.0, 0.0)) drawer.stroke = ColorRGBa.WHITE
drawer.vertexBuffer(grid, DrawPrimitive.LINES)
drawer.stroke = ColorRGBa.GREEN
drawer.lineSegment(Vector3.ZERO, Vector3(0.0, step, 0.0)) // Axis cross
drawer.stroke = ColorRGBa.RED
drawer.stroke = ColorRGBa.BLUE drawer.lineSegment(Vector3.ZERO, Vector3(step, 0.0, 0.0))
drawer.lineSegment(Vector3.ZERO, Vector3(0.0, 0.0, step))
} drawer.stroke = ColorRGBa.GREEN
} drawer.lineSegment(Vector3.ZERO, Vector3(0.0, step, 0.0))
drawer.stroke = ColorRGBa.BLUE
drawer.lineSegment(Vector3.ZERO, Vector3(0.0, 0.0, step))
}
}
} }

View File

@@ -0,0 +1,36 @@
package org.openrndr.extras.camera
import org.openrndr.Extension
import org.openrndr.Program
import org.openrndr.draw.Drawer
import org.openrndr.math.Vector3
/**
* Extension that provides orbital camera view and controls.
*/
class Orbital : Extension {
override var enabled: Boolean = true
var eye = Vector3.UNIT_Z * 10.0
var lookAt = Vector3.ZERO
var near = 0.1
var far = 1000.0
var fov = 90.0
var userInteraction = true
var keySpeed = 1.0
val camera by lazy { OrbitalCamera(eye, lookAt, fov, near, far) }
val controls by lazy { OrbitalControls(camera, userInteraction, keySpeed) }
override fun setup(program: Program) {
controls.setup(program)
}
override fun beforeDraw(drawer: Drawer, program: Program) {
camera.beforeDraw(drawer, program)
}
override fun afterDraw(drawer: Drawer, program: Program) {
camera.afterDraw(drawer, program)
}
}

View File

@@ -1,154 +1,168 @@
package org.openrndr.extras.camera package org.openrndr.extras.camera
import org.openrndr.Extension import org.openrndr.Extension
import org.openrndr.Program import org.openrndr.Program
import org.openrndr.draw.Drawer import org.openrndr.draw.DepthTestPass
import org.openrndr.math.Matrix44 import org.openrndr.draw.Drawer
import org.openrndr.math.Spherical import org.openrndr.math.Matrix44
import org.openrndr.math.Vector3 import org.openrndr.math.Spherical
import kotlin.math.abs import org.openrndr.math.Vector3
import org.openrndr.math.transforms.lookAt as lookAt_ import kotlin.math.abs
import org.openrndr.math.transforms.lookAt as lookAt_
class OrbitalCamera(eye: Vector3, lookAt: Vector3, var fov: Double, var near: Double = 0.1, var far: Double = 1000.0) : Extension {
// current position in spherical coordinates class OrbitalCamera(eye: Vector3 = Vector3.ZERO, lookAt: Vector3 = Vector3.UNIT_Z, var fov: Double = 90.0, var near: Double = 0.1, var far: Double = 1000.0) : Extension {
var spherical = Spherical.fromVector(eye) // current position in spherical coordinates
private set var spherical = Spherical.fromVector(eye)
var lookAt = lookAt private set
private set var lookAt = lookAt
private set
private var sphericalEnd = Spherical.fromVector(eye)
private var lookAtEnd = lookAt.copy() var depthTest = true
private var dirty: Boolean = true
private var lastSeconds: Double = -1.0 private var sphericalEnd = Spherical.fromVector(eye)
private var lookAtEnd = lookAt
var fovEnd = fov private var dirty: Boolean = true
private var lastSeconds: Double = -1.0
var dampingFactor = 0.05
var zoomSpeed = 1.0 var fovEnd = fov
fun setView(lookAt: Vector3, spherical: Spherical, fov:Double) { var dampingFactor = 0.05
this.lookAt = lookAt var zoomSpeed = 1.0
this.lookAtEnd = lookAt
this.spherical = spherical fun setView(lookAt: Vector3, spherical: Spherical, fov: Double) {
this.sphericalEnd = spherical this.lookAt = lookAt
this.fov = fov this.lookAtEnd = lookAt
this.fovEnd= fov this.spherical = spherical
} this.sphericalEnd = spherical
this.fov = fov
fun rotate(rotX: Double, rotY: Double) { this.fovEnd = fov
sphericalEnd += Spherical(0.0, rotX, rotY) }
sphericalEnd = sphericalEnd.makeSafe()
dirty = true fun rotate(rotX: Double, rotY: Double) {
} sphericalEnd += Spherical(0.0, rotX, rotY)
sphericalEnd = sphericalEnd.makeSafe()
fun rotateTo(rotX: Double, rotY: Double) { dirty = true
sphericalEnd = sphericalEnd.copy(theta = rotX, phi = rotY) }
sphericalEnd = sphericalEnd.makeSafe()
dirty = true fun rotateTo(rotX: Double, rotY: Double) {
} sphericalEnd = sphericalEnd.copy(theta = rotX, phi = rotY)
sphericalEnd = sphericalEnd.makeSafe()
fun rotateTo(eye: Vector3) { dirty = true
sphericalEnd = Spherical.fromVector(eye) }
sphericalEnd = sphericalEnd.makeSafe()
dirty = true fun rotateTo(eye: Vector3) {
} sphericalEnd = Spherical.fromVector(eye)
sphericalEnd = sphericalEnd.makeSafe()
fun dollyIn() { dirty = true
val zoomScale = Math.pow(0.95, zoomSpeed) }
dolly(sphericalEnd.radius * zoomScale - sphericalEnd.radius)
} fun dollyIn() {
val zoomScale = Math.pow(0.95, zoomSpeed)
fun dollyOut() { dolly(sphericalEnd.radius * zoomScale - sphericalEnd.radius)
val zoomScale = Math.pow(0.95, zoomSpeed) }
dolly(sphericalEnd.radius / zoomScale - sphericalEnd.radius)
} fun dollyOut() {
val zoomScale = Math.pow(0.95, zoomSpeed)
fun dolly(distance: Double) { dolly(sphericalEnd.radius / zoomScale - sphericalEnd.radius)
sphericalEnd += Spherical(distance, 0.0, 0.0) }
dirty = true
} fun dolly(distance: Double) {
sphericalEnd += Spherical(distance, 0.0, 0.0)
fun pan(x: Double, y: Double, z: Double) { dirty = true
val view = viewMatrix() }
val xColumn = Vector3(view.c0r0, view.c1r0, view.c2r0) * x
val yColumn = Vector3(view.c0r1, view.c1r1, view.c2r1) * y fun pan(x: Double, y: Double, z: Double) {
val zColumn = Vector3(view.c0r2, view.c1r2, view.c2r2) * z val view = viewMatrix()
lookAtEnd += xColumn + yColumn + zColumn val xColumn = Vector3(view.c0r0, view.c1r0, view.c2r0) * x
dirty = true val yColumn = Vector3(view.c0r1, view.c1r1, view.c2r1) * y
} val zColumn = Vector3(view.c0r2, view.c1r2, view.c2r2) * z
lookAtEnd += xColumn + yColumn + zColumn
fun panTo(target : Vector3) { dirty = true
lookAtEnd = target }
dirty = true
} fun panTo(target: Vector3) {
lookAtEnd = target
fun dollyTo(distance: Double) { dirty = true
sphericalEnd = sphericalEnd.copy(radius = distance ) }
dirty = true
} fun dollyTo(distance: Double) {
sphericalEnd = sphericalEnd.copy(radius = distance)
fun zoom(degrees: Double) { dirty = true
fovEnd += degrees }
}
fun zoom(degrees: Double) {
fun update(timeDelta: Double) { fovEnd += degrees
if (!dirty) return dirty = true
dirty = false }
val dampingFactor = dampingFactor * timeDelta / 0.0060 fun zoomTo(degrees: Double) {
val sphericalDelta = sphericalEnd - spherical fovEnd = degrees
val lookAtDelta = lookAtEnd - lookAt dirty = true
val fovDelta = fovEnd - fov }
if (
abs(sphericalEnd.radius) > EPSILON || fun update(timeDelta: Double) {
abs(sphericalEnd.theta) > EPSILON || if (!dirty) return
abs(sphericalEnd.phi) > EPSILON || dirty = false
abs(lookAtDelta.x) > EPSILON ||
abs(lookAtDelta.y) > EPSILON || val dampingFactor = dampingFactor * timeDelta / 0.0060
abs(lookAtDelta.z) > EPSILON || val sphericalDelta = sphericalEnd - spherical
abs(fovDelta) > EPSILON val lookAtDelta = lookAtEnd - lookAt
) { val fovDelta = fovEnd - fov
if (
fov += (fovDelta * dampingFactor) abs(sphericalEnd.radius) > EPSILON ||
spherical += (sphericalDelta * dampingFactor) abs(sphericalEnd.theta) > EPSILON ||
spherical = spherical.makeSafe() abs(sphericalEnd.phi) > EPSILON ||
lookAt += (lookAtDelta * dampingFactor) abs(lookAtDelta.x) > EPSILON ||
dirty = true abs(lookAtDelta.y) > EPSILON ||
abs(lookAtDelta.z) > EPSILON ||
} else { abs(fovDelta) > EPSILON
spherical = sphericalEnd.copy() ) {
lookAt = lookAtEnd.copy()
} fov += (fovDelta * dampingFactor)
spherical = spherical.makeSafe() spherical += (sphericalDelta * dampingFactor)
} spherical = spherical.makeSafe()
lookAt += (lookAtDelta * dampingFactor)
fun viewMatrix(): Matrix44 { dirty = true
return lookAt_(Vector3.fromSpherical(spherical) + lookAt, lookAt, Vector3.UNIT_Y)
} } else {
spherical = sphericalEnd.copy()
companion object { lookAt = lookAtEnd.copy()
private const val EPSILON = 0.000001 }
} spherical = spherical.makeSafe()
}
// EXTENSION
override var enabled: Boolean = true fun viewMatrix(): Matrix44 {
return lookAt_(Vector3.fromSpherical(spherical) + lookAt, lookAt, Vector3.UNIT_Y)
override fun beforeDraw(drawer: Drawer, program: Program) { }
if (lastSeconds == -1.0) lastSeconds = program.seconds
companion object {
val delta = program.seconds - lastSeconds private const val EPSILON = 0.000001
lastSeconds = program.seconds }
update(delta) // EXTENSION
override var enabled: Boolean = true
drawer.perspective(fov, program.window.size.x / program.window.size.y, near, far)
drawer.view = viewMatrix() override fun beforeDraw(drawer: Drawer, program: Program) {
} if (lastSeconds == -1.0) lastSeconds = program.seconds
override fun afterDraw(drawer: Drawer, program: Program) { val delta = program.seconds - lastSeconds
drawer.view = Matrix44.IDENTITY lastSeconds = program.seconds
drawer.ortho()
} update(delta)
}
drawer.perspective(fov, program.window.size.x / program.window.size.y, near, far)
drawer.view = viewMatrix()
if (depthTest) {
drawer.drawStyle.depthWrite = true
drawer.drawStyle.depthTestPass = DepthTestPass.LESS_OR_EQUAL
}
}
override fun afterDraw(drawer: Drawer, program: Program) {
drawer.view = Matrix44.IDENTITY
drawer.ortho()
}
}

View File

@@ -1,134 +1,134 @@
package org.openrndr.extras.camera package org.openrndr.extras.camera
import org.openrndr.* import org.openrndr.*
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.tan import kotlin.math.tan
class OrbitalControls(val orbitalCamera: OrbitalCamera, val userInteraction: Boolean = true, val keySpeed: Double = 1.0) : Extension { class OrbitalControls(val orbitalCamera: OrbitalCamera , val userInteraction: Boolean = true, val keySpeed: Double = 1.0) : Extension {
enum class STATE { enum class STATE {
NONE, NONE,
ROTATE, ROTATE,
PAN, PAN,
} }
private var state = STATE.NONE private var state = STATE.NONE
var fov = orbitalCamera.fov var fov = orbitalCamera.fov
private lateinit var program: Program private lateinit var program: Program
private lateinit var lastMousePosition: Vector2 private lateinit var lastMousePosition: Vector2
private fun mouseScrolled(event: MouseEvent) { private fun mouseScrolled(event: MouseEvent) {
if (abs(event.rotation.x) > 0.1) return if (abs(event.rotation.x) > 0.1) return
when { when {
event.rotation.y > 0 -> orbitalCamera.dollyIn() event.rotation.y > 0 -> orbitalCamera.dollyIn()
event.rotation.y < 0 -> orbitalCamera.dollyOut() event.rotation.y < 0 -> orbitalCamera.dollyOut()
} }
} }
private fun mouseMoved(event: MouseEvent) { private fun mouseMoved(event: MouseEvent) {
if (state == STATE.NONE) return if (state == STATE.NONE) return
val delta = lastMousePosition - event.position val delta = lastMousePosition - event.position
lastMousePosition = event.position lastMousePosition = event.position
if (state == STATE.PAN) { if (state == STATE.PAN) {
val offset = Vector3.fromSpherical(orbitalCamera.spherical) - orbitalCamera.lookAt val offset = Vector3.fromSpherical(orbitalCamera.spherical) - orbitalCamera.lookAt
// half of the fov is center to top of screen // half of the fov is center to top of screen
val targetDistance = offset.length * tan((fov / 2) * PI / 180) val targetDistance = offset.length * tan((fov / 2) * PI / 180)
val panX = (2 * delta.x * targetDistance / program.window.size.x) val panX = (2 * delta.x * targetDistance / program.window.size.x)
val panY = (2 * delta.y * targetDistance / program.window.size.y) val panY = (2 * delta.y * targetDistance / program.window.size.y)
orbitalCamera.pan(panX, -panY, 0.0) orbitalCamera.pan(panX, -panY, 0.0)
} else { } else {
val rotX = 2 * PI * delta.x / program.window.size.x val rotX = 2 * PI * delta.x / program.window.size.x
val rotY = 2 * PI * delta.y / program.window.size.y val rotY = 2 * PI * delta.y / program.window.size.y
orbitalCamera.rotate(rotX, rotY) orbitalCamera.rotate(rotX, rotY)
} }
} }
private fun mouseButtonDown(event: MouseEvent) { private fun mouseButtonDown(event: MouseEvent) {
val previousState = state val previousState = state
when (event.button) { when (event.button) {
MouseButton.LEFT -> { MouseButton.LEFT -> {
state = STATE.ROTATE state = STATE.ROTATE
} }
MouseButton.RIGHT -> { MouseButton.RIGHT -> {
state = STATE.PAN state = STATE.PAN
} }
MouseButton.CENTER -> { MouseButton.CENTER -> {
} }
MouseButton.NONE -> { MouseButton.NONE -> {
} }
} }
if (previousState == STATE.NONE) { if (previousState == STATE.NONE) {
lastMousePosition = event.position lastMousePosition = event.position
} }
} }
fun keyPressed(keyEvent: KeyEvent) { fun keyPressed(keyEvent: KeyEvent) {
if (keyEvent.key == KEY_ARROW_RIGHT) { if (keyEvent.key == KEY_ARROW_RIGHT) {
orbitalCamera.pan(keySpeed, 0.0, 0.0) orbitalCamera.pan(keySpeed, 0.0, 0.0)
} }
if (keyEvent.key == KEY_ARROW_LEFT) { if (keyEvent.key == KEY_ARROW_LEFT) {
orbitalCamera.pan(-keySpeed, 0.0, 0.0) orbitalCamera.pan(-keySpeed, 0.0, 0.0)
} }
if (keyEvent.key == KEY_ARROW_UP) { if (keyEvent.key == KEY_ARROW_UP) {
orbitalCamera.pan(0.0, keySpeed, 0.0) orbitalCamera.pan(0.0, keySpeed, 0.0)
} }
if (keyEvent.key == KEY_ARROW_DOWN) { if (keyEvent.key == KEY_ARROW_DOWN) {
orbitalCamera.pan(0.0, -keySpeed, 0.0) orbitalCamera.pan(0.0, -keySpeed, 0.0)
} }
if (keyEvent.name == "q") { if (keyEvent.name == "q") {
orbitalCamera.pan(0.0, -keySpeed, 0.0) orbitalCamera.pan(0.0, -keySpeed, 0.0)
} }
if (keyEvent.name == "e") { if (keyEvent.name == "e") {
orbitalCamera.pan(0.0, keySpeed, 0.0) orbitalCamera.pan(0.0, keySpeed, 0.0)
} }
if (keyEvent.name == "w") { if (keyEvent.name == "w") {
orbitalCamera.pan(0.0, 0.0, -keySpeed) orbitalCamera.pan(0.0, 0.0, -keySpeed)
} }
if (keyEvent.name == "s") { if (keyEvent.name == "s") {
orbitalCamera.pan(0.0, 0.0, keySpeed) orbitalCamera.pan(0.0, 0.0, keySpeed)
} }
if (keyEvent.name == "a") { if (keyEvent.name == "a") {
orbitalCamera.pan(-keySpeed, 0.0, 0.0) orbitalCamera.pan(-keySpeed, 0.0, 0.0)
} }
if (keyEvent.name == "d") { if (keyEvent.name == "d") {
orbitalCamera.pan(keySpeed, 0.0, 0.0) orbitalCamera.pan(keySpeed, 0.0, 0.0)
} }
if (keyEvent.key == KEY_PAGE_UP) { if (keyEvent.key == KEY_PAGE_UP) {
orbitalCamera.zoom(keySpeed) orbitalCamera.zoom(keySpeed)
} }
if (keyEvent.key == KEY_PAGE_DOWN) { if (keyEvent.key == KEY_PAGE_DOWN) {
orbitalCamera.zoom(-keySpeed) orbitalCamera.zoom(-keySpeed)
} }
} }
// EXTENSION // EXTENSION
override var enabled: Boolean = true override var enabled: Boolean = true
override fun setup(program: Program) { override fun setup(program: Program) {
this.program = program this.program = program
if (userInteraction) { if (userInteraction) {
program.mouse.moved.listen { mouseMoved(it) } program.mouse.moved.listen { mouseMoved(it) }
program.mouse.buttonDown.listen { mouseButtonDown(it) } program.mouse.buttonDown.listen { mouseButtonDown(it) }
program.mouse.buttonUp.listen { state = STATE.NONE } program.mouse.buttonUp.listen { state = STATE.NONE }
program.mouse.scrolled.listen { mouseScrolled(it) } program.mouse.scrolled.listen { mouseScrolled(it) }
program.keyboard.keyDown.listen { keyPressed(it) } program.keyboard.keyDown.listen { keyPressed(it) }
program.keyboard.keyRepeat.listen{ keyPressed(it) } program.keyboard.keyRepeat.listen{ keyPressed(it) }
} }
} }
} }