From 984e974a9cddb87f576a280bbffc6efbc76f3d9f Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 27 Mar 2025 19:53:04 +0100 Subject: [PATCH 1/9] Add raytraced animation of the EPFL logo and fix inverted boxes issue --- .../cyfra/foton/rt/shapes/Box.scala | 2 +- .../cyfra/samples/foton/RaytraceEPFL.scala | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala diff --git a/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Box.scala b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Box.scala index fa56c8cb..c6fbb657 100644 --- a/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Box.scala +++ b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Box.scala @@ -38,7 +38,7 @@ case class Box( val tEnter = max(tMinX, tMinY, tMinZ) val tExit = min(tMaxX, tMaxY, tMaxZ) - when(tEnter < tExit || tExit < 0.0f) { + when(tEnter > tExit || tExit < 0.0f) { currentHit } otherwise { val hitDistance = when(tEnter > 0f)(tEnter).otherwise(tExit) diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala b/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala new file mode 100644 index 00000000..062d0384 --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala @@ -0,0 +1,91 @@ +package io.computenode.cyfra.samples.foton + +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.foton.animation.AnimationFunctions.smooth +import io.computenode.cyfra.utility.Color.hex +import io.computenode.cyfra.utility.Units.Milliseconds +import io.computenode.cyfra.foton.* +import io.computenode.cyfra.foton.rt.animation.{AnimatedScene, AnimationRtRenderer} +import io.computenode.cyfra.foton.rt.shapes.{Plane, Shape, Sphere, Box} +import io.computenode.cyfra.foton.rt.{Camera, Material} +import scala.concurrent.duration.DurationInt + +import java.nio.file.Paths + +object BoxRaytrace: + @main + def raytraceEPFL() = + val lightMaterial = Material( + color = (1f, 0.3f, 0.3f), + emissive = vec3(40f) + ) + + val floorMaterial = Material( + color = vec3(0.5f), + emissive = vec3(0f), + roughness = 0.9f + ) + val wallMaterial = Material( + color = (0.4f, 0.0f, 0.0f), + emissive = vec3(0f), + percentSpecular = 0f, + specularColor = (1f, 0.3f, 0.3f) * 0.1f, + roughness = 1f + ) + + val x = 0f + val y = -1.5f + val z = 15f + + val staticShapes: List[Shape] = List( + + //E + Box((x + 0f, y + 0f, z + 0f), (x + 1f, y + 5f, z + 1f), wallMaterial), + Box((x + 1f, y + 4f, z + 0f), (x + 3f, y + 5f, z + 1f), wallMaterial), + Box((x + 1f, y + 2f, z + 0f), (x + 3f, y + 3f, z + 1f), wallMaterial), + Box((x + 1f, y + 0f, z + 0f), (x + 3f, y + 1f, z + 1f), wallMaterial), + + //P + Box((x + 4f, y + 0f, z + 0f), (x + 5f, y + 5f, z + 1f), wallMaterial), + Box((x + 5f, y + 0f, z + 0f), (x + 7f, y + 1f, z + 1f), wallMaterial), + Box((x + 7f, y + 0f, z + 0f), (x + 8f, y + 2f, z + 1f), wallMaterial), + Box((x + 5f, y + 2f, z + 0f), (x + 8f, y + 3f, z + 1f), wallMaterial), + + // F + Box((x + 9f, y + 0f, z + 0f), (x + 10f, y + 5f, z + 1f), wallMaterial), + Box((x + 10f, y + 0f, z + 0f), (x + 12f, y + 1f, z + 1f), wallMaterial), + Box((x + 10f, y + 2f, z + 0f), (x + 12f, y + 3f, z + 1f), wallMaterial), + + //L + Box((x + 13f, y + 0f, z + 0f), (x + 14f, y + 5f, z + 1f), wallMaterial), + Box((x + 14f, y + 4f, z + 0f), (x + 16f, y + 5f, z + 1f), wallMaterial), + + // Light + Sphere((0f, -140f, 10f), 50f, lightMaterial), + + // Floor + Plane((0f, 3.5f, 0f), (0f, 1f, 0f), floorMaterial), + ) + + val scene = AnimatedScene( + shapes = staticShapes, + camera = Camera(position = (smooth(from = -20f, to = 40f, duration = 3.seconds), 0f, -1f)), + duration = 3.seconds + ) + + val parameters = AnimationRtRenderer.Parameters( + width = 640, + height = 480, + superFar = 300f, + pixelIterations = 10000, + iterations = 2, + bgColor = hex("#ADD8E6"), + framesPerSecond = 30 + ) + val renderer = AnimationRtRenderer(parameters) + renderer.renderFramesToDir(scene, Paths.get("raytraceEPFL")) + +// Renderable with ffmpeg -framerate 30 -pattern_type sequence -start_number 01 -i frame%02d.png -s:v 1920x1080 -c:v libx264 -crf 17 -pix_fmt yuv420p output.mp4 + +// ffmpeg -t 3 -i output.mp4 -vf "fps=30,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif From 1081e5e94f87f1b51d46082fd768f4c2ea7a938e Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 27 Mar 2025 20:34:10 +0100 Subject: [PATCH 2/9] Add Cylinder shape, orbit animation function and planet animation --- .../foton/animation/AnimationFunctions.scala | 9 ++ .../cyfra/foton/rt/shapes/Cylinder.scala | 85 +++++++++++++++++++ .../foton/rt/shapes/ShapeCollection.scala | 14 +-- .../foton/AnimatedPlanetAroundSun.scala | 78 +++++++++++++++++ 4 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/main/scala/io/computenode/cyfra/foton/rt/shapes/Cylinder.scala create mode 100644 src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala diff --git a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala index 1901a705..e834440c 100644 --- a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala +++ b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala @@ -30,6 +30,15 @@ object AnimationFunctions: .otherwise: to + + def orbit(center: Vec3[Float32], radius: Float32, duration: Milliseconds, at: Milliseconds = Milliseconds(0), + initialAngle: Float32, finalAngle: Float32): AnimationInstant ?=> Vec3[Float32] = + inst ?=> + val angle = smooth(initialAngle, finalAngle, duration, at) + val x = center.x + radius * cos(angle) + val z = center.z + radius * sin(angle) + (x, center.y, z) + // def freefall(from: Float32, to: Float32, g: Float32): Float32 => Vec3[Float32] = // t => // val distance = to - from diff --git a/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Cylinder.scala b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Cylinder.scala new file mode 100644 index 00000000..3a367e04 --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/Cylinder.scala @@ -0,0 +1,85 @@ +package io.computenode.cyfra.foton.rt.shapes + +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Control.* +import io.computenode.cyfra.dsl.Functions.* +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.GStruct +import io.computenode.cyfra.dsl.{*, given} +import io.computenode.cyfra.foton.rt.Material +import io.computenode.cyfra.foton.rt.RtRenderer.{MinRayHitTime, RayHitInfo} + +import java.nio.file.Paths +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits +import scala.concurrent.duration.DurationInt +import scala.concurrent.{Await, ExecutionContext} +case class Cylinder( + center: Vec3[Float32], + radius: Float32, + height: Float32, + material: Material +) extends GStruct[Cylinder] with Shape: + def testRay( + rayPos: Vec3[Float32], + rayDir: Vec3[Float32], + currentHit: RayHitInfo, + ): RayHitInfo = + val capTop = center.y + height/2f + val capBottom = center.y - height/2f + + val ox = rayPos.x - center.x + val oz = rayPos.z - center.z + + val a = rayDir.x * rayDir.x + rayDir.z * rayDir.z + val b = 2f * (ox * rayDir.x + oz * rayDir.z) + val c = ox * ox + oz * oz - radius * radius + val discr = b * b - 4f * a * c + + val sideHit = when(discr >= 0f && !(a === 0f)) { + val sqrtD = sqrt(discr) + val t0 = (-b - sqrtD) / (2f * a) + val t1 = (-b + sqrtD) / (2f * a) + val t = when(t0 > 0f)(t0).elseWhen(t1 > 0f)(t1).otherwise(-1f) + when(t > 0f) { + val hitY = rayPos.y + rayDir.y * t + when(hitY >= capBottom && hitY <= capTop && t < currentHit.dist) { + val hitPoint = rayPos + rayDir * t + val normal = normalize((hitPoint.x - center.x, 0f, hitPoint.z - center.z)) + RayHitInfo(t, normal, material) + } otherwise currentHit + } otherwise currentHit + } otherwise currentHit + + val bottomHit = when(!(rayDir.y === 0f)) { + val t = (capBottom - rayPos.y) / rayDir.y + when(t > 0f && t < currentHit.dist) { + val hitPoint = rayPos + rayDir * t + val dx = hitPoint.x - center.x + val dz = hitPoint.z - center.z + val dist2 = dx * dx + dz * dz + when(dist2 <= radius * radius) { + RayHitInfo(t, (0f, -1f, 0f), material) + } otherwise currentHit + } otherwise currentHit + } otherwise currentHit + + val topHit = when(!(rayDir.y === 0f)) { + val t = (capTop - rayPos.y) / rayDir.y + when(t > 0f && t < currentHit.dist) { + val hitPoint = rayPos + rayDir * t + val dx = hitPoint.x - center.x + val dz = hitPoint.z - center.z + val dist2 = dx * dx + dz * dz + when(dist2 <= radius * radius) { + RayHitInfo(t, (0f, 1f, 0f), material) + } otherwise currentHit + } otherwise currentHit + } otherwise currentHit + + val bestHit = + when(sideHit.dist <= bottomHit.dist && sideHit.dist <= topHit.dist){sideHit} + .elseWhen(bottomHit.dist <= sideHit.dist && bottomHit.dist <= topHit.dist){bottomHit} + .otherwise(topHit) + + when(bestHit.dist < currentHit.dist)(bestHit) otherwise currentHit diff --git a/src/main/scala/io/computenode/cyfra/foton/rt/shapes/ShapeCollection.scala b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/ShapeCollection.scala index 97c35038..419019d9 100644 --- a/src/main/scala/io/computenode/cyfra/foton/rt/shapes/ShapeCollection.scala +++ b/src/main/scala/io/computenode/cyfra/foton/rt/shapes/ShapeCollection.scala @@ -10,13 +10,13 @@ import io.computenode.cyfra.dsl.given import io.computenode.cyfra.dsl.{GSeq, GStruct} import io.computenode.cyfra.foton.rt.RtRenderer.RayHitInfo import izumi.reflect.Tag - import scala.util.chaining.* class ShapeCollection( val boxes: List[Box], val spheres: List[Sphere], val quads: List[Quad], + val cylinders: List[Cylinder], val planes: List[Plane] ) extends Shape: @@ -24,19 +24,22 @@ class ShapeCollection( shapes.collect { case box: Box => box }, shapes.collect { case sphere: Sphere => sphere }, shapes.collect { case quad: Quad => quad }, + shapes.collect { case cylinder: Cylinder => cylinder}, shapes.collect { case plane: Plane => plane } ) def addShape(shape: Shape): ShapeCollection = shape match case box: Box => - ShapeCollection(box :: boxes, spheres, quads, planes) + ShapeCollection(box :: boxes, spheres, quads, cylinders, planes) case sphere: Sphere => - ShapeCollection(boxes, sphere :: spheres, quads, planes) + ShapeCollection(boxes, sphere :: spheres, quads, cylinders,planes) case quad: Quad => - ShapeCollection(boxes, spheres, quad :: quads, planes) + ShapeCollection(boxes, spheres, quad :: quads, cylinders,planes) + case cylinder : Cylinder => + ShapeCollection(boxes, spheres, quads, cylinder :: cylinders,planes) case plane: Plane => - ShapeCollection(boxes, spheres, quads, plane :: planes) + ShapeCollection(boxes, spheres, quads, cylinders, plane :: planes) case _ => assert(false, "Unknown shape type: Broken sealed hierarchy") def testRay(rayPos: Vec3[Float32], rayDir: Vec3[Float32], noHit: RayHitInfo): RayHitInfo = @@ -50,3 +53,4 @@ class ShapeCollection( .pipe(testShapeType(spheres, _)) .pipe(testShapeType(boxes, _)) .pipe(testShapeType(planes, _)) + .pipe(testShapeType(cylinders, _)) \ No newline at end of file diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala new file mode 100644 index 00000000..020eb5d0 --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala @@ -0,0 +1,78 @@ +package io.computenode.cyfra.samples.foton + +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.foton.animation.AnimationFunctions.smooth +import io.computenode.cyfra.utility.Color.hex +import io.computenode.cyfra.utility.Units.Milliseconds +import io.computenode.cyfra.foton.* +import io.computenode.cyfra.foton.rt.animation.{AnimatedScene, AnimationRtRenderer} +import io.computenode.cyfra.foton.rt.shapes.{Plane, Shape, Sphere} +import io.computenode.cyfra.foton.rt.{Camera, Material} +import scala.concurrent.duration.DurationInt + +import java.nio.file.Paths +import io.computenode.cyfra.foton.animation.AnimationFunctions.orbit +import io.computenode.cyfra.foton.rt.shapes.Cylinder + +object Orbit: + @main + def orbitraytrace() = + + val jupiterMaterial = Material( + color = vec3(0.1f, 0.7f, 0.6f), + emissive = vec3(0.02f), + percentSpecular = 0.4f, + specularColor = vec3(0.1f, 0.085f, 0.04f), + roughness = 0.3f + ) + val ringMaterial = Material( + color = vec3(0.6f, 0.6f, 0.7f), + emissive = vec3(0.1f), + percentSpecular = 0.8f, + specularColor = vec3(0.5f), + roughness = 0.1f, + refractionChance = 0.4f, + indexOfRefraction = 1.1f, + refractionRoughness = 0.1f + ) + + val sunMaterial = Material( + color = vec3(1f, 0.2f, 0.05f), + emissive = vec3(4f, 0.4f, 0.1f) + ) + + val lightMaterial = Material( + color = (1f, 0.3f, 0.3f), + emissive = vec3(4f) + ) + + val staticShapes: List[Shape] = List( + Sphere((-1f, 0.5f, 14f),5f,sunMaterial), + Sphere((-140f, -140f, 10f), 50f, lightMaterial), + ) + + val scene = AnimatedScene( + shapes = staticShapes ::: List( + Sphere(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f), 1f, jupiterMaterial), + Cylinder(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) + ), + camera = Camera(position = (0f, 0f, 0f)), + duration = 5.seconds + ) + + val parameters = AnimationRtRenderer.Parameters( + width = 640, + height = 480, + superFar = 300f, + pixelIterations = 500, + iterations = 2, + bgColor = hex("#000000"), + framesPerSecond = 30 + ) + val renderer = AnimationRtRenderer(parameters) + renderer.renderFramesToDir(scene, Paths.get("Orbit")) + +// Renderable with ffmpeg -framerate 30 -pattern_type sequence -start_number 01 -i frame%02d.png -s:v 1920x1080 -c:v libx264 -crf 17 -pix_fmt yuv420p output.mp4 + +// ffmpeg -t 3 -i output.mp4 -vf "fps=30,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif From 12186e1882c3b769ccfe912777f233edf9e9f1b7 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 27 Mar 2025 21:06:18 +0100 Subject: [PATCH 3/9] introduce color change with time --- .../foton/animation/AnimationFunctions.scala | 11 +++++++++-- .../samples/foton/AnimatedPlanetAroundSun.scala | 15 ++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala index e834440c..dd96853d 100644 --- a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala +++ b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala @@ -30,7 +30,14 @@ object AnimationFunctions: .otherwise: to - + def colorChange(color1: Vec3[Float32], color2: Vec3[Float32], changeRate: Float32): AnimationInstant ?=> Vec3[Float32] = + inst ?=> + val clampedTime = inst.time.mod(changeRate) + when(clampedTime < changeRate/2f){ + color1 + } otherwise { + color2 + } def orbit(center: Vec3[Float32], radius: Float32, duration: Milliseconds, at: Milliseconds = Milliseconds(0), initialAngle: Float32, finalAngle: Float32): AnimationInstant ?=> Vec3[Float32] = inst ?=> @@ -38,7 +45,7 @@ object AnimationFunctions: val x = center.x + radius * cos(angle) val z = center.z + radius * sin(angle) (x, center.y, z) - + // def freefall(from: Float32, to: Float32, g: Float32): Float32 => Vec3[Float32] = // t => // val distance = to - from diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala index 020eb5d0..0aee0173 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala @@ -12,8 +12,9 @@ import io.computenode.cyfra.foton.rt.{Camera, Material} import scala.concurrent.duration.DurationInt import java.nio.file.Paths -import io.computenode.cyfra.foton.animation.AnimationFunctions.orbit +import io.computenode.cyfra.foton.animation.AnimationFunctions.* import io.computenode.cyfra.foton.rt.shapes.Cylinder +import io.computenode.cyfra.dsl.Control.when object Orbit: @main @@ -41,23 +42,27 @@ object Orbit: color = vec3(1f, 0.2f, 0.05f), emissive = vec3(4f, 0.4f, 0.1f) ) + val sunMaterial1Color1 = vec3(1f, 0.2f, 0.05f) + val sunMaterial1Color2 = vec3(0f, 0f, 1f) val lightMaterial = Material( color = (1f, 0.3f, 0.3f), emissive = vec3(4f) ) + val staticShapes: List[Shape] = List( - Sphere((-1f, 0.5f, 14f),5f,sunMaterial), - Sphere((-140f, -140f, 10f), 50f, lightMaterial), + + Sphere((0f, -140f, 10f), 50f, lightMaterial), ) val scene = AnimatedScene( shapes = staticShapes ::: List( Sphere(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f), 1f, jupiterMaterial), + Sphere((-1f, 0.5f, 14f),5f, Material(colorChange(sunMaterial1Color1, sunMaterial1Color2, 4f), emissive = vec3(0f, 0.4f, 0.1f))), Cylinder(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) ), - camera = Camera(position = (0f, 0f, 0f)), + camera = Camera(position = (0f, 0f, -50f)), duration = 5.seconds ) @@ -68,7 +73,7 @@ object Orbit: pixelIterations = 500, iterations = 2, bgColor = hex("#000000"), - framesPerSecond = 30 + framesPerSecond = 60 ) val renderer = AnimationRtRenderer(parameters) renderer.renderFramesToDir(scene, Paths.get("Orbit")) From 819e2f44ca7370ce59deacc647ad83eb56c01678 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 27 Mar 2025 21:35:00 +0100 Subject: [PATCH 4/9] improve color change function --- .../foton/animation/AnimationFunctions.scala | 16 +++++++++------- .../samples/foton/AnimatedPlanetAroundSun.scala | 16 ++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala index dd96853d..1e22b874 100644 --- a/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala +++ b/src/main/scala/io/computenode/cyfra/foton/animation/AnimationFunctions.scala @@ -31,13 +31,15 @@ object AnimationFunctions: to def colorChange(color1: Vec3[Float32], color2: Vec3[Float32], changeRate: Float32): AnimationInstant ?=> Vec3[Float32] = - inst ?=> - val clampedTime = inst.time.mod(changeRate) - when(clampedTime < changeRate/2f){ - color1 - } otherwise { - color2 - } + inst ?=> + val time = inst.time.mod(changeRate * 2f) + val t = when(time < changeRate) { + time / changeRate + } otherwise { + (2f * changeRate - time) / changeRate + } + color1 * (1f - t) + color2 * t + def orbit(center: Vec3[Float32], radius: Float32, duration: Milliseconds, at: Milliseconds = Milliseconds(0), initialAngle: Float32, finalAngle: Float32): AnimationInstant ?=> Vec3[Float32] = inst ?=> diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala index 0aee0173..e054fd39 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala @@ -42,8 +42,8 @@ object Orbit: color = vec3(1f, 0.2f, 0.05f), emissive = vec3(4f, 0.4f, 0.1f) ) - val sunMaterial1Color1 = vec3(1f, 0.2f, 0.05f) - val sunMaterial1Color2 = vec3(0f, 0f, 1f) + val sunMaterial1Color1 = vec3(1.0f, 0.5f, 0.0f) + val sunMaterial1Color2 = vec3(1f, 0f, 0.0f) val lightMaterial = Material( color = (1f, 0.3f, 0.3f), @@ -58,11 +58,11 @@ object Orbit: val scene = AnimatedScene( shapes = staticShapes ::: List( - Sphere(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f), 1f, jupiterMaterial), - Sphere((-1f, 0.5f, 14f),5f, Material(colorChange(sunMaterial1Color1, sunMaterial1Color2, 4f), emissive = vec3(0f, 0.4f, 0.1f))), - Cylinder(orbit((0f, 1f, 14f), 8f, 60.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) + Sphere(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f), 1f, jupiterMaterial), + Sphere((-1f, 0.5f, 14f),5f, Material(colorChange(sunMaterial1Color1, sunMaterial1Color2, 900f), emissive = vec3(0.6f, 0.1f, 0.1f))), + Cylinder(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) ), - camera = Camera(position = (0f, 0f, -50f)), + camera = Camera(position = (0f, 0f, -10f)), duration = 5.seconds ) @@ -70,10 +70,10 @@ object Orbit: width = 640, height = 480, superFar = 300f, - pixelIterations = 500, + pixelIterations = 1000, iterations = 2, bgColor = hex("#000000"), - framesPerSecond = 60 + framesPerSecond = 30 ) val renderer = AnimationRtRenderer(parameters) renderer.renderFramesToDir(scene, Paths.get("Orbit")) From 0245a160200b4459fc365f9ad93bfabc895cb769 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 27 Mar 2025 21:41:15 +0100 Subject: [PATCH 5/9] various renaming in AnimatedPlanetAroundSun --- .../foton/AnimatedPlanetAroundSun.scala | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala index e054fd39..a14c842f 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala @@ -16,17 +16,18 @@ import io.computenode.cyfra.foton.animation.AnimationFunctions.* import io.computenode.cyfra.foton.rt.shapes.Cylinder import io.computenode.cyfra.dsl.Control.when -object Orbit: +object AnimatedPlanetAroundSun: @main - def orbitraytrace() = + def planetRaytrace() = - val jupiterMaterial = Material( + val alienPlanetMaterial = Material( color = vec3(0.1f, 0.7f, 0.6f), emissive = vec3(0.02f), percentSpecular = 0.4f, specularColor = vec3(0.1f, 0.085f, 0.04f), roughness = 0.3f ) + val ringMaterial = Material( color = vec3(0.6f, 0.6f, 0.7f), emissive = vec3(0.1f), @@ -38,10 +39,6 @@ object Orbit: refractionRoughness = 0.1f ) - val sunMaterial = Material( - color = vec3(1f, 0.2f, 0.05f), - emissive = vec3(4f, 0.4f, 0.1f) - ) val sunMaterial1Color1 = vec3(1.0f, 0.5f, 0.0f) val sunMaterial1Color2 = vec3(1f, 0f, 0.0f) @@ -52,13 +49,12 @@ object Orbit: val staticShapes: List[Shape] = List( - Sphere((0f, -140f, 10f), 50f, lightMaterial), ) val scene = AnimatedScene( shapes = staticShapes ::: List( - Sphere(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f), 1f, jupiterMaterial), + Sphere(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f), 1f, alienPlanetMaterial), Sphere((-1f, 0.5f, 14f),5f, Material(colorChange(sunMaterial1Color1, sunMaterial1Color2, 900f), emissive = vec3(0.6f, 0.1f, 0.1f))), Cylinder(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) ), @@ -70,13 +66,13 @@ object Orbit: width = 640, height = 480, superFar = 300f, - pixelIterations = 1000, + pixelIterations = 10000, iterations = 2, bgColor = hex("#000000"), framesPerSecond = 30 ) val renderer = AnimationRtRenderer(parameters) - renderer.renderFramesToDir(scene, Paths.get("Orbit")) + renderer.renderFramesToDir(scene, Paths.get("raytracePlanetAndSun")) // Renderable with ffmpeg -framerate 30 -pattern_type sequence -start_number 01 -i frame%02d.png -s:v 1920x1080 -c:v libx264 -crf 17 -pix_fmt yuv420p output.mp4 From 6407b376a83e7c285f14404231151bd9e3353321 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 28 Mar 2025 16:39:29 +0100 Subject: [PATCH 6/9] Add a wave, a spiral and a fractal animation --- .../foton/AnimatedConnectingForms.scala | 55 +++++++++++++++++++ .../cyfra/samples/foton/AnimatedSpiral.scala | 40 ++++++++++++++ .../samples/foton/AnimatedWaveCircle.scala | 43 +++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/main/scala/io/computenode/cyfra/samples/foton/AnimatedConnectingForms.scala create mode 100644 src/main/scala/io/computenode/cyfra/samples/foton/AnimatedSpiral.scala create mode 100644 src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedConnectingForms.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedConnectingForms.scala new file mode 100644 index 00000000..2ae2335d --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedConnectingForms.scala @@ -0,0 +1,55 @@ +package io.computenode.cyfra.samples.foton + +import io.computenode.cyfra +import io.computenode.cyfra.* +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Functions.* +import io.computenode.cyfra.dsl.GSeq +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.foton.animation.AnimatedFunctionRenderer.Parameters +import io.computenode.cyfra.foton.animation.AnimationFunctions.* +import io.computenode.cyfra.foton.animation.{AnimatedFunction, AnimatedFunctionRenderer} +import io.computenode.cyfra.utility.Color.* +import io.computenode.cyfra.utility.Math3D.* + +import scala.concurrent.duration.DurationInt +import java.nio.file.Paths +import io.computenode.cyfra.dsl.Control.when + +object AnimatedConnectingForms: + @main + def AnimatedConnectingForms2() = + + def connectingForms(uv: Vec2[Float32])(using AnimationInstant): Int32 = + val p1 = smooth(from = 0.355f, to = 0.4f, duration = 10.seconds) + val p2 = smooth(from = 0.4f, to = 0.355f, duration = 10.seconds, at = 10.seconds) + val const1 = (p1, p1) + val const2 = (p2, p2) + GSeq.gen(uv, next = v => { + when(!(p1 === 0.4f)){ + val abs_v = (abs(v.x), abs(v.y)) + ((abs_v.x * abs_v.x) - (abs_v.y * abs_v.y), 2.0f * abs_v.x * abs_v.y) + const1 + }otherwise{ + val abs_v = (abs(v.x), abs(v.y)) + ((abs_v.x * abs_v.x) - (abs_v.y * abs_v.y), 2.0f * abs_v.x * abs_v.y) + const2 + } + + }).limit(1000).map(length).takeWhile(_ < 2.0f).count + + def connectingFormsColor(uv: Vec2[Float32])(using AnimationInstant): Vec4[Float32] = + val rotatedUv = rotate(uv, Math.PI.toFloat / 3.0f) + val recursionCount = connectingForms(rotatedUv) + val f = min(1f, recursionCount.asFloat / 100f) + val color = interpolate(InterpolationThemes.Red, f) + ( + color.r, + color.g, + color.b, + 1.0f + ) + + val animatedConnectingForms = AnimatedFunction.fromCoord(connectingFormsColor, 20.seconds) + + val renderer = AnimatedFunctionRenderer(Parameters(1024, 1024, 5)) + renderer.renderFramesToDir(animatedConnectingForms, Paths.get("connectingForms")) + diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedSpiral.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedSpiral.scala new file mode 100644 index 00000000..2f1ba172 --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedSpiral.scala @@ -0,0 +1,40 @@ +package io.computenode.cyfra.samples.foton + +import io.computenode.cyfra +import io.computenode.cyfra.* +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Functions.* +import io.computenode.cyfra.dsl.GSeq +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.foton.animation.AnimatedFunctionRenderer.Parameters +import io.computenode.cyfra.foton.animation.AnimationFunctions.* +import io.computenode.cyfra.foton.animation.{AnimatedFunction, AnimatedFunctionRenderer} +import io.computenode.cyfra.utility.Color.* +import io.computenode.cyfra.utility.Math3D.* + +import scala.concurrent.duration.DurationInt +import java.nio.file.Paths +import io.computenode.cyfra.dsl.Control.when + +object AnimatedSpiral: + @main + def Spiral() = + def spiral(uv: Vec2[Float32])(using a: AnimationInstant): Vec4[Float32] = + val centered = uv + val angle = atan2(centered.y, centered.x) + val radius = length(centered) + val f = abs(sin(10f * radius + 5f * angle + a.time)) + val color = interpolate(InterpolationThemes.Black, f) + val rotatedUv = rotate(uv, Math.PI.toFloat / 3.0f) + ( + color.r, + color.g, + color.b, + 1.0f + ) + + val animatedSpiral = AnimatedFunction.fromCoord(spiral, 2.seconds) + + val renderer = AnimatedFunctionRenderer(Parameters(1024, 1024, 80)) + renderer.renderFramesToDir(animatedSpiral, Paths.get("animatedSpiral")) + diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala new file mode 100644 index 00000000..e24e7af5 --- /dev/null +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala @@ -0,0 +1,43 @@ +package io.computenode.cyfra.samples.foton + +import io.computenode.cyfra +import io.computenode.cyfra.* +import io.computenode.cyfra.dsl.Algebra.{*, given} +import io.computenode.cyfra.dsl.Functions.* +import io.computenode.cyfra.dsl.GSeq +import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.foton.animation.AnimatedFunctionRenderer.Parameters +import io.computenode.cyfra.foton.animation.AnimationFunctions.* +import io.computenode.cyfra.foton.animation.{AnimatedFunction, AnimatedFunctionRenderer} +import io.computenode.cyfra.utility.Color.* +import io.computenode.cyfra.utility.Math3D.* + +import scala.concurrent.duration.DurationInt +import java.nio.file.Paths +import io.computenode.cyfra.dsl.Control.when + +//This pattern was adapted from https://www.shadertoy.com/view/XsXXDn by Silexars + +object AnimatedWaveCircle: + @main + def animatedWaveCircle() = + def waveColor(uv: Vec2[Float32])(using t: AnimationInstant): Vec4[Float32] = + val aspect = 840f / 473f + val p = (uv.x * aspect, uv.y) + var c = Array[Float32](0f, 0f, 0f) + val l = length(p) + var z = t.time + var uvCompute = p + + for i <- 0 until 3 do + z += 0.07f * (i + 1).toFloat + val multiplyFactor = (sin(z) + 1f) * abs(sin(l * 9f - 2f * z)) + uvCompute = (uvCompute.y +(p.x * (1f/l)) * multiplyFactor, uvCompute.x + (p.y * (1f/l)) * multiplyFactor) + val uvMod = (uvCompute.x.mod(1f), uvCompute.y.mod(1f)) + c(i) = 0.01f / length((uvMod._1 - 0.5f, uvMod._2 - 0.5f)) + + (c(0) / l, c(1) / l, c(2) / l, t.time) + + val animatedStar = AnimatedFunction.fromCoord(waveColor, 30.milliseconds) + val renderer = AnimatedFunctionRenderer(Parameters(840, 473, 30000)) + renderer.renderFramesToDir(animatedStar, Paths.get("animatedWaveCircle")) \ No newline at end of file From daa3c34134fb5496692082969d15b19ec60773ef Mon Sep 17 00:00:00 2001 From: MatthieuSLR9 <160244482+MatthieuSLR9@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:19:28 +0200 Subject: [PATCH 7/9] Rename RaytraceEPFL.scala to AnimatedRaytraceEPFL.scala --- .../foton/{RaytraceEPFL.scala => AnimatedRaytraceEPFL.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/scala/io/computenode/cyfra/samples/foton/{RaytraceEPFL.scala => AnimatedRaytraceEPFL.scala} (100%) diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedRaytraceEPFL.scala similarity index 100% rename from src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala rename to src/main/scala/io/computenode/cyfra/samples/foton/AnimatedRaytraceEPFL.scala From 72a1b19a7778dfd1d20dc9568e8275d11bdb1587 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 31 Mar 2025 17:35:07 +0200 Subject: [PATCH 8/9] change the parameters of the renderer for nicer animations --- .../cyfra/samples/foton/AnimatedPlanetAroundSun.scala | 8 ++++---- .../{RaytraceEPFL.scala => AnimatedRaytraceEPFL.scala} | 4 ++-- .../cyfra/samples/foton/AnimatedWaveCircle.scala | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/main/scala/io/computenode/cyfra/samples/foton/{RaytraceEPFL.scala => AnimatedRaytraceEPFL.scala} (98%) diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala index a14c842f..0b8a6d69 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedPlanetAroundSun.scala @@ -54,12 +54,12 @@ object AnimatedPlanetAroundSun: val scene = AnimatedScene( shapes = staticShapes ::: List( - Sphere(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f), 1f, alienPlanetMaterial), + Sphere(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 90f, 390f), 1f, alienPlanetMaterial), Sphere((-1f, 0.5f, 14f),5f, Material(colorChange(sunMaterial1Color1, sunMaterial1Color2, 900f), emissive = vec3(0.6f, 0.1f, 0.1f))), - Cylinder(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 0f, 300f),2f, 0f, ringMaterial) + Cylinder(orbit((0f, 1f, 14f), 8f, 90.seconds, 0.millis, 90f, 390f),2f, 0f, ringMaterial) ), camera = Camera(position = (0f, 0f, -10f)), - duration = 5.seconds + duration = 1990.milliseconds ) val parameters = AnimationRtRenderer.Parameters( @@ -69,7 +69,7 @@ object AnimatedPlanetAroundSun: pixelIterations = 10000, iterations = 2, bgColor = hex("#000000"), - framesPerSecond = 30 + framesPerSecond = 60 ) val renderer = AnimationRtRenderer(parameters) renderer.renderFramesToDir(scene, Paths.get("raytracePlanetAndSun")) diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedRaytraceEPFL.scala similarity index 98% rename from src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala rename to src/main/scala/io/computenode/cyfra/samples/foton/AnimatedRaytraceEPFL.scala index 062d0384..c00f822b 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/RaytraceEPFL.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedRaytraceEPFL.scala @@ -70,8 +70,8 @@ object BoxRaytrace: val scene = AnimatedScene( shapes = staticShapes, - camera = Camera(position = (smooth(from = -20f, to = 40f, duration = 3.seconds), 0f, -1f)), - duration = 3.seconds + camera = Camera(position = (smooth(from = -20f, to = 40f, duration = 6.seconds), 0f, -1f)), + duration = 6.seconds ) val parameters = AnimationRtRenderer.Parameters( diff --git a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala index e24e7af5..21860afa 100644 --- a/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala +++ b/src/main/scala/io/computenode/cyfra/samples/foton/AnimatedWaveCircle.scala @@ -38,6 +38,6 @@ object AnimatedWaveCircle: (c(0) / l, c(1) / l, c(2) / l, t.time) - val animatedStar = AnimatedFunction.fromCoord(waveColor, 30.milliseconds) + val animatedStar = AnimatedFunction.fromCoord(waveColor, 8.milliseconds) val renderer = AnimatedFunctionRenderer(Parameters(840, 473, 30000)) renderer.renderFramesToDir(animatedStar, Paths.get("animatedWaveCircle")) \ No newline at end of file From a35b14c1037bd64651c52e252da9aedde4f0fb6c Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 31 Mar 2025 17:43:39 +0200 Subject: [PATCH 9/9] Add new InterpolationTheme --- src/main/scala/io/computenode/cyfra/utility/Color.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/io/computenode/cyfra/utility/Color.scala b/src/main/scala/io/computenode/cyfra/utility/Color.scala index e8e99a4a..e41b26a6 100644 --- a/src/main/scala/io/computenode/cyfra/utility/Color.scala +++ b/src/main/scala/io/computenode/cyfra/utility/Color.scala @@ -43,6 +43,8 @@ object Color: object InterpolationThemes: val Blue: InterpolationTheme = ((8f, 22f, 104f) * (1 / 255f), (62f, 82f, 199f) * (1 / 255f), (221f, 233f, 255f) * (1 / 255f)) val Black: InterpolationTheme = ((255f, 255f, 255f) * (1 / 255f), (0f, 0f, 0f), (0f, 0f, 0f)) + val Red: InterpolationTheme = ((104f, 22f, 8f) * (1 / 255f),(199f, 82f, 62f) * (1 / 255f),(255f, 233f, 221f) * (1 / 255f)) + def interpolate(theme: InterpolationTheme, f: Float32): Vec3[Float32] = val (c1, c2, c3) = theme