From a63bc8a71e056572038db6a5e10c22dcf86d047d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 27 Nov 2024 20:50:33 +0000 Subject: [PATCH] Fixed #78: Add SDF helpers --- .../src/test/scala/ultraviolet/sdfTests.scala | 113 ++++++++++++++++++ .../src/main/scala/ultraviolet/sdf.scala | 26 ++++ 2 files changed, 139 insertions(+) create mode 100644 ultraviolet/js/src/test/scala/ultraviolet/sdfTests.scala create mode 100644 ultraviolet/shared/src/main/scala/ultraviolet/sdf.scala diff --git a/ultraviolet/js/src/test/scala/ultraviolet/sdfTests.scala b/ultraviolet/js/src/test/scala/ultraviolet/sdfTests.scala new file mode 100644 index 0000000..908ecc7 --- /dev/null +++ b/ultraviolet/js/src/test/scala/ultraviolet/sdfTests.scala @@ -0,0 +1,113 @@ +package ultraviolet + +import ultraviolet.syntax.* + +class sdfTests extends munit.FunSuite { + + test("A circle SDF") { + + val p = vec2(1.0f, 0.0f) + + val actual = + ultraviolet.sdf.circle(p, 1.0f) + + val expected = + 0.0f + + assert(closeEnough(clue(actual), clue(expected))) + } + + test("Circle SDF acceptance test") { + inline def fragment: Shader[Unit, Float] = + Shader { _ => + + import ultraviolet.sdf.* + + def proxy: (vec2, Float) => Float = (p, r) => circle(p, r) + + proxy(vec2(0.5f), 1.5f) + } + + val actual = + fragment.toGLSL[WebGL2].toOutput.code + + // DebugAST.toAST(fragment) + + assertEquals( + actual, + s""" + |float def0(in vec2 p,in float r){ + | return length(p)-r; + |} + |def0(vec2(0.5),1.5); + |""".stripMargin.trim + ) + } + + test("Square SDF acceptance test") { + inline def fragment: Shader[Unit, Float] = + Shader { _ => + + import ultraviolet.sdf.* + + def proxy: (vec2, vec2) => Float = (p, b) => square(p, b) + + proxy(vec2(0.5f), vec2(1.5f)) + } + + val actual = + fragment.toGLSL[WebGL2].toOutput.code + + // DebugAST.toAST(fragment) + + assertEquals( + actual, + s""" + |float def0(in vec2 p,in vec2 b){ + | vec2 d=abs(p)-b; + | return length(max(d,0.0))+min(max(d.x,d.y),0.0); + |} + |def0(vec2(0.5),vec2(1.5)); + |""".stripMargin.trim + ) + } + + test("Star with five points SDF acceptance test") { + inline def fragment: Shader[Unit, Float] = + Shader { _ => + + import ultraviolet.sdf.* + + def proxy: (vec2, Float, Float) => Float = (p, r, rf) => star(p, r, rf) + + proxy(vec2(0.5f), 1.5f, 1.0f) + } + + val actual = + fragment.toGLSL[WebGL2].toOutput.code + + // DebugAST.toAST(fragment) + + assertEquals( + actual, + s""" + |float def0(in vec2 p,in float r,in float rf){ + | vec2 k1=vec2(0.80901700258255,-0.5877852439880371); + | vec2 k2=vec2(-k1.x,k1.y); + | vec2 p2=vec2(abs(p.x),p.y); + | p2=p2-((2.0*max(dot(k1,p2),0.0))*k1); + | p2=p2-((2.0*max(dot(k2,p2),0.0))*k2); + | p2=vec2(abs(p2.x),p2.y-r); + | vec2 ba=(rf*(vec2(-k1.y,k1.x)))-vec2(0.0,1.0); + | float h=clamp(dot(p2,ba)/dot(ba,ba),0.0,r); + | return (length(p2-(ba*h)))*(sign((p2.y*ba.x)-(p2.x*ba.y))); + |} + |def0(vec2(0.5),1.5,1.0); + |""".stripMargin.trim + ) + } + + def closeEnough(a: Float, b: Float): Boolean = + Math.abs(Math.abs(a) - Math.abs(b)) < 0.01f + +} diff --git a/ultraviolet/shared/src/main/scala/ultraviolet/sdf.scala b/ultraviolet/shared/src/main/scala/ultraviolet/sdf.scala new file mode 100644 index 0000000..231d296 --- /dev/null +++ b/ultraviolet/shared/src/main/scala/ultraviolet/sdf.scala @@ -0,0 +1,26 @@ +package ultraviolet + +import ultraviolet.syntax.* + +object sdf: + + inline def circle(point: vec2, radius: Float): Float = + length(point) - radius + + inline def square(point: vec2, halfSize: vec2): Float = + val d = abs(point) - halfSize + length(max(d, 0.0f)) + min(max(d.x, d.y), 0.0f) + + inline def star(point: vec2, radius: Float, innerRadius: Float): Float = + val k1: vec2 = vec2(0.809016994375f, -0.587785252292f) + val k2: vec2 = vec2(-k1.x, k1.y) + var p2 = vec2(abs(point.x), point.y) + + p2 = p2 - 2.0f * max(dot(k1, p2), 0.0f) * k1 + p2 = p2 - 2.0f * max(dot(k2, p2), 0.0f) * k2 + p2 = vec2(abs(p2.x), p2.y - radius) + + val ba: vec2 = innerRadius * vec2(-k1.y, k1.x) - vec2(0.0f, 1.0f) + val h: Float = clamp(dot(p2, ba) / dot(ba, ba), 0.0f, radius) + + length(p2 - ba * h) * sign(p2.y * ba.x - p2.x * ba.y)