Skip to content

FLineUpload.kt

src/jvmMain/kotlin/afield/graphics/render/FLineUpload.kt · 283 lines

package and imports — 63 lines, from line 1
package afield.graphics.render

import afield.graphics.three.three

import afield.geometry.path.FLine
import afield.app.diagnostics.Named
import afield.graphics.vulkan.buffers.DynamicallyWriteable
import afield.ai.learning.*
import afield.ai.retrieval.*
import afield.animation.interpolation.*
import afield.animation.keyframes.*
import afield.animation.motion.*
import afield.app.commands.*
import afield.app.diagnostics.*
import afield.audio.analysis.*
import afield.audio.synth.*
import afield.data.collections.*
import afield.data.formats.*
import afield.data.graph.*
import afield.data.sorting.*
import afield.document.context.*
import afield.document.graph.*
import afield.document.properties.*
import afield.document.workspace.*
import afield.geometry.mesh.*
import afield.geometry.path.*
import afield.geometry.spatial.*
import afield.graphics.color.*
import afield.graphics.interaction.*
import afield.graphics.render.*
import afield.graphics.shader.*
import afield.graphics.text.*
import afield.graphics.vulkan.buffers.*
import afield.io.osc.*
import afield.io.process.*
import afield.lang.lambda.*
import afield.lang.python.*
import afield.lang.remotejs.*
import afield.lang.stacksl.*
import afield.math.autodiff.*
import afield.math.control.*
import afield.math.estimation.*
import afield.math.functions.*
import afield.math.linalg.*
import afield.math.optimization.*
import afield.math.statistics.*
import afield.math.units.*
import afield.media.image.*
import afield.media.video.*
import afield.runtime.execution.*
import afield.runtime.languages.*
import afield.runtime.lifecycle.*
import afield.simulation.agents.*
import afield.simulation.automata.*
import afield.simulation.particles.*
import afield.study.build.*
import afield.study.evidence.*
import afield.study.search.*
import afield.study.source.*
import afield.study.ui.*
import afield.ui.controls.*
import afield.ui.output.*
import afield.ui.visualization.*

Writes FLines into GPU vertex streams: pointsOut, linesOut and trianglesOut sample lines into DynamicallyWriteable position and colour buffers.

FLineMap is a named, ordered collection of lines with a revision hash for change detection.

class FLineMap : LinkedHashMap<String, FLine>()

Ordered map of named FLines with a lightweight revision hash.

Handy for GPU uploads where stable keys and change detection matter.

class FLineMap : LinkedHashMap<String, FLine>() {

val rev: Long

Combined revision hash of all contained lines.

    val rev: Long
        get() {
            var hash: Long = 1L
            values.forEach { hash = 31 * hash + it.rev }
            return hash
        }

operator fun plusAssign(f: FLine)

    operator fun plusAssign(f: FLine) {
        plusAssign("fline_${f.uid}" to f)
    }

operator fun plusAssign(f: Pair<String, FLine>)

    operator fun plusAssign(f: Pair<String, FLine>) {
        this[f.first] = f.second
    }

operator fun plusAssign(f: Named<FLine>)

    operator fun plusAssign(f: Named<FLine>) {
        this[f.name] = f.value
    }

override fun toString(): String

    override fun toString(): String = prettySummary(
        "FLineMap",
        "size" to size,
        "keys" to keys.take(4),
        "rev" to rev
    )
}

fun pointsOut

Convenience overload of pointsOut for an FLineMap.

fun pointsOut(
    m: FLineMap, vertexData: DynamicallyWriteable,
    pointData: DynamicallyWriteable,
    colorData: DynamicallyWriteable
) = pointsOut(m.values, vertexData, colorData, pointData)

fun pointsOut

Write point positions, colours and sizes for a collection of FLines.

Positions go to vertexData, one colour per point to colorData and one float per point to pointSizeData, all in node order. Close instructions are skipped — they carry no coordinate — so the count returned is the number of drawable nodes rather than nodes.size, and the three buffers stay in step with each other. Per-node pointSize and color properties win over the line-level ones.

@return number of points written.

