How to write index buffer?

Hello! I’ve been playing with VertexBuffer a lot recently and notice that the guide does not include how to use it with index buffers.

Looking at the API, it shows that the Drawer method for drawing vertexBuffers has an overload to draw with IndexBuffers.

The only problem is, I don’t know what’s the proper way to write an index buffer. If anyone can help with this part, it’d be great. Thanks!

By searching with ag in openrndr I see that

openrndr-core/src/main/kotlin/org/openrndr/internal/VertexBufferDrawer.kt
21:    fun drawVertexBuffer(drawContext: DrawContext, drawStyle: DrawStyle, primitive: DrawPrimitive, indexBuffer: IndexBuffer, vertexBuffers:List<VertexBuffer>, offset:Int, indexCount:Int) {

uses IndexBuffer and VertexBuffer.

Then createDynamicIndexBuffer() returns an IndexBuffer.

Does that give you any ideas? (I haven’t tried this myself)

There’s also Low-level drawing - OPENRNDR GUIDE but not many details there.

I’ll have to check it out. I’m wondering if the offset is in bytes or in element count. If it is in bytes, I would assume it’s with a stride of 4, assuming that we are using Uint16.

I also checked out the low level drawing guide but there are numerous broken links! :slight_smile:

The examples from the example repo are missing, but can be found by looking at the history:
The examples are back now :slight_smile:

Since IndexBuffer does not have a .shadow or .put you’d have to use .write directly.

val ib = indexBuffer(100, IndexType.INT16)
val bb = ByteBuffer.allocateDirect(200)
bb.order(ByteOrder.nativeOrder());
for (i in 0 until 100) { 
    bb.putShort(i.toShort()) 
} 
bb.rewind()
ib.write(bb)
1 Like

Thanks for explaining Edwin! I made an example using the index buffer. Posting it here for posterity :slight_smile:


//Example using index buffer
//draw a quad with 4 vertices and 6 indices
fun main() = application {
    program {
        val vb = vertexBuffer(vertexFormat {
            position(3)
            color(4)
        },4)
        vb.put {
            write(Vector3(width*0.25,height*0.25,0.0))
            write(Vector4(1.0,0.0,0.0,1.0))
            write(Vector3(width*0.75,height*0.25,0.0))
            write(Vector4(0.0,0.0,1.0,1.0))
            write(Vector3(width*0.25,height*0.75,0.0))
            write(Vector4(0.0,1.0,0.0,1.0))
            write(Vector3(width*0.75,height*0.75,0.0))
            write(Vector4(1.0,0.0,1.0,1.0))
        }

        //2 triangles, 6 indices
        val ib = indexBuffer(6, IndexType.INT16)
        //INT16 is 2 Bytes, so total needed size is 6x2=12
        val bb = ByteBuffer.allocateDirect(12) //in bytes
        bb.order(ByteOrder.nativeOrder()) //something to do with endianness
        //kotlin equivalent of Int16 is Short
        bb.putShort(0.toShort())
        bb.putShort(1.toShort())
        bb.putShort(2.toShort())
        bb.putShort(1.toShort())
        bb.putShort(3.toShort())
        bb.putShort(2.toShort())
        bb.rewind() //return the position of ByteBuffer back to 0 before writing to index buffer
        ib.write(bb)

        extend {
            drawer.shadeStyle = shadeStyle {
                vertexTransform = """
                    va_color = a_color;
                """.trimIndent()
                fragmentTransform = """
                    x_fill = va_color;
                """
            }
            drawer.vertexBuffer(ib,listOf(vb),DrawPrimitive.TRIANGLES)
        }
    }
}
1 Like

Thanks for the example @TSAO! :slight_smile:

It inspired me to do a remix trying to make it easier to add vertices and indices by reducing the amount of places one needs to update.

imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.color.presets.MAGENTA
import org.openrndr.extra.color.presets.ORANGE
import org.openrndr.math.Vector3
import java.nio.ByteBuffer
import java.nio.ByteOrder
/*
Example using index buffer
Draw 2 quads with 6 vertices and 12 indices
 0        1        2
 +--------+--------+
 |      / |       /|
 |    /   |     /  |
 |  /     |   /    |
 |/       | /      |
 +--------+--------+
 3        4        5
 Ascii diagram by https://textik.com/
*/
data class Vertex(val position: Vector3, val color: ColorRGBa)

fun main() = application {
    program {
        fun winPos(u: Double, v: Double) = drawer.bounds.position(u, v).xy0

        val vertices = listOf(
            Vertex(winPos(0.25, 0.25), ColorRGBa.RED), //0
            Vertex(winPos(0.50, 0.25), ColorRGBa.BLUE), //1
            Vertex(winPos(0.75, 0.25), ColorRGBa.GREEN), //2
            Vertex(winPos(0.25, 0.75), ColorRGBa.MAGENTA), //3
            Vertex(winPos(0.50, 0.75), ColorRGBa.YELLOW), //4
            Vertex(winPos(0.75, 0.75), ColorRGBa.ORANGE), //5
        )
        // Note: don't mix windings! Here all clockwise.
        val indices = listOf(
            0, 1, 3,
            1, 4, 3,
            1, 2, 4,
            2, 5, 4
        )

        // VertexBuffer
        val vb = vertexBuffer(vertexFormat {
            position(3)
            color(4)
        }, vertices.size)

        vb.put {
            vertices.forEach {
                write(it.position)
                write(it.color)
            }
        }

        // IndexBuffer
        val ib = indexBuffer(indices.size, IndexType.INT16)
        val bb = ByteBuffer.allocateDirect(ib.indexCount * ib.type.sizeInBytes)
        bb.order(ByteOrder.nativeOrder()) //something to do with endianness
        indices.forEach {
            bb.putShort(it.toShort()) //kotlin equivalent of Int16 is Short
        }
        bb.rewind() //return the position of ByteBuffer back to 0 before writing to index buffer
        ib.write(bb)

        extend {
            drawer.shadeStyle = shadeStyle {
                vertexTransform = "va_color = a_color;"
                fragmentTransform = "x_fill = va_color;"
            }
            drawer.vertexBuffer(ib, listOf(vb), DrawPrimitive.TRIANGLES)
        }
    }
}

It would be even nicer if it was possible to just specify vertices and indices and the vertexFormat is calculated automatically.

Just chiming in to let you know that I have recovered the samples in the openrndr-examples repository. I broke those when I updated openrndr-guide tooling, fix was easy and should not have waited for months :slight_smile:

Just for fun, I figured out a way pretty close to this. In VertexFormat.kt, after the existing fun attribute() I declare this method:

    fun attribute(variable: KProperty1<*, *>, arraySize: Int = 1) {
        attribute(
            variable.name,
            when (variable.returnType.classifier) {
                Double::class -> VertexElementType.FLOAT32
                Vector2::class -> VertexElementType.VECTOR2_FLOAT32
                Vector3::class -> VertexElementType.VECTOR3_FLOAT32
                Vector4::class, ColorRGBa::class -> VertexElementType.VECTOR4_FLOAT32
                else -> throw IllegalArgumentException(
                    "attribute($variable) unsupported"
                )
            }, arraySize
        )
    }

Then in my program above I can change the declaration of vb to look like this:

        val vb = vertexBuffer(vertexFormat {
            attribute(Vertex::position)
            attribute(Vertex::color)
        }, vertices.size)

Maybe not good for targeting JS and others?
Sorry for the tangent :slight_smile: