diff --git a/src/main/kotlin/Hit.kt b/src/main/kotlin/Hit.kt new file mode 100644 index 0000000..a97525c --- /dev/null +++ b/src/main/kotlin/Hit.kt @@ -0,0 +1 @@ +data class Hit(val point: Point, val normal: Vector, val distance: Float, val material: Material) \ No newline at end of file diff --git a/src/main/kotlin/Material.kt b/src/main/kotlin/Material.kt new file mode 100644 index 0000000..1de21fb --- /dev/null +++ b/src/main/kotlin/Material.kt @@ -0,0 +1,3 @@ +interface Material { + fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor +} \ No newline at end of file diff --git a/src/main/kotlin/MaterialColor.kt b/src/main/kotlin/MaterialColor.kt new file mode 100644 index 0000000..2aac534 --- /dev/null +++ b/src/main/kotlin/MaterialColor.kt @@ -0,0 +1,14 @@ +data class MaterialColor(val r: Float, val g: Float, val b: Float) { + fun toColor(): Color = Color( + (r * UByte.MAX_VALUE.toFloat()).toInt().toUByte(), + (g * UByte.MAX_VALUE.toFloat()).toInt().toUByte(), + (b * UByte.MAX_VALUE.toFloat()).toInt().toUByte() + ) + + operator fun times(rhs: MaterialColor) = MaterialColor(r * rhs.r, g * rhs.g, b * rhs.b) + operator fun plus(rhs: MaterialColor) = MaterialColor(r + rhs.r, g + rhs.g, b + rhs.b) + + companion object { + val Black: MaterialColor = MaterialColor(0f, 0f, 0f) + } +} \ No newline at end of file diff --git a/src/main/kotlin/Plane.kt b/src/main/kotlin/Plane.kt new file mode 100644 index 0000000..91ef3f7 --- /dev/null +++ b/src/main/kotlin/Plane.kt @@ -0,0 +1,9 @@ +data class Plane(val point: Point, val normal: Vector, val material: Material) : Thing { + override fun intersects(ray: Ray): Hit? { + val bottom = dot(normal, ray.direction) + if (bottom == 0.0f) return null + val t = dot(normal, point - ray.origin) / bottom + if (t <= 0.0f) return null + return Hit(ray.at(t), normal, t, material) + } +} \ No newline at end of file diff --git a/src/main/kotlin/PointLight.kt b/src/main/kotlin/PointLight.kt new file mode 100644 index 0000000..08569bd --- /dev/null +++ b/src/main/kotlin/PointLight.kt @@ -0,0 +1 @@ +data class PointLight(val point: Point, val color: MaterialColor) \ No newline at end of file diff --git a/src/main/kotlin/Ray.kt b/src/main/kotlin/Ray.kt index ff45f50..c218d62 100644 --- a/src/main/kotlin/Ray.kt +++ b/src/main/kotlin/Ray.kt @@ -1,25 +1,4 @@ import java.io.File -import java.lang.Float.max -import kotlin.math.min -import kotlin.math.pow -import kotlin.math.sqrt - -data class Hit(val point: Point, val normal: Vector, val distance: Float, val material: Material) - -data class MaterialColor(val r: Float, val g: Float, val b: Float) { - fun toColor(): Color = Color( - (r * UByte.MAX_VALUE.toFloat()).toInt().toUByte(), - (g * UByte.MAX_VALUE.toFloat()).toInt().toUByte(), - (b * UByte.MAX_VALUE.toFloat()).toInt().toUByte() - ) - - operator fun times(rhs: MaterialColor) = MaterialColor(r * rhs.r, g * rhs.g, b * rhs.b) - operator fun plus(rhs: MaterialColor) = MaterialColor(r + rhs.r, g + rhs.g, b + rhs.b) - - companion object { - val Black: MaterialColor = MaterialColor(0f, 0f, 0f) - } -} operator fun Float.times(rhs: MaterialColor) = MaterialColor(this * rhs.r, this * rhs.g, this * rhs.b) @@ -27,125 +6,9 @@ data class Ray(val origin: Point, val direction: Vector) { fun at(t: Float) = origin + t * direction } -interface Thing { - fun intersects(ray: Ray): Hit? -} - -interface Material { - fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor -} - -data class WhateverMaterial(val color: MaterialColor, - val ambient: MaterialColor = MaterialColor(0.1f, 0.1f, 0.1f), - val specular: Float = 100f -): Material { - override fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor { - var color = this.color * ambient - val light = scene.light - val rayToLight = (hit.point + 0.0001f * hit.normal).rayTo(light.point) - if (scene.intersects(rayToLight) == null) { - - val lambert = max(0f, dot(hit.normal, rayToLight.direction)) - color += lambert * light.color - - val h = (-ray.direction + rayToLight.direction).normalized() - - val intensity = dot(hit.normal, h).pow(specular) - - color += intensity * MaterialColor(0.3f, 0f, 0f) - } - - return color - } -} - -data class ReflectiveMaterial( - val reflectivity: Float, - val color: MaterialColor, - val ambient: MaterialColor = MaterialColor(0.1f, 0.1f, 0.1f), - val specular: Float = 10f -): Material { - override fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor { - var color = this.color * ambient - val light = scene.light - val offsetPoint = hit.point + 0.0001f * hit.normal - val rayToLight = offsetPoint.rayTo(light.point) - if (scene.intersects(rayToLight) == null) { - - val lambert = max(0f, dot(hit.normal, rayToLight.direction)) - color += lambert * light.color - - val h = (-ray.direction + rayToLight.direction).normalized() - - val intensity = (hit.normal dot h).pow(specular) - - color += intensity * MaterialColor(0.3f, 0f, 0f) - } - - val reflectionDirection = ray.direction - 2 * (ray.direction dot hit.normal) * hit.normal - val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection)) ?: MaterialColor.Black - - return (1.0f - reflectivity) * color + reflectivity * reflectedColor - } -} - - -data class Plane(val point: Point, val normal: Vector, val material: Material) : Thing { - override fun intersects(ray: Ray): Hit? { - val bottom = dot(normal, ray.direction) - if (bottom == 0.0f) return null - val t = dot(normal, point - ray.origin) / bottom - if (t <= 0.0f) return null - return Hit(ray.at(t), normal, t, material) - } -} - -data class Sphere(val center: Point, val radius: Float, val material: Material) : Thing { - override fun intersects(ray: Ray): Hit? { - val l = center - ray.origin - val tc = dot(l, ray.direction) - if (tc < 0f) return null - - val d = l.squaredLength - tc * tc - val radiusSquared = radius * radius - - if (d > radiusSquared) return null - - val thc = sqrt(radiusSquared - d) - - val t = min(tc - thc, tc + thc) - - val hitPoint = ray.at(t) - return Hit(hitPoint, (hitPoint - center).normalized(), t, material) - } -} fun dot(lhs: Vector, rhs: Vector): Float = lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z -data class PointLight(val point: Point, val color: MaterialColor) - -data class Scene(val things: List, val light: PointLight): Thing { - override fun intersects(ray: Ray): Hit? { - var closest: Hit? = null - for (thing in things) { - val hit = thing.intersects(ray) - if (hit != null && (closest == null || hit.distance < closest.distance)) { - closest = hit - } - } - - return closest - } - - fun colorForRay(ray: Ray): MaterialColor? { - val hit = intersects(ray) - if (hit != null) { - return hit.material.shade(ray, hit, this) - } - return null - } -} - fun main() { val bmp = Bitmap(1000, 1000) val origin = Point(500f, 500f, -500f) diff --git a/src/main/kotlin/ReflectiveMaterial.kt b/src/main/kotlin/ReflectiveMaterial.kt new file mode 100644 index 0000000..d0cdf58 --- /dev/null +++ b/src/main/kotlin/ReflectiveMaterial.kt @@ -0,0 +1,31 @@ +import kotlin.math.pow + +data class ReflectiveMaterial( + val reflectivity: Float, + val color: MaterialColor, + val ambient: MaterialColor = MaterialColor(0.1f, 0.1f, 0.1f), + val specular: Float = 10f +): Material { + override fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor { + var color = this.color * ambient + val light = scene.light + val offsetPoint = hit.point + 0.0001f * hit.normal + val rayToLight = offsetPoint.rayTo(light.point) + if (scene.intersects(rayToLight) == null) { + + val lambert = java.lang.Float.max(0f, dot(hit.normal, rayToLight.direction)) + color += lambert * light.color + + val h = (-ray.direction + rayToLight.direction).normalized() + + val intensity = (hit.normal dot h).pow(specular) + + color += intensity * MaterialColor(0.3f, 0f, 0f) + } + + val reflectionDirection = ray.direction - 2 * (ray.direction dot hit.normal) * hit.normal + val reflectedColor = scene.colorForRay(Ray(offsetPoint, reflectionDirection)) ?: MaterialColor.Black + + return (1.0f - reflectivity) * color + reflectivity * reflectedColor + } +} \ No newline at end of file diff --git a/src/main/kotlin/Scene.kt b/src/main/kotlin/Scene.kt new file mode 100644 index 0000000..3acab8e --- /dev/null +++ b/src/main/kotlin/Scene.kt @@ -0,0 +1,21 @@ +data class Scene(val things: List, val light: PointLight): Thing { + override fun intersects(ray: Ray): Hit? { + var closest: Hit? = null + for (thing in things) { + val hit = thing.intersects(ray) + if (hit != null && (closest == null || hit.distance < closest.distance)) { + closest = hit + } + } + + return closest + } + + fun colorForRay(ray: Ray): MaterialColor? { + val hit = intersects(ray) + if (hit != null) { + return hit.material.shade(ray, hit, this) + } + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/Sphere.kt b/src/main/kotlin/Sphere.kt new file mode 100644 index 0000000..58ab018 --- /dev/null +++ b/src/main/kotlin/Sphere.kt @@ -0,0 +1,22 @@ +import kotlin.math.min +import kotlin.math.sqrt + +data class Sphere(val center: Point, val radius: Float, val material: Material) : Thing { + override fun intersects(ray: Ray): Hit? { + val l = center - ray.origin + val tc = dot(l, ray.direction) + if (tc < 0f) return null + + val d = l.squaredLength - tc * tc + val radiusSquared = radius * radius + + if (d > radiusSquared) return null + + val thc = sqrt(radiusSquared - d) + + val t = min(tc - thc, tc + thc) + + val hitPoint = ray.at(t) + return Hit(hitPoint, (hitPoint - center).normalized(), t, material) + } +} \ No newline at end of file diff --git a/src/main/kotlin/Thing.kt b/src/main/kotlin/Thing.kt new file mode 100644 index 0000000..62fc0f2 --- /dev/null +++ b/src/main/kotlin/Thing.kt @@ -0,0 +1,3 @@ +interface Thing { + fun intersects(ray: Ray): Hit? +} \ No newline at end of file diff --git a/src/main/kotlin/WhateverMaterial.kt b/src/main/kotlin/WhateverMaterial.kt new file mode 100644 index 0000000..1d8aeca --- /dev/null +++ b/src/main/kotlin/WhateverMaterial.kt @@ -0,0 +1,25 @@ +import kotlin.math.pow + +data class WhateverMaterial(val color: MaterialColor, + val ambient: MaterialColor = MaterialColor(0.1f, 0.1f, 0.1f), + val specular: Float = 100f +): Material { + override fun shade(ray: Ray, hit: Hit, scene: Scene): MaterialColor { + var color = this.color * ambient + val light = scene.light + val rayToLight = (hit.point + 0.0001f * hit.normal).rayTo(light.point) + if (scene.intersects(rayToLight) == null) { + + val lambert = java.lang.Float.max(0f, dot(hit.normal, rayToLight.direction)) + color += lambert * light.color + + val h = (-ray.direction + rayToLight.direction).normalized() + + val intensity = dot(hit.normal, h).pow(specular) + + color += intensity * MaterialColor(0.3f, 0f, 0f) + } + + return color + } +} \ No newline at end of file