Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small kool showcase examples #532

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ repositories {
mavenCentral()
maven("https://maven.scijava.org/content/groups/public")
maven("https://jitpack.io")
maven("https://raw.githubusercontent.com/kotlin-graphics/mary/master")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please get artifacts deployed to maven.scijava.org, instead of a custom GitHub-based repository, which should not be done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What shouldn't be done is loading big jars (ie fat jars)

This is not the case, the ones I push weight few KBs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be done at all IMHO—now you have to run that repo forever or else old builds will break. And as described in that article, it creates a situation where there's another repository that has to be queried for all the dependencies, slowing down builds. Just release the projects on maven.scijava.org or oss.sonatype.org! Once you have the infrastructure in place for Sonatype, deploying is a button push—the only huge PITA is getting it set up at the beginning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now you have to run that repo forever or else old builds will break.

True, but you know what I have to do to run it forever? Just avoid deleting it

I might be move everything to maven, though (here)

I'd love to deploy to maven.scijava.org, but I have a dev way to release very often and that wasn't compatible with that, or do I recall it wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publishing to maven.scijava.org is just a standard deploy over WebDAV, I'm sure the Gradle tooling will support it with your credentials both manually and/or automatically. As long as your tooling doesn't try to deploy the same release artifact more than once, which is forbidden due to release artifacts being immutable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much does it take the publication over there? How long before it's available for consumption?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artifacts published to maven.scijava.org are available instantly. Artifacts published to oss.sonatype.org (or s01.oss.sonatype.org) are available instantly from those URLs, but typically take 10-30 minutes to get mirrored over to Maven Central.

// mavenLocal()
}

Expand Down Expand Up @@ -133,6 +134,8 @@ dependencies {

implementation("org.jfree:jfreechart:1.5.0")
implementation("net.imagej:imagej-ops:0.45.5")

implementation("kotlin.graphics:kool:0.9.79")
}

val isRelease: Boolean
Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/graphics/scenery/FullscreenObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package graphics.scenery

import graphics.scenery.geometry.GeometryType
import graphics.scenery.attribute.material.Material
import kool.floatBufferOf
import kool.toFloatBuffer