fun pointsOut(
    m: Collection<FLine>,
    vertexData: DynamicallyWriteable,
    colorData: DynamicallyWriteable,
    pointSizeData: DynamicallyWriteable
): Int {

    var num = 0
    colorData.write {
        val cd = this
        pointSizeData.write {
            val pointd = this
            vertexData.write {

                m.forEachIndexed { i, f ->
                    val fallback = f.pointSize
                    val fallbackColor = f.color
                    f.nodes.forEach { node ->
                        // A Close carries no coordinate at all (`Node(0)`, so `to` indexes an empty
                        // storage), and every rect, `Rect.toFLine` and arrowhead ends in one — reading it
                        // threw IndexOutOfBoundsException for any `pointed` closed line. The Skiko and SVG
                        // point writers have always skipped it; this one now does too.
                        if (node is FLine.Close) return@forEach
                        put(node.to)
                        val q = (node.properties["pointSize"] as? Number ?: fallback).f
                        pointd.put(q)
                        cd.put((node.properties["color"] as? Vec4 ?: fallbackColor))
                        num++
                    }
                }
            }
        }
    }
    return num
}

fun trianglesOut

Convenience overload of trianglesOut for an FLineMap.

fun trianglesOut(
    m: FLineMap, vertexData: DynamicallyWriteable,
    colorData: DynamicallyWriteable,
    samplesPerCubic: Int = 100
) = trianglesOut(m.values, vertexData, colorData, samplesPerCubic)

fun trianglesOut

Tessellate filled FLines into triangles and write positions and colors.

@return number of vertices written.

fun trianglesOut(
    m: Collection<FLine>, vertexData: DynamicallyWriteable, colorData: DynamicallyWriteable, samplesPerCubic: Int = 100
): Int {

    var a = 0
    var splits = m.map { f ->
        val colors = f.colorsPer()
        var b = a
        a += colors.size / 4
        b to colors
    }

    var num = 0
    colorData.write {
        val cd = this
        vertexData.write {

            m.forEachIndexed { i, f ->
                f.tessellate(samplesPerCubic) { verts, index ->
                    index.forEach {
                        val at = vec(verts[3 * it + 0], verts[3 * it + 1], verts[3 * it + 2])
                        put(at)

                        // this can't possibly work with cubic interpolations, we need to map index back to drawing instruction
                        cd.put(splits[i].second[4 * it + 0])
                        cd.put(splits[i].second[4 * it + 1])
                        cd.put(splits[i].second[4 * it + 2])
                        cd.put(splits[i].second[4 * it + 3])
                        num++
                    }
                }
            }
        }
    }
    return num
}

fun linesOut

Convenience overload of linesOut for a single FLine.

fun linesOut(
    m: FLine,
    vertexData: DynamicallyWriteable? = null,
    colorData: DynamicallyWriteable? = null,
    samplesPerCubic: Int = 100
) = linesOut(listOf(m), vertexData, colorData, samplesPerCubic)

fun linesOut

Convenience overload of linesOut for an FLineMap.

fun linesOut(
    m: FLineMap, vertexData: DynamicallyWriteable? = null,
    colorData: DynamicallyWriteable? = null,
    samplesPerCubic: Int = 100
) = linesOut(m.values, vertexData, colorData, samplesPerCubic)

fun linesOut

Linearize stroke paths into a vertex stream, plus an optional colour stream indexed by node.

The two streams are not parallel and are not the same length. vertexData gets one entry per linearized sample — samplesPerCubic of them for every CubicTo — while colorData gets FLine.colorsPer() per line, which is one Vec4 per node. They are joined through the 4th component of each vertex: it carries inst + base, where base accumulates nodes.size across lines, i.e. an index into the concatenated per-node colour array. Do not read it as a per-vertex colour or as a segment id.

A sentinel (0,0,0,-1) is written after every line, not between them, so N lines produce N sentinels including a trailing one — a consumer that stops at "between" will read one too many. A node-less FLine still contributes its sentinel and nothing else.

@return number of vertices written, counting every sentinel — so samples + N, not samples.

fun linesOut(
    m: Collection<FLine>,
    vertexData: DynamicallyWriteable? = null,
    colorData: DynamicallyWriteable? = null,
    samplesPerCubic: Int = 100
): Int {

    var num = 0

    colorData?.write {
        m.map { f ->
            put(f.colorsPer())
        }
    }

    vertexData?.write {
        var base = 0
        m.forEach { f ->
            f.uniform_linearize(samplesPerCubic = samplesPerCubic) { at, inst ->
                put(at, inst + (if (inst < 0) -base else base))
                num += 1
            }
            put(0f, 0f, 0f, -1f) // this is actually important
            num += 1
            base += f.nodes.size
        }
    }

    return num
}