Skip to content

Commit

Permalink
Merge pull request #227 from xulman/addingArrows
Browse files Browse the repository at this point in the history
Adding arrows
  • Loading branch information
skalarproduktraum authored Mar 9, 2019
2 parents 1a5b1e7 + f04a32b commit 01b2df7
Show file tree
Hide file tree
Showing 2 changed files with 292 additions and 0 deletions.
157 changes: 157 additions & 0 deletions src/main/kotlin/graphics/scenery/Arrow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package graphics.scenery

import cleargl.GLVector
import graphics.scenery.backends.ShaderType
import java.nio.FloatBuffer
import java.nio.IntBuffer

/**
* Class for creating 3D arrows, derived from [Node] and using [HasGeometry].
* The arrow is defined with [vector] having the vector's foot/start as the
* reference coordinate (arrow's foot will appear at the coordinate given to setPosition()).
*
* /|\
* / | \
* /--+--\
* |
* |
* |
* The arrow is created as the main segment (drawn vertically bottom to up),
* then 3 segments to build a triangle and then another triangle perpendicular
* to the former one; altogehter 7 segments drawn sequentially.
* The arrow head scales with the length of the arrow.
*
* @author Vladimir Ulman <ulman@mpi-cbg.de>
*/
class Arrow(var vector: GLVector = GLVector(0f,3)) : Node("Arrow"), HasGeometry {
/** Size of one vertex (e.g. 3 in 3D) */
override val vertexSize: Int = 3
/** Size of one texcoord (e.g. 2 in 3D) */
override val texcoordSize: Int = 2
/** Geometry type -- Default for Line is [GeometryType.LINE] */
override var geometryType: GeometryType = GeometryType.LINE_STRIP_ADJACENCY
/** Vertex buffer */
override var vertices: FloatBuffer = BufferUtils.allocateFloat(30)
/** Normal buffer */
override var normals: FloatBuffer = BufferUtils.allocateFloat(30)
/** Texcoord buffer */
override var texcoords: FloatBuffer = BufferUtils.allocateFloat(20)
/** Index buffer */
override var indices: IntBuffer = IntBuffer.wrap(intArrayOf())

/** Shader property for the line's starting segment color. Consumed by the renderer. */
@ShaderProperty
var startColor = GLVector(0.0f, 1.0f, 0.0f, 1.0f)

/** Shader property for the line's color. Consumed by the renderer. */
@ShaderProperty
var lineColor = GLVector(1.0f, 1.0f, 1.0f, 1.0f)

/** Shader property for the line's end segment color. Consumed by the renderer. */
@ShaderProperty
var endColor = GLVector(0.7f, 0.5f, 0.5f, 1.0f)

/** Shader property for the line's cap length (start and end caps). Consumed by the renderer. */
@ShaderProperty
var capLength = 1

/** Shader property to keep track of the current number of vertices. Consumed by the renderer. */
@ShaderProperty
var vertexCount: Int = 0
private set

/** Shader property for the line's edge width. Consumed by the renderer. */
@ShaderProperty
var edgeWidth = 2.0f

//shortcut to null vector... to prevent from creating it anew with every call to reshape()
private val zeroGLvec = GLVector(0.0f, 0.0f, 0.0f)

init {
material = ShaderMaterial.fromClass(Line::class.java, listOf(ShaderType.VertexShader, ShaderType.GeometryShader, ShaderType.FragmentShader))
material.cullingMode = Material.CullingMode.None

reshape(vector)
}

/**
* Changes the shape of this arrow.
*
* @param vector The vector defining the shape of the arrow
*/
fun reshape(vector: GLVector) {
//init the data structures
clearPoints()

/** create the vector shape */
//first of the two mandatory surrounding fake points that are never displayed
addPoint(zeroGLvec)

//the main "vertical" segment of the vector
addPoint(zeroGLvec)
addPoint(vector)

//the "horizontal" base segment of the "arrow head" triangles
var base = if (vector.x() == 0.0f && vector.y() == 0.0f) {
//the input 'vector' must be parallel to the z-axis,
//we can use this particular 'base' then
GLVector(0.0f, 1.0f, 0.0f)
}
else {
//vector 'base' is perpendicular to the input 'vector'
GLVector(-vector.y(), vector.x(), 0.0f).normalize()
}

//the width of the "arrow head" triangle
val v = 0.1f * vector.magnitude()

//the first triangle:
base = base.times(v)
addPoint(vector.times(0.8f).plus(base))
addPoint(vector.times(0.8f).minus(base))
addPoint(vector)
//NB: the 0.8f defines the height (1-0.8) of the "arrow head" triangle

//the second triangle:
base = base.cross(vector).normalize().times(v)
addPoint(vector.times(0.8f).plus(base))
addPoint(vector.times(0.8f).minus(base))
addPoint(vector)

//second of the two mandatory surrounding fake points that are never displayed
addPoint(vector)
}

private fun addPoint(p: GLVector) {
vertices.position(vertices.limit())
vertices.limit(vertices.limit() + 3)
vertices.put(p.toFloatArray())
vertices.flip()

normals.position(normals.limit())
normals.limit(normals.limit() + 3)
normals.put(p.toFloatArray())
normals.flip()

texcoords.position(texcoords.limit())
texcoords.limit(texcoords.limit() + 2)
texcoords.put(0.225f)
texcoords.put(0.225f)
texcoords.flip()

dirty = true
vertexCount = vertices.limit()/vertexSize

boundingBox = generateBoundingBox()
}

private fun clearPoints() {
vertices.clear()
normals.clear()
texcoords.clear()

vertices.limit(0)
normals.limit(0)
texcoords.limit(0)
}
}
135 changes: 135 additions & 0 deletions src/test/tests/graphics/scenery/tests/examples/basic/ArrowExample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package graphics.scenery.tests.examples.basic