/**
*
Expand All @@ -12,12 +14,11 @@ class FullscreenObject : Mesh("FullscreenObject") {
init {
geometry {
// fake geometry
this.vertices = BufferUtils.allocateFloatAndPut(
floatArrayOf(
this.vertices = floatBufferOf(
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f))
-1.0f, 1.0f, 0.0f)

this.normals = BufferUtils.allocateFloatAndPut(
floatArrayOf(
Expand Down
62 changes: 35 additions & 27 deletions src/main/kotlin/graphics/scenery/Icosphere.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package graphics.scenery

import graphics.scenery.utils.Face
import graphics.scenery.utils.extensions.minus
import graphics.scenery.utils.extensions.plus
import graphics.scenery.utils.extensions.times
import graphics.scenery.utils.extensions.xy
import graphics.scenery.utils.inc
import graphics.scenery.utils.set
import kool.FloatBuffer
import kool.IntBuffer
import kool.adr
import kool.toPtr
import org.joml.Vector2f
import org.joml.Vector3f
import org.lwjgl.system.MemoryUtil
import java.util.*
import kotlin.math.*

Expand All @@ -16,15 +25,15 @@ import kotlin.math.*
* @param[subdivisions] Number of subdivisions of the base icosahedron
*/
open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere") {
fun MutableList<Vector3f>.addVertex(vararg v: Float) {
this.add(Vector3f(v))
fun MutableList<Vector3f>.addVertex(x: Float, y: Float, z: Float) {
this += Vector3f(x, y, z)
}

fun MutableList<Triple<Int, Int, Int>>.addFace(i: Int, j: Int, k: Int) {
this.add(kotlin.Triple(i, j, k))
fun MutableList<Face>.addFace(i: Int, j: Int, k: Int) {
this += Triple(i, j, k)
}

protected fun createBaseVertices(vertices: MutableList<Vector3f>, indices: MutableList<Triple<Int, Int, Int>>) {
protected fun createBaseVertices(vertices: MutableList<Vector3f>, indices: MutableList<Face>) {
val s = sqrt((5.0f - sqrt(5.0f)) / 10.0f)
val t = sqrt((5.0f + sqrt(5.0f)) / 10.0f)

Expand Down Expand Up @@ -73,11 +82,11 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere

protected fun refineTriangles(recursionLevel: Int,
vertices: MutableList<Vector3f>,
indices: MutableList<Triple<Int, Int, Int>>): MutableList<Triple<Int, Int, Int>> {
indices: MutableList<Face>): MutableList<Face> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hooray for type aliasing! 🏆

// refine triangles
var faces = indices
(0 until recursionLevel).forEach {
val faces2 = ArrayList<Triple<Int, Int, Int>>(indices.size * 3)
repeat(recursionLevel) {
val faces2 = ArrayList<Face>(indices.size * 3)

for (triangle in faces) {
// replace triangle by 4 triangles
Expand Down Expand Up @@ -124,7 +133,7 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere
val i = this.addVertex(middle)

// store it, return index
middlePointIndexCache.put(key, i)
middlePointIndexCache[key] = i
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elect86 Do you notice these manually, or lean on an IDE cleanup/simplification action? If so, what is it?

Copy link
Member Author

@elect86 elect86 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If suggested, "Alt+Enter" opens the action dialog and then you select the suggestion

Otherwise you need to explicitely call .set in order to force the IDE to pull in the correct import and then execute the suggestion

return i
}

Expand All @@ -136,17 +145,20 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere

init {
val vertexBuffer = ArrayList<Vector3f>()
val indexBuffer = ArrayList<Triple<Int, Int, Int>>()
val indexBuffer = ArrayList<Face>()

createBaseVertices(vertexBuffer, indexBuffer)
val faces = refineTriangles(subdivisions, vertexBuffer, indexBuffer)

geometry {

vertices = BufferUtils.allocateFloat(faces.size * 3 * 3)
normals = BufferUtils.allocateFloat(faces.size * 3 * 3)
texcoords = BufferUtils.allocateFloat(faces.size * 3 * 2)
indices = BufferUtils.allocateInt(0)
vertices = FloatBuffer(faces.size * 3 * 3)
normals = FloatBuffer(faces.size * 3 * 3)
texcoords = FloatBuffer(faces.size * 3 * 2)
indices = IntBuffer(0)
var pVtx = vertices.adr.toPtr<Vector3f>()
var pNorm = normals.adr.toPtr<Vector3f>()
var pTxc = texcoords.adr.toPtr<Vector2f>()

faces.forEach { f ->
val v1 = vertexBuffer[f.first]
Expand All @@ -156,13 +168,13 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere
val uv2 = vertexToUV(v2.normalize())
val uv3 = vertexToUV(v3.normalize())

(v1 * radius).get(vertices).position(vertices.position() + 3)
(v2 * radius).get(vertices).position(vertices.position() + 3)
(v3 * radius).get(vertices).position(vertices.position() + 3)
pVtx++[0] = v1 * radius
pVtx++[0] = v2 * radius
pVtx++[0] = v3 * radius

v1.get(normals).position(normals.position() + 3)
v2.get(normals).position(normals.position() + 3)
v3.get(normals).position(normals.position() + 3)
pNorm++[0] = v1
pNorm++[0] = v2
pNorm++[0] = v3

val uvNormal = (uv2 - uv1).cross(uv3 - uv1)
if(uvNormal.z() < 0.0f) {
Expand All @@ -177,14 +189,10 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere
}
}

uv1.xy().get(texcoords).position(texcoords.position() + 2)
uv2.xy().get(texcoords).position(texcoords.position() + 2)
uv3.xy().get(texcoords).position(texcoords.position() + 2)
pTxc++[0] = uv1.xy()
pTxc++[0] = uv2.xy()
pTxc++[0] = uv3.xy()
}

vertices.flip()
normals.flip()
texcoords.flip()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure: the kool FloatBuffer does not require flipping, whereas the one allocated by BufferUtils does?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using absolute methods to upload data, that's why

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I just wanted to make sure these lines didn't get dropped by accident. 👍

}

boundingBox = generateBoundingBox()
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/graphics/scenery/Node.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ interface Node : Networkable {

@Throws(IllegalStateException::class)
fun <T, U: T> getAttribute(attributeType: Class<T>) : U {
return getAttributeOrNull<T, U>(attributeType) ?: throw IllegalStateException("Node doesn't have attribute named " + attributeType)
return getAttributeOrNull<T, U>(attributeType) ?: throw IllegalStateException("Node doesn't have attribute named $attributeType")
}

fun ifSpatial(block: Spatial.() -> Unit): Spatial? {
Expand Down
38 changes: 13 additions & 25 deletions src/main/kotlin/graphics/scenery/attribute/geometry/Geometry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package graphics.scenery.attribute.geometry
import graphics.scenery.*
import graphics.scenery.geometry.GeometryType
import graphics.scenery.utils.extensions.minus
import graphics.scenery.utils.get
import graphics.scenery.utils.inc
import graphics.scenery.utils.set
import kool.*
import org.joml.Vector3f
import java.io.Serializable
import java.nio.FloatBuffer
Expand Down Expand Up @@ -41,38 +45,22 @@ interface Geometry : Serializable {
* STL's facet storage format into account.
*/
fun recalculateNormals() {
val vertexBufferView = vertices.asReadOnlyBuffer()
var i = 0
val normals = ArrayList<Float>()
var pVtx = vertices.adr.toPtr<Vector3f>()
var pNorm = normals.adr.toPtr<Vector3f>()

while (i < vertexBufferView.limit() - 1) {
val v1 = Vector3f(vertexBufferView[i], vertexBufferView[i + 1], vertexBufferView[i + 2])
i += 3

val v2 = Vector3f(vertexBufferView[i], vertexBufferView[i + 1], vertexBufferView[i + 2])
i += 3

val v3 = Vector3f(vertexBufferView[i], vertexBufferView[i + 1], vertexBufferView[i + 2])
i += 3
for (i in vertices.indices step 3) {
val v1 = pVtx++[0]
val v2 = pVtx++[0]
val v3 = pVtx++[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super concise, but I find it harder to understand. Maybe a middle ground could be:

fun makeVector(array, i) { return Vector3f(array[i], array[i + 1], array[i + 2]) }

val v1 = makeVector(vertexBufferView, i += 3)
val v2 = makeVector(vertexBufferView, i += 3)
val v3 = makeVector(vertexBufferView, i += 3)

or some such? Or even attach a toVector(i) method to the FloatBuffer class here?

It is fun to use overloaded operators, but the pointer thing with [0] is strange.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's essentially *pVtx++

Another alternative:

        for (i in vertices.indices step 3) {
            val v1 = pVtx[i]
            val v2 = pVtx[i + 1]
            val v3 = pVtx[i + 2]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like that much more!


val a = v2 - v1
val b = v3 - v1

val n = a.cross(b).normalize()

normals.add(n.x())
normals.add(n.y())
normals.add(n.z())

normals.add(n.x())
normals.add(n.y())
normals.add(n.z())

normals.add(n.x())
normals.add(n.y())
normals.add(n.z())
pNorm++[0] = n
pNorm++[0] = n
pNorm++[0] = n
}

this.normals = BufferUtils.allocateFloatAndPut(normals.toFloatArray())
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/graphics/scenery/utils/geometry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package graphics.scenery.utils

typealias Face = Triple<Int, Int, Int>
31 changes: 31 additions & 0 deletions src/main/kotlin/graphics/scenery/utils/pointers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package graphics.scenery.utils

import kool.Ptr
import kool.unsafe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ unsafe? Like sun.misc.Unsafe? Do you discourage its use? Or...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, atm it's the way for low level native operations

The whole Lwjgl ultimately rely on that as well

import org.joml.Vector2f
import org.joml.Vector3f

@JvmName("Vector3fGet")
operator fun Ptr<Vector3f>.get(index: Int): Vector3f {
val ofs = adr.toLong() + (index shl 2)
return Vector3f(unsafe.getFloat(ofs), unsafe.getFloat(ofs + 4), unsafe.getFloat(ofs + 8))
}

@JvmName("Vector3fSet")
operator fun Ptr<Vector3f>.set(index: Int, v: Vector3f) {
val ofs = adr.toLong() + (index shl 2)
unsafe.putFloat(ofs, v.x)
unsafe.putFloat(ofs + 4, v.y)
unsafe.putFloat(ofs + 8, v.z)
}
@JvmName("Vector2fSet")
operator fun Ptr<Vector2f>.set(index: Int, v: Vector2f) {
val ofs = adr.toLong() + (index shl 2)
unsafe.putFloat(ofs, v.x)
unsafe.putFloat(ofs + 4, v.y)
}

@JvmName("Vector3fInc")
operator fun Ptr<Vector3f>.inc() = Ptr<Vector3f>(adr.toLong() + 12)
@JvmName("Vector2fInc")
operator fun Ptr<Vector2f>.inc() = Ptr<Vector2f>(adr.toLong() + 8)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import graphics.scenery.volumes.Volume
import graphics.scenery.volumes.VolumeManager
import ij.IJ
import ij.ImagePlus
import kool.ByteBuffer
import net.imglib2.img.Img
import net.imglib2.img.display.imagej.ImageJFunctions
import net.imglib2.type.numeric.integer.UnsignedShortType
Expand Down Expand Up @@ -40,7 +41,7 @@ class CustomVolumeManagerExample : SceneryBase("CustomVolumeManagerExample") {
))
volumeManager.customTextures.add("OutputRender")

val outputBuffer = MemoryUtil.memCalloc(1280*720*4)
val outputBuffer = ByteBuffer(1280*720*4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the conciseness of this. But it makes me nervous that the kool buffer types are named ByteBuffer and FloatBuffer, when java.nio also has classes with those names. Does the possibility of name clashes or reader confusion concern you? If so, you could rename the kool buffer types to something else like KoolByteBuffer and KoolFloatBuffer, or Uint8Buffer and Float32Buffer.

Copy link
Member Author

@elect86 elect86 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They look like types, but are methods instead. It's a technique called fake/dummy constructors.

You'll recognize those in the IDE by being in italics

It's coherent with the current way to allocate Arrays and Lists

val ints = IntArray(2)
val ints = List(2) { i -> i + 1 }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. As long as they are actually returning ByteBuffer and FloatBuffer objects, that's awesome.

val outputTexture = Texture.fromImage(Image(outputBuffer, 1280, 720), usage = hashSetOf(Texture.UsageType.LoadStoreImage, Texture.UsageType.Texture))
volumeManager.material().textures["OutputRender"] = outputTexture

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package graphics.scenery.tests.unit

import graphics.scenery.BufferUtils
import graphics.scenery.utils.lazyLogger
import kool.FloatBuffer
import org.junit.Test
import java.nio.ByteOrder
import kotlin.test.assertEquals
Expand All @@ -23,7 +24,7 @@ class BufferUtilsTests {
fun testAllocateFloat() {
logger.info("Testing allocation of a new direct float buffer ...")
val size = kotlin.random.Random.nextInt(1, 10000)
val floatBuf = BufferUtils.allocateFloat(size)
val floatBuf = FloatBuffer(size)

assertEquals(size, floatBuf.capacity(), "Float buffer capacity was expected to be $size, but is ${floatBuf.capacity()}")
assertEquals (ByteOrder.nativeOrder(), floatBuf.order(), "Float buffer was expected to be in ${ByteOrder.nativeOrder()}, but is ${floatBuf.order()}")
Expand Down