// Run this in a Field box. The small portable model needs no Python at runtime. val field = loadStippleTransport("public/docs/assets/stipple_transport/two-images.json") val canonical = field.sourceParticles(400, seed = 44) val lattice = (0..6).flatMap { row -> val v = -1.0 + row / 3.0 [ (0..24).map { vec(-1.0 + it / 12.0, v, 0.0) }, (0..24).map { vec(v, -1.0 + it / 12.0, 0.0) }, ] } var transportTime = 1.0 var movieTime = 0.5 var integrationSteps = 24 fun drawSheet() { // Always deform canonical coordinates, so particle i retains its identity. val particles = field.transport(canonical, transportTime, movieTime, integrationSteps) val dots = FLine() particles.forEach { dots.moveTo(it * 180.0) } dots.pointed = true dots.pointSize = 2.5 dots.color = vec(0.45, 0.85, 0.8, 1.0) val warped = lattice.map { row -> val f = FLine() f.breakNext() field.transport(row, transportTime, movieTime, integrationSteps).forEach { p -> f.lineTo(p * 180.0) } f.color = vec(0.55, 0.65, 0.8, 0.65) f.thickness = 0.65 f } lines(warped + dots, w = 800.0, h = 540.0, fitToSize = true) markdown( "times", "transport time **$transportTime** · movie time **$movieTime** · $integrationSteps integration steps", ) } controlGroup("transport plane") { // Experimental ranges remain accessible; extrapolation is not trained. slider("transport time", min = -0.25, max = 1.4, step = 0.025, initialValue = transportTime) { transportTime = it drawSheet() } slider("movie time", min = -0.25, max = 1.25, step = 0.025, initialValue = movieTime) { movieTime = it drawSheet() } slider("integration steps", min = 1.0, max = 64.0, step = 1.0, initialValue = integrationSteps.toDouble()) { integrationSteps = it.toInt() drawSheet() } } drawSheet() println("stipple sheet ready: ${field.parameterCount} parameters; independent transport/movie controls")