import cleargl.GLVector
import graphics.scenery.*
import graphics.scenery.backends.Renderer
import org.junit.Test
import kotlin.concurrent.thread
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin

/**
* Simple example to demonstrate the drawing of 3D arrows/vectors.
*
* This example will draw a circle made of vectors using
* the [Arrow] class. One vector will, however, always
* illuminate differently, and the position of such will
* circulate...
*
* @author Vladimir Ulman <ulman@mpi-cbg.de>
*/
class ArrowExample : SceneryBase("ArrowExample") {

override fun init() {
renderer = Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight)
hub.add(SceneryElement.Renderer, renderer!!)

setupScene()
useScene()
}


private fun setupScene() {
//boundaries of our world
val hull = Box(GLVector(50.0f, 50.0f, 50.0f), insideNormals = true)
hull.material.diffuse = GLVector(0.2f, 0.2f, 0.2f)
hull.material.cullingMode = Material.CullingMode.Front
scene.addChild(hull)

//lights and camera
var pl = emptyArray<PointLight>()
for (i in 0..3)
{
val l = PointLight(radius = 200.0f)
l.intensity = 600.0f
l.emissionColor = GLVector(1.0f,3)

scene.addChild(l)
pl = pl.plus(l)
}
pl[0].position = GLVector(0f,10f,0f)
pl[1].position = GLVector(0f,-10f,0f)
pl[2].position = GLVector(-10f,0f,0f)
pl[3].position = GLVector(10f,0f,0f)

val cam: Camera = DetachedHeadCamera()
cam.position = GLVector(0.0f, 0.0f, 15.0f)
cam.perspectiveCamera(50.0f, windowWidth.toFloat(), windowHeight.toFloat())
cam.active = true
scene.addChild(cam)
}


private fun useScene() {
//we shall have faint and bright vectors...
val matBright = Material()
matBright.diffuse = GLVector(0.0f, 1.0f, 0.0f)
matBright.ambient = GLVector(1.0f, 1.0f, 1.0f)
matBright.specular = GLVector(1.0f, 1.0f, 1.0f)
matBright.cullingMode = Material.CullingMode.None

val matFaint = Material()
matFaint.diffuse = GLVector(0.0f, 0.6f, 0.6f)
matFaint.ambient = GLVector(1.0f, 1.0f, 1.0f)
matFaint.specular = GLVector(1.0f, 1.0f, 1.0f)
matFaint.cullingMode = Material.CullingMode.None

//... arranged along a circle
val circleCentre = GLVector(0.0f,3)
val circleRadius = 6.0f

val arrowsInCircle = 30


//create the circle of vectors
var al = emptyArray<Arrow>()
var lastPos = GLVector(circleRadius,0f,0f)
var currPos : GLVector
for (i in 1..arrowsInCircle)
{
val curAng = (i*2.0f* PI/arrowsInCircle).toFloat()
currPos = GLVector(circleRadius*cos(curAng) +circleCentre.x(),
circleRadius*sin(curAng) +circleCentre.y(),
circleCentre.z())

// ========= this is how you create an Arrow =========
val a = Arrow(currPos.minus(lastPos)) //shape of the vector itself
a.position = lastPos //position/base of the vector
a.material = matFaint //usual stuff follows...
a.edgeWidth = 0.5f
scene.addChild(a)

// if you want to change the shape and position of the vector later,
// you can use this (instead of creating a new vector)
// a.reshape( newVector )
// a.position = newBase
// ========= this is how you create an Arrow =========

al = al.plus(a)

lastPos = currPos
}


//finally, have some fun...
thread {
var i = 0
while (true) {
al[i].material = matFaint
i = (i+1).rem(arrowsInCircle)
al[i].material = matBright

Thread.sleep(150)
}
}
}

override fun inputSetup() {
setupCameraModeSwitching(keybinding = "C")
}

@Test override fun main() {
super.main()
}
}

0 comments on commit 01b2df7

Please sign in to comment.