package phenax.utils import java.io.File import kotlin.properties.ReadOnlyProperty import org.openrndr.ApplicationBuilder import org.openrndr.Program import org.openrndr.draw.* import org.openrndr.color.* import org.openrndr.extra.filewatcher.watchingFile import org.openrndr.extra.olive.oliveProgram as oliveProgramOriginal fun Int.seconds(): Int = this * 1000 fun Int.minutes(): Int = this.seconds() * 60 fun recordDuration(millis: Int) { System.setProperty("phenax.record.frameCount", (60 * millis / 1000).toInt().toString()) } fun recordDuration(): Int? = System.getProperty("phenax.record.frameCount")?.toIntOrNull() val recordingEnabled: Boolean by lazy { System.getenv("PHENAX_RECORD") != null } val screenshotEnabled: Boolean by lazy { System.getenv("PHENAX_SCREENSHOT") != null } // fun classDir(): String = // "src/main/kotlin/${stackRootClassName(sanitize = // true).split(".").dropLast(1).joinToString("/")}"; // fun localComputeShader(name: String): ComputeShader = // ComputeShader.fromCode(File("src/main/resources/shaders/${name}.cs").readText(), name) // Have to keep the same name (oliveProgram) to get update on save to work fun ApplicationBuilder.oliveProgram(init: Program.() -> Unit) { if (recordingEnabled || screenshotEnabled) { program(init) } else { // TODO: Make watching shader files work oliveProgramOriginal(init = init) } } fun preprocessShader(shaderCode: String): String { val pattern = Regex("#include\\s+<([A-Za-z0-9-_/.]+)>") val includes = pattern.findAll(shaderCode) var parts: MutableList = mutableListOf() var lastIndex = 0 includes.forEach { parts.add(shaderCode.slice(lastIndex..(it.range.first - 1))) lastIndex = it.range.last + 1 val modulePath = it.groupValues[1] val moduleContents = File("src/main/resources/shaders/modules/${modulePath}.glsl").readText() parts.add("/* Module ${modulePath} */") parts.add(moduleContents) parts.add("/* End module ${modulePath} */") } parts.add(shaderCode.slice(lastIndex..(shaderCode.length - 1))) return parts.joinToString(separator = "\n") } val defaultShaderCode: String = "" fun averageColor(cb: ColorBuffer): ColorRGBa { val shadow = cb.shadow shadow.download() var r = 0.0 var g = 0.0 var b = 0.0 var a = 0.0 for (y in 0 until cb.height) { for (x in 0 until cb.width) { val c = shadow[x, y] r += c.r g += c.g b += c.b a += c.alpha } } val n = (cb.width * cb.height).toDouble() return ColorRGBa(r / n, g / n, b / n, a / n) } fun Program.liveComputeShader(name: String): ReadOnlyProperty { val shaderCode by watchingFile(File("src/main/resources/shaders/${name}.glsl")) { file -> println("change") preprocessShader(file.readText().toString()) } var cs: ComputeShader = ComputeShader.fromCode(shaderCode, name) if (!recordingEnabled || screenshotEnabled) { var lastCompiledCode: String = shaderCode program.extend { if (shaderCode != lastCompiledCode) { cs.destroy() try { cs = ComputeShader.fromCode(shaderCode, name) } catch (e: Exception) { println("Shader compile failed: ${e.message}") // Restore old shader if new code has error or default shader try { cs = ComputeShader.fromCode(lastCompiledCode, name) } catch (e: Exception) { println("Falling back to default shader") cs = ComputeShader.fromCode(defaultShaderCode, name) } } lastCompiledCode = shaderCode } } } return ReadOnlyProperty { _, _ -> cs } }