Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ object AnimationFunctions:
.otherwise:
to

def colorChange(color1: Vec3[Float32], color2: Vec3[Float32], changeRate: Float32): AnimationInstant ?=> Vec3[Float32] =
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 ?=>
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions src/main/scala/io/computenode/cyfra/foton/rt/shapes/Cylinder.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,36 @@ 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:

def this(shapes: List[Shape]) = this(
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 =
Expand All @@ -50,3 +53,4 @@ class ShapeCollection(
.pipe(testShapeType(spheres, _))
.pipe(testShapeType(boxes, _))
.pipe(testShapeType(planes, _))
.pipe(testShapeType(cylinders, _))
Original file line number Diff line number Diff line change
@@ -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"))

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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.*
import io.computenode.cyfra.foton.rt.shapes.Cylinder
import io.computenode.cyfra.dsl.Control.when

object AnimatedPlanetAroundSun:
@main
def planetRaytrace() =

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),
percentSpecular = 0.8f,
specularColor = vec3(0.5f),
roughness = 0.1f,
refractionChance = 0.4f,
indexOfRefraction = 1.1f,
refractionRoughness = 0.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),
emissive = vec3(4f)
)


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, 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, 90f, 390f),2f, 0f, ringMaterial)
),
camera = Camera(position = (0f, 0f, -10f)),
duration = 1990.milliseconds
)

val parameters = AnimationRtRenderer.Parameters(
width = 640,
height = 480,
superFar = 300f,
pixelIterations = 10000,
iterations = 2,
bgColor = hex("#000000"),
framesPerSecond = 60
)
val renderer = AnimationRtRenderer(parameters)
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

// 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
Original file line number Diff line number Diff line change
@@ -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 = 6.seconds), 0f, -1f)),
duration = 6.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
Loading