# Drawing per-vertex colored meshes and variable thickness lines

**URL:** <https://openrndr.discourse.group/t/drawing-per-vertex-colored-meshes-and-variable-thickness-lines/266>\
**Category:** OPENRNDR\
**Created:** [July 25, 2021, 8:45pm UTC](https://openrndr.discourse.group/t/drawing-per-vertex-colored-meshes-and-variable-thickness-lines/266 "2021-07-25T20:45:16Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [July 25, 2021, 8:45pm UTC](https://openrndr.discourse.group/t/drawing-per-vertex-colored-meshes-and-variable-thickness-lines/266/1 "2021-07-25T20:45:16Z")

</div>

I just wrote a simple program drawing 300 triangles with unique colors in each vertex, the shader doing color interpolation. I like gradients 🙂

> **imports**
>
> ```kotlin
> import aBeLibs.geometry.randomPoint
> import org.openrndr.application
> import org.openrndr.draw.DrawPrimitive
> import org.openrndr.draw.shadeStyle
> import org.openrndr.draw.vertexBuffer
> import org.openrndr.draw.vertexFormat
> import org.openrndr.extra.noise.Random
> 
> ```

```kotlin
// openrndr 0.4
fun main() = application {
    configure {
        width = 800
        height = 800
    }
    program {
        // create a buffer and specify it's format and size.
        val geometry = vertexBuffer(vertexFormat {
            position(3)
            color(4)
        }, 3 * 100)

        // create an area with some padding around the edges
        val area = drawer.bounds.offsetEdges(-50.0)
        // populate the vertex buffer.
        geometry.put {
            for (i in 0 until geometry.vertexCount) {
                write(area.randomPoint().vector3(z = 0.0))
                write(Random.vector4(0.0, 1.0))
            }
        }
        extend {
            // shader using the color attributes from our buffer
            drawer.shadeStyle = shadeStyle {
                fragmentTransform = "x_fill = va_color;"
            }
            drawer.vertexBuffer(geometry, DrawPrimitive.TRIANGLES)
        }
    }
}

```

 ![WigglyLine-2021-07-25-22.35.55](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/25598702cebe79c7e2bbef66ce6fdfd52ae5272a.jpeg)

```auto
/**
 * Returns a random point inside a rectangle
 */

fun Rectangle.randomPoint(): Vector2 {
    return Vector2.uniform(this.corner, this.corner + this.dimensions)
}

```

---

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [July 25, 2021, 10:10pm UTC](https://openrndr.discourse.group/t/drawing-per-vertex-colored-meshes-and-variable-thickness-lines/266/2 "2021-07-25T22:10:54Z")

</div>

The code above was a preparation for this program, which converts an open animated shapeContour into a mesh (a triangle strip) with variable width and unique colors per vertex.

> **imports**
>
> ```kotlin
> import org.openrndr.application
> import org.openrndr.color.ColorRGBa
> import org.openrndr.draw.DrawPrimitive
> import org.openrndr.draw.shadeStyle
> import org.openrndr.draw.vertexBuffer
> import org.openrndr.draw.vertexFormat
> import org.openrndr.extensions.Screenshots
> import org.openrndr.extra.noise.Random
> import org.openrndr.math.Polar
> import org.openrndr.shape.ShapeContour
> 
> ```

```kotlin
fun main() = application {
    configure {
        width = 800
        height = 800
    }
    program {
        val geometry = vertexBuffer(vertexFormat {
            position(3)
            color(4)
        }, 100)

        extend(Screenshots())
        extend {
            drawer.clear(ColorRGBa.WHITE)
            drawer.shadeStyle = shadeStyle {
                fragmentTransform = "x_fill = va_color;"
            }
            val line = ShapeContour.fromPoints(List(geometry.vertexCount / 2) {
                val theta = it * 10.0 + seconds * 20
                val radius = 200.0 + 80 * Random.perlin(theta * 0.01, 1.0)
                Polar(theta, radius).cartesian + drawer.bounds.center
            }, false)
            
            val points = geometry.vertexCount / 2
            geometry.put {
                for (i in 0 until points) {
                    val pc = i / (points - 1.0)
                    val color = ColorRGBa.GRAY.mix(ColorRGBa.PINK, pc).toVector4()
                    val pos = line.position(pc)
                    val normal = line.normal(pc).normalized *
                            (40 + 20 * Random.perlin(pos * 0.01))
                    write((pos + normal).vector3(z = 0.0))
                    write(color)
                    write((pos - normal).vector3(z = 0.0))
                    write(color)
                }
            }
            drawer.vertexBuffer(geometry, DrawPrimitive.TRIANGLE_STRIP)
        }
    }
}

```

 ![WigglyLine-2021-07-26-00.08.02](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/0c7162e1433d391e9d507d4f06f530b11b05a53b.png)

---

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [January 29, 2022, 8:15pm UTC](https://openrndr.discourse.group/t/drawing-per-vertex-colored-meshes-and-variable-thickness-lines/266/3 "2022-01-29T20:15:27Z")

</div>

# _Thickline_

**A class to draw variable width lines**

![apps4.WigglyLine2-2022-01-29-21.06.05](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/c20686b9d8d69443518c6954352c3d66e0446189.gif)

> **imports**
>
> ```auto
> import org.openrndr.KEY_ESCAPE
> import org.openrndr.application
> import org.openrndr.color.ColorRGBa
> import org.openrndr.draw.*
> import org.openrndr.math.Vector2
> import org.openrndr.shape.ShapeContour
> import org.openrndr.extra.videoprofiles.gif
> import kotlin.math.PI
> import kotlin.math.cos
> import kotlin.math.sin
> 
> ```

```auto
fun main() = application {
    configure {
        width = 400
        height = 400
    }
    program {
        val thickLine = ThickLine(
            200, ColorRGBa.WHITE, ColorRGBa.GRAY
        ) { t -> 10.0 - 10.0 * cos(t * PI * 2) }

        // extend(ScreenRecorder()) { gif() }

        extend {
            drawer.clear(ColorRGBa.WHITE)

            val thinLine = ShapeContour.fromPoints(List(50) {
                val a = seconds * 2 + it * 0.08
                Vector2(
                    sin(a + sin(a * 0.31)),
                    sin(a * 0.53 + sin(a * 0.73))
                ) * 160.0 + drawer.bounds.center
            }, false)

            thickLine.draw(drawer, thinLine)
        }
        keyboard.keyDown.listen {
            if(it.key == KEY_ESCAPE) {
                application.exit()
            }
        }
    }
}

/**
 * A class to draw [ShapeContour] objects as a thick line. The color is 
 * linearly interpolated from [color0] to [color1] from line start to end.
 * The thickness of the line is defined by a function that takes a normalized
 * `t` Double value as input and outputs the desired width. Get a uniform width 
 * using something like `{ t -> 10.0 }`
 */
class ThickLine(
    private val pointCount: Int,
    private val color0: ColorRGBa,
    private val color1: ColorRGBa,
    val width: (Double) -> Double
) {
    val geometry = vertexBuffer(vertexFormat {
        position(3)
        color(4)
    }, pointCount * 2)

    fun draw(drawer: Drawer, thinLine: ShapeContour) {
        drawer.shadeStyle = shadeStyle {
            fragmentTransform = "x_fill = va_color;"
        }
        geometry.put {
            for (i in 0 until pointCount) {
                val pc = i / (pointCount - 1.0)
                val color = color0.mix(color1, pc).toVector4()
                val pos = thinLine.position(pc)
                val normal = thinLine.normal(pc).normalized * width(pc) / 2.0
                write((pos + normal).vector3(z = 0.0))
                write(color)
                write((pos - normal).vector3(z = 0.0))
                write(color)
            }
        }
        drawer.vertexBuffer(geometry, DrawPrimitive.TRIANGLE_STRIP)
    }
}

```
