Extruding a shape into a 3D mesh

A program inspired by a question in Slack. The goal was to create an extruded 3D shape and apply a texture to the front while giving a flat color to the sides.

Normally one could assign uv texture coordinates to all the vertices (basically mapping each vertex to a position in a texture) when creating the mesh, but in this case I only observed the normal and if it’s z component is 0, I knew the corresponding vertex belonged to the sides of the shape.

If you visualize the normals of the shape in your head, you will see that one flat side has va_normal.z = 1.0, the other side has va_normal.z = -1.0, and the curved side has va_normal.z = 0.0.

imports
import org.openrndr.WindowMultisample
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.meshgenerators.extrudeShape
import org.openrndr.extra.meshgenerators.meshGenerator
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.shapes.hobbyCurve
import org.openrndr.math.Polar
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.shape.ShapeContour
fun main() {
    application {
        configure {
            width = 900
            height = 900
            multisample = WindowMultisample.SampleCount(8)
        }
        program {
            // Add an interactive 3D camera
            extend(Orbital()) {
                this.eye = Vector3(0.0, 0.0, 50.0)
            }
            // make a smooth 2D star shape with 7 tips
            val tips = 7
            val c = ShapeContour.fromPoints(List(tips * 2) {
                Polar(it * 180.0 / tips, 20.0 + 20 * (it % 2)).cartesian
            }, true).hobbyCurve().shape

            // extrude the 2D shape into a 3D mesh
            val m = meshGenerator {
                extrudeShape(c, 10.0)
            }

            // create a texture to decorate the mesh
            val tex = renderTarget(400, 400) {
                colorBuffer()
            }.also { rt ->
                drawer.isolatedWithTarget(rt) {
                    ortho(rt)
                    strokeWeight = 20.0
                    repeat(20) {
                        stroke = ColorRGBa.fromVector(Vector3.uniform(0.0, 1.0))
                        lineSegment(
                            Vector2(it * 20.0, 0.0),
                            Vector2(it * 20.0, 400.0),
                        )
                    }
                }
            }.colorBuffer(0)

            // create a shader to apply the texture to the two flat surfaces of the star
            // and a flat color on the curved side
            val shader = shadeStyle {
                fragmentTransform = """
                        // calculate a normalized uv coordinate for the texture
                        vec2 uv = (va_position.xy - p_corner) / p_dimensions;
                        // normal.z is zero for the curved side area
                        // in that case use yellow, otherwise, read from the texture
                        x_fill.rgb = va_normal.z == 0.0 ? 
                            vec3(1.0, 0.9, 0.0) :
                            texture(p_tex, uv).rgb;
                """.trimIndent()

                // pass the position and dimensions of the star
                parameter("corner", c.bounds.corner)
                parameter("dimensions", c.bounds.dimensions)

                // pass the texture
                parameter("tex", tex)
            }

            extend {
                drawer.clear(ColorRGBa.WHITE)
                drawer.shadeStyle = shader
                drawer.vertexBuffer(m, DrawPrimitive.TRIANGLES)
            }
        }
    }
}
1 Like