From 3d3d60695a0e30ffca60aa1cd1f1c881d62fbbcc Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Wed, 18 Jan 2023 04:05:21 -0500 Subject: [PATCH 1/9] Chapter 14: WIP. --- src/main/kotlin/material/material.kt | 58 ++++++++++------------- src/main/kotlin/shapes/cone.kt | 8 +++- src/main/kotlin/shapes/cube.kt | 10 +++- src/main/kotlin/shapes/cylinder.kt | 8 +++- src/main/kotlin/shapes/group.kt | 32 +++++++++++++ src/main/kotlin/shapes/plane.kt | 10 +++- src/main/kotlin/shapes/shape.kt | 34 ++++++-------- src/main/kotlin/shapes/sphere.kt | 10 +++- src/test/kotlin/shapes/grouptest.kt | 70 ++++++++++++++++++++++++++++ src/test/kotlin/shapes/shapetest.kt | 36 +++++++++++++- 10 files changed, 211 insertions(+), 65 deletions(-) create mode 100644 src/main/kotlin/shapes/group.kt create mode 100644 src/test/kotlin/shapes/grouptest.kt diff --git a/src/main/kotlin/material/material.kt b/src/main/kotlin/material/material.kt index e19391c..7a1589b 100644 --- a/src/main/kotlin/material/material.kt +++ b/src/main/kotlin/material/material.kt @@ -11,25 +11,34 @@ import pattern.SolidPattern import shapes.Shape import kotlin.math.pow -data class Material(val pattern: Pattern = SolidPattern(Color.WHITE), - val ambient: Double = DEFAULT_AMBIENT, - val diffuse: Double = DEFAULT_DIFFUSE, - val specular: Double = DEFAULT_SPECULAR, - val shininess: Double = DEFAULT_SHININESS, - val reflectivity: Double = DEFAULT_REFLECTIVITY, - val transparency: Double = DEFAULT_TRANSPARENCY, - val refractiveIndex: Double = DEFAULT_REFRACTIVE_INDEX) { +class Material(val pattern: Pattern = SolidPattern(Color.WHITE), + ambient: Number = DEFAULT_AMBIENT, + diffuse: Number = DEFAULT_DIFFUSE, + specular: Number = DEFAULT_SPECULAR, + shininess: Number = DEFAULT_SHININESS, + reflectivity: Number = DEFAULT_REFLECTIVITY, + transparency: Number = DEFAULT_TRANSPARENCY, + refractiveIndex: Number = DEFAULT_REFRACTIVE_INDEX) { + + val ambient = ambient.toDouble() + val diffuse = diffuse.toDouble() + val specular = specular.toDouble() + val shininess = shininess.toDouble() + val reflectivity = reflectivity.toDouble() + val transparency = transparency.toDouble() + val refractiveIndex = refractiveIndex.toDouble() // Convenience constructor to create a material with a solid pattern. constructor(color: Color, - ambient: Double = DEFAULT_AMBIENT, - diffuse: Double = DEFAULT_DIFFUSE, - specular: Double = DEFAULT_SPECULAR, - shininess: Double = DEFAULT_SHININESS, - reflectivity: Double = DEFAULT_REFLECTIVITY, - transparency: Double = DEFAULT_TRANSPARENCY, - refractiveIndex: Double = DEFAULT_REFRACTIVE_INDEX): - this(SolidPattern(color), ambient, diffuse, specular, shininess, reflectivity, transparency, refractiveIndex) + ambient: Number = DEFAULT_AMBIENT, + diffuse: Number = DEFAULT_DIFFUSE, + specular: Number = DEFAULT_SPECULAR, + shininess: Number = DEFAULT_SHININESS, + reflectivity: Number = DEFAULT_REFLECTIVITY, + transparency: Number = DEFAULT_TRANSPARENCY, + refractiveIndex: Number = DEFAULT_REFRACTIVE_INDEX): + this(SolidPattern(color), + ambient, diffuse, specular, shininess, reflectivity, transparency, refractiveIndex) internal fun lighting(shape: Shape, light: Light, @@ -62,23 +71,6 @@ data class Material(val pattern: Pattern = SolidPattern(Color.WHITE), return ambient + diffuse + specular } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Material) return false - - if (pattern != other.pattern) return false - if (!almostEquals(ambient, other.ambient)) return false - if (!almostEquals(diffuse, other.diffuse)) return false - if (!almostEquals(specular, other.specular)) return false - if (!almostEquals(shininess, other.shininess)) return false - - return true - } - - override fun hashCode(): Int = - 31 * (31 * (31 * (31 * pattern.hashCode() + ambient.hashCode()) + - diffuse.hashCode()) + specular.hashCode()) + shininess.hashCode() - companion object { const val DEFAULT_AMBIENT = 0.1 const val DEFAULT_DIFFUSE = 0.9 diff --git a/src/main/kotlin/shapes/cone.kt b/src/main/kotlin/shapes/cone.kt index a56316c..f78ea66 100644 --- a/src/main/kotlin/shapes/cone.kt +++ b/src/main/kotlin/shapes/cone.kt @@ -12,12 +12,16 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, val closed: Boolean = false, transformation: Matrix = Matrix.I, material: Material = Material(), - castsShadow: Boolean = true): - Shape(transformation, material, castsShadow) { + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, material, castsShadow, parent) { val minimum = minimum.toDouble() val maximum = maximum.toDouble() + override fun withParent(parent: Shape?): Shape = + Cone(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val a = rayLocal.direction.x * rayLocal.direction.x - rayLocal.direction.y * rayLocal.direction.y + diff --git a/src/main/kotlin/shapes/cube.kt b/src/main/kotlin/shapes/cube.kt index e874ba6..1e01ae5 100644 --- a/src/main/kotlin/shapes/cube.kt +++ b/src/main/kotlin/shapes/cube.kt @@ -7,8 +7,14 @@ import math.* import math.Intersection import kotlin.math.absoluteValue -class Cube(transformation: Matrix = Matrix.I, material: Material = Material(), castsShadow: Boolean = true): - Shape(transformation, material, castsShadow) { +class Cube(transformation: Matrix = Matrix.I, + material: Material = Material(), + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, material, castsShadow, parent) { + override fun withParent(parent: Shape?): Shape = + Cube(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val (xtMin, xtMax) = checkAxis(rayLocal.origin.x, rayLocal.direction.x) val (ytMin, ytMax) = checkAxis(rayLocal.origin.y, rayLocal.direction.y) diff --git a/src/main/kotlin/shapes/cylinder.kt b/src/main/kotlin/shapes/cylinder.kt index 5cb3ad0..412b4d7 100644 --- a/src/main/kotlin/shapes/cylinder.kt +++ b/src/main/kotlin/shapes/cylinder.kt @@ -12,12 +12,16 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, val closed: Boolean = false, transformation: Matrix = Matrix.I, material: Material = Material(), - castsShadow: Boolean = true): - Shape(transformation, material, castsShadow) { + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, material, castsShadow, parent) { val minimum = minimum.toDouble() val maximum = maximum.toDouble() + override fun withParent(parent: Shape?): Shape = + Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val a = rayLocal.direction.x * rayLocal.direction.x + rayLocal.direction.z * rayLocal.direction.z diff --git a/src/main/kotlin/shapes/group.kt b/src/main/kotlin/shapes/group.kt new file mode 100644 index 0000000..79768f6 --- /dev/null +++ b/src/main/kotlin/shapes/group.kt @@ -0,0 +1,32 @@ +package shapes + +// By Sebastian Raaphorst, 2023. + +import material.Material +import math.Intersection +import math.Matrix +import math.Ray +import math.Tuple + +class Group(transformation: Matrix = Matrix.I, + children: List = emptyList(), + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, Material(), castsShadow, parent) { + + // Make copies of all the children to backreference this as their parent. + val children = children.map { it.withParent(this) } + + operator fun contains(s: Shape): Boolean = + s in children + + override fun withParent(parent: Shape?): Shape = + Group(transformation, children, castsShadow, parent) + + override fun localIntersect(rayLocal: Ray): List = + children.flatMap { it.intersect(rayLocal) }.sortedBy { it.t } + + override fun localNormalAt(localPoint: Tuple): Tuple { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/shapes/plane.kt b/src/main/kotlin/shapes/plane.kt index dc76430..0b9c159 100644 --- a/src/main/kotlin/shapes/plane.kt +++ b/src/main/kotlin/shapes/plane.kt @@ -6,8 +6,14 @@ import material.Material import math.* import kotlin.math.absoluteValue -class Plane(transformation: Matrix = Matrix.I, material: Material = Material(), castsShadow: Boolean = true): - Shape(transformation, material, castsShadow) { +class Plane(transformation: Matrix = Matrix.I, + material: Material = Material(), + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, material, castsShadow, parent) { + override fun withParent(parent: Shape?): Shape = + Plane(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { if (rayLocal.direction.y.absoluteValue < DEFAULT_PRECISION) return emptyList() diff --git a/src/main/kotlin/shapes/shape.kt b/src/main/kotlin/shapes/shape.kt index ea99aea..8b739bd 100644 --- a/src/main/kotlin/shapes/shape.kt +++ b/src/main/kotlin/shapes/shape.kt @@ -8,10 +8,13 @@ import math.Matrix import math.Ray import math.Tuple import java.util.UUID +import kotlin.math.PI +import kotlin.math.sqrt abstract class Shape(val transformation: Matrix, val material: Material, - val castsShadow: Boolean = true, + val castsShadow: Boolean, + val parent: Shape?, private val id: UUID = UUID.randomUUID()) { init { if (!transformation.isTransformation()) @@ -19,15 +22,19 @@ abstract class Shape(val transformation: Matrix, "\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}") } + abstract fun withParent(parent: Shape? = null): Shape + // Convert a point from world space to object space. // Later on, we use parent here. - fun worldToLocal(tuple: Tuple): Tuple = - transformation.inverse * tuple + internal fun worldToLocal(tuple: Tuple): Tuple = + transformation.inverse * (parent?.worldToLocal(tuple) ?: tuple) // Convert a normal vector from object space to world space. // Later on, we will use parent here. - private fun normalToWorld(localNormal: Tuple): Tuple = - (transformation.inverse.transpose * localNormal).toVector().normalized + internal fun normalToWorld(localNormal: Tuple): Tuple { + val normal = (transformation.inverse.transpose * localNormal).toVector().normalized + return parent?.normalToWorld(normal) ?: normal + } // The intersect method transforms the ray to object space and then passes // it to localNormalAt, which should comprise the concrete implementation of @@ -52,21 +59,6 @@ abstract class Shape(val transformation: Matrix, // Convert back to world space. return normalToWorld(localNormal) } - internal abstract fun localNormalAt(localPoint: Tuple): Tuple - - - // We want shapes to be considered only equal if they represent exactly the same shape. - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Shape) return false - if (id != other.id) return false - if (transformation != other.transformation) return false - if (material != other.material) return false - - return true - } - - override fun hashCode(): Int = - 31 * (31 * transformation.hashCode() + material.hashCode()) + id.hashCode() + internal abstract fun localNormalAt(localPoint: Tuple): Tuple } diff --git a/src/main/kotlin/shapes/sphere.kt b/src/main/kotlin/shapes/sphere.kt index 73fc503..aaf06ce 100644 --- a/src/main/kotlin/shapes/sphere.kt +++ b/src/main/kotlin/shapes/sphere.kt @@ -9,8 +9,14 @@ import math.Ray import math.Tuple import kotlin.math.sqrt -class Sphere(transformation: Matrix = Matrix.I, material: Material = Material(), castsShadow: Boolean = true): - Shape(transformation, material, castsShadow) { +class Sphere(transformation: Matrix = Matrix.I, + material: Material = Material(), + castsShadow: Boolean = true, + parent: Shape? = null): + Shape(transformation, material, castsShadow, parent) { + override fun withParent(parent: Shape?): Shape = + Sphere(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val sphereToRay = rayLocal.origin - Tuple.PZERO diff --git a/src/test/kotlin/shapes/grouptest.kt b/src/test/kotlin/shapes/grouptest.kt new file mode 100644 index 0000000..b88a3cb --- /dev/null +++ b/src/test/kotlin/shapes/grouptest.kt @@ -0,0 +1,70 @@ +package shapes + +// By Sebastian Raaphorst, 2023. + +import math.Matrix +import math.Ray +import math.Tuple +import org.junit.jupiter.api.Test +import kotlin.test.* + +class GroupTest { + @Test + fun `New group`() { + val g = Group() + assertEquals(Matrix.I, g.transformation) + assertTrue(g.children.isEmpty()) + } + + @Test + fun `New group with test shape`() { + val s = ShapeTest.TestShape() + val g = Group(children = listOf(s)) + assertEquals(1, g.children.size) + val sp = g.children[0] + + // Check that s has changed and been assigned the group as a parent. + assertNotSame(s, sp) + assertEquals(g, sp.parent) + + // Check that g does not contain the initial shape, but does contain the new shape. + assertFalse(s in g) + assertTrue(sp in g) + } + + @Test + fun `Intersect ray with empty group`() { + val g = Group() + val r = Ray(Tuple.PZERO, Tuple.VZ) + val xs = g.localIntersect(r) + assertTrue(xs.isEmpty()) + } + + @Test + fun `Intersect ray with nonempty group`() { + val s1 = Sphere() + val s2 = Sphere(transformation = Matrix.translate(0, 0, -3)) + val s3 = Sphere(transformation = Matrix.translate(5, 0, 0)) + val g = Group(children = listOf(s1, s2, s3)) + + val r = Ray(Tuple.point(0, 0, -5), Tuple.VZ) + val xs = g.localIntersect(r) + assertEquals(4, xs.size) + val sp1 = g.children[0] + val sp2 = g.children[1] + assertSame(sp2, xs[0].shape) + assertSame(sp2, xs[1].shape) + assertSame(sp1, xs[2].shape) + assertSame(sp1, xs[3].shape) + } + + @Test + fun `Intersect ray with transformed group`() { + val s = Sphere(transformation = Matrix.translate(5, 0, 0)) + val g = Group(transformation = Matrix.scale(2, 2, 2), children = listOf(s)) + + val r = Ray(Tuple.point(10, 0, -10), Tuple.VZ) + val xs = g.intersect(r) + assertEquals(2, xs.size) + } +} diff --git a/src/test/kotlin/shapes/shapetest.kt b/src/test/kotlin/shapes/shapetest.kt index 458229b..bc1ccfa 100644 --- a/src/test/kotlin/shapes/shapetest.kt +++ b/src/test/kotlin/shapes/shapetest.kt @@ -6,13 +6,22 @@ import material.Material import math.* import org.junit.jupiter.api.Test import kotlin.math.PI +import kotlin.math.sqrt class ShapeTest { class TestShape(transformation: Matrix = Matrix.I, - material: Material = Material()): Shape(transformation, material) { + material: Material = Material(), + parent: Shape? = null): + Shape(transformation, material, true, parent) { // We need to use a var here to store a ray. var savedRay = Ray(Tuple.PZERO, Tuple.VZERO) + override fun withParent(parent: Shape?): Shape { + val s = TestShape(transformation, material, parent) + s.savedRay = savedRay + return s + } + override fun localIntersect(rayLocal: Ray): List { savedRay = rayLocal return emptyList() @@ -57,4 +66,29 @@ class ShapeTest { val n = s.normalAt(Tuple.point(0, sqrt2by2, -sqrt2by2)) assertAlmostEquals(Tuple.vector(0, 0.97014, -0.24254), n) } + + @Test + fun `Point from world to local space`() { + val s = Sphere(Matrix.translate(5, 0, 0)) + val g2 = Group(Matrix.scale(2, 2, 2), listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + + // We have to get the new sphere to have the parent set. + val sNew = (g1.children[0] as Group).children[0] + val p = sNew.worldToLocal(Tuple.point(-2, 0, -10)) + assertAlmostEquals(Tuple.point(0, 0, -1), p) + } + + @Test + fun `Normal from local to world space`() { + val s = Sphere(Matrix.translate(5, 0, 0)) + val g2 = Group(Matrix.scale(1, 2, 3), listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + + // We have to get the new sphere to have the parent set. + val sNew = (g1.children[0] as Group).children[0] + val sqrt3by3 = sqrt(3.0) / 3 + val n = sNew.normalToWorld(Tuple.vector(sqrt3by3, sqrt3by3, sqrt3by3)) + assertAlmostEquals(Tuple.vector(0.28571, 0.42857, -0.85714), n) + } } From a05102574549b8dcd6a21dc6fef2bddd13f9ea71 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Wed, 18 Jan 2023 04:19:40 -0500 Subject: [PATCH 2/9] Chapter 14: Renaming files to Kotlin convention. --- src/main/kotlin/material/{material.kt => Material.kt} | 1 - src/main/kotlin/math/{color.kt => Color.kt} | 0 src/main/kotlin/math/{computations.kt => Computations.kt} | 0 src/main/kotlin/math/{intersection.kt => Intersection.kt} | 0 src/main/kotlin/math/{matrix.kt => Matrix.kt} | 0 src/main/kotlin/math/{ray.kt => Ray.kt} | 0 src/main/kotlin/math/{shared.kt => Shared.kt} | 0 src/main/kotlin/math/{transformable.kt => Transformable.kt} | 0 src/main/kotlin/math/{tuple.kt => Tuple.kt} | 0 src/main/kotlin/output/{canvas.kt => Canvas.kt} | 0 src/main/kotlin/output/{show.kt => Show.kt} | 0 .../kotlin/pattern/{blendedpattern.kt => BlendedPattern.kt} | 0 .../kotlin/pattern/{checkerpattern.kt => CheckerPattern.kt} | 0 .../kotlin/pattern/{gradientpattern.kt => GradientPattern.kt} | 0 src/main/kotlin/pattern/{noisepattern.kt => NoisePattern.kt} | 0 src/main/kotlin/pattern/{pattern.kt => Pattern.kt} | 0 .../pattern/{perlinnoisepattern.kt => PerlinNoisePattern.kt} | 0 src/main/kotlin/pattern/{ringpattern.kt => RingPattern.kt} | 0 .../pattern/{simplexnoisepattern.kt => SimplexNoisePattern.kt} | 0 src/main/kotlin/pattern/{solidpattern.kt => SolidPattern.kt} | 1 - .../kotlin/pattern/{stripedpattern.kt => StripedPattern.kt} | 0 src/main/kotlin/scene/{camera.kt => Camera.kt} | 0 src/main/kotlin/scene/{world.kt => World.kt} | 2 -- src/main/kotlin/shapes/{cone.kt => Cone.kt} | 0 src/main/kotlin/shapes/{cube.kt => Cube.kt} | 0 src/main/kotlin/shapes/{cylinder.kt => Cylinder.kt} | 0 src/main/kotlin/shapes/{group.kt => Group.kt} | 0 src/main/kotlin/shapes/{plane.kt => Plane.kt} | 0 src/main/kotlin/shapes/{shape.kt => Shape.kt} | 2 -- src/main/kotlin/shapes/{sphere.kt => Sphere.kt} | 0 src/test/kotlin/light/{pointlighttest.kt => PointLightTest.kt} | 0 src/test/kotlin/material/{materialtest.kt => MaterialTest.kt} | 0 src/test/kotlin/math/{colortest.kt => ColorTest.kt} | 0 .../kotlin/math/{intersectiontest.kt => IntersectionTest.kt} | 0 src/test/kotlin/math/{matrixtest.kt => MatrixTest.kt} | 0 src/test/kotlin/math/{raytest.kt => RayTest.kt} | 0 src/test/kotlin/math/{sharedtest.kt => SharedTest.kt} | 0 src/test/kotlin/math/{tupletest.kt => TupleTest.kt} | 0 src/test/kotlin/output/{canvastest.kt => CanvasTest.kt} | 2 +- .../pattern/{blendedpatterntest.kt => BlendedPatternTest.kt} | 2 ++ .../pattern/{checkerpatterntest.kt => CheckerPatternTest.kt} | 0 .../pattern/{gradientpatterntest.kt => GradientPatternTest.kt} | 0 src/test/kotlin/pattern/{patterntest.kt => PatternTest.kt} | 0 .../kotlin/pattern/{ringpatterntest.kt => RingPatternTest.kt} | 0 .../kotlin/pattern/{solidpatterntest.kt => SolidPatternTest.kt} | 0 .../pattern/{stripedpatterntest.kt => StripedPatternTest.kt} | 0 src/test/kotlin/scene/{cameratest.kt => CameraTest.kt} | 0 src/test/kotlin/scene/{worldtest.kt => WorldTest.kt} | 0 src/test/kotlin/shapes/{conetest.kt => ConeTest.kt} | 0 src/test/kotlin/shapes/{cubetest.kt => CubeTest.kt} | 0 src/test/kotlin/shapes/{cylindertest.kt => CylinderTest.kt} | 0 src/test/kotlin/shapes/{grouptest.kt => GroupTest.kt} | 0 src/test/kotlin/shapes/{planetest.kt => PlaneTest.kt} | 0 src/test/kotlin/shapes/{shapetest.kt => ShapeTest.kt} | 0 src/test/kotlin/shapes/{spheretest.kt => SphereTest.kt} | 0 55 files changed, 3 insertions(+), 7 deletions(-) rename src/main/kotlin/material/{material.kt => Material.kt} (99%) rename src/main/kotlin/math/{color.kt => Color.kt} (100%) rename src/main/kotlin/math/{computations.kt => Computations.kt} (100%) rename src/main/kotlin/math/{intersection.kt => Intersection.kt} (100%) rename src/main/kotlin/math/{matrix.kt => Matrix.kt} (100%) rename src/main/kotlin/math/{ray.kt => Ray.kt} (100%) rename src/main/kotlin/math/{shared.kt => Shared.kt} (100%) rename src/main/kotlin/math/{transformable.kt => Transformable.kt} (100%) rename src/main/kotlin/math/{tuple.kt => Tuple.kt} (100%) rename src/main/kotlin/output/{canvas.kt => Canvas.kt} (100%) rename src/main/kotlin/output/{show.kt => Show.kt} (100%) rename src/main/kotlin/pattern/{blendedpattern.kt => BlendedPattern.kt} (100%) rename src/main/kotlin/pattern/{checkerpattern.kt => CheckerPattern.kt} (100%) rename src/main/kotlin/pattern/{gradientpattern.kt => GradientPattern.kt} (100%) rename src/main/kotlin/pattern/{noisepattern.kt => NoisePattern.kt} (100%) rename src/main/kotlin/pattern/{pattern.kt => Pattern.kt} (100%) rename src/main/kotlin/pattern/{perlinnoisepattern.kt => PerlinNoisePattern.kt} (100%) rename src/main/kotlin/pattern/{ringpattern.kt => RingPattern.kt} (100%) rename src/main/kotlin/pattern/{simplexnoisepattern.kt => SimplexNoisePattern.kt} (100%) rename src/main/kotlin/pattern/{solidpattern.kt => SolidPattern.kt} (96%) rename src/main/kotlin/pattern/{stripedpattern.kt => StripedPattern.kt} (100%) rename src/main/kotlin/scene/{camera.kt => Camera.kt} (100%) rename src/main/kotlin/scene/{world.kt => World.kt} (99%) rename src/main/kotlin/shapes/{cone.kt => Cone.kt} (100%) rename src/main/kotlin/shapes/{cube.kt => Cube.kt} (100%) rename src/main/kotlin/shapes/{cylinder.kt => Cylinder.kt} (100%) rename src/main/kotlin/shapes/{group.kt => Group.kt} (100%) rename src/main/kotlin/shapes/{plane.kt => Plane.kt} (100%) rename src/main/kotlin/shapes/{shape.kt => Shape.kt} (98%) rename src/main/kotlin/shapes/{sphere.kt => Sphere.kt} (100%) rename src/test/kotlin/light/{pointlighttest.kt => PointLightTest.kt} (100%) rename src/test/kotlin/material/{materialtest.kt => MaterialTest.kt} (100%) rename src/test/kotlin/math/{colortest.kt => ColorTest.kt} (100%) rename src/test/kotlin/math/{intersectiontest.kt => IntersectionTest.kt} (100%) rename src/test/kotlin/math/{matrixtest.kt => MatrixTest.kt} (100%) rename src/test/kotlin/math/{raytest.kt => RayTest.kt} (100%) rename src/test/kotlin/math/{sharedtest.kt => SharedTest.kt} (100%) rename src/test/kotlin/math/{tupletest.kt => TupleTest.kt} (100%) rename src/test/kotlin/output/{canvastest.kt => CanvasTest.kt} (98%) rename src/test/kotlin/pattern/{blendedpatterntest.kt => BlendedPatternTest.kt} (98%) rename src/test/kotlin/pattern/{checkerpatterntest.kt => CheckerPatternTest.kt} (100%) rename src/test/kotlin/pattern/{gradientpatterntest.kt => GradientPatternTest.kt} (100%) rename src/test/kotlin/pattern/{patterntest.kt => PatternTest.kt} (100%) rename src/test/kotlin/pattern/{ringpatterntest.kt => RingPatternTest.kt} (100%) rename src/test/kotlin/pattern/{solidpatterntest.kt => SolidPatternTest.kt} (100%) rename src/test/kotlin/pattern/{stripedpatterntest.kt => StripedPatternTest.kt} (100%) rename src/test/kotlin/scene/{cameratest.kt => CameraTest.kt} (100%) rename src/test/kotlin/scene/{worldtest.kt => WorldTest.kt} (100%) rename src/test/kotlin/shapes/{conetest.kt => ConeTest.kt} (100%) rename src/test/kotlin/shapes/{cubetest.kt => CubeTest.kt} (100%) rename src/test/kotlin/shapes/{cylindertest.kt => CylinderTest.kt} (100%) rename src/test/kotlin/shapes/{grouptest.kt => GroupTest.kt} (100%) rename src/test/kotlin/shapes/{planetest.kt => PlaneTest.kt} (100%) rename src/test/kotlin/shapes/{shapetest.kt => ShapeTest.kt} (100%) rename src/test/kotlin/shapes/{spheretest.kt => SphereTest.kt} (100%) diff --git a/src/main/kotlin/material/material.kt b/src/main/kotlin/material/Material.kt similarity index 99% rename from src/main/kotlin/material/material.kt rename to src/main/kotlin/material/Material.kt index 7a1589b..8d24fc6 100644 --- a/src/main/kotlin/material/material.kt +++ b/src/main/kotlin/material/Material.kt @@ -5,7 +5,6 @@ package material import light.Light import math.Color import math.Tuple -import math.almostEquals import pattern.Pattern import pattern.SolidPattern import shapes.Shape diff --git a/src/main/kotlin/math/color.kt b/src/main/kotlin/math/Color.kt similarity index 100% rename from src/main/kotlin/math/color.kt rename to src/main/kotlin/math/Color.kt diff --git a/src/main/kotlin/math/computations.kt b/src/main/kotlin/math/Computations.kt similarity index 100% rename from src/main/kotlin/math/computations.kt rename to src/main/kotlin/math/Computations.kt diff --git a/src/main/kotlin/math/intersection.kt b/src/main/kotlin/math/Intersection.kt similarity index 100% rename from src/main/kotlin/math/intersection.kt rename to src/main/kotlin/math/Intersection.kt diff --git a/src/main/kotlin/math/matrix.kt b/src/main/kotlin/math/Matrix.kt similarity index 100% rename from src/main/kotlin/math/matrix.kt rename to src/main/kotlin/math/Matrix.kt diff --git a/src/main/kotlin/math/ray.kt b/src/main/kotlin/math/Ray.kt similarity index 100% rename from src/main/kotlin/math/ray.kt rename to src/main/kotlin/math/Ray.kt diff --git a/src/main/kotlin/math/shared.kt b/src/main/kotlin/math/Shared.kt similarity index 100% rename from src/main/kotlin/math/shared.kt rename to src/main/kotlin/math/Shared.kt diff --git a/src/main/kotlin/math/transformable.kt b/src/main/kotlin/math/Transformable.kt similarity index 100% rename from src/main/kotlin/math/transformable.kt rename to src/main/kotlin/math/Transformable.kt diff --git a/src/main/kotlin/math/tuple.kt b/src/main/kotlin/math/Tuple.kt similarity index 100% rename from src/main/kotlin/math/tuple.kt rename to src/main/kotlin/math/Tuple.kt diff --git a/src/main/kotlin/output/canvas.kt b/src/main/kotlin/output/Canvas.kt similarity index 100% rename from src/main/kotlin/output/canvas.kt rename to src/main/kotlin/output/Canvas.kt diff --git a/src/main/kotlin/output/show.kt b/src/main/kotlin/output/Show.kt similarity index 100% rename from src/main/kotlin/output/show.kt rename to src/main/kotlin/output/Show.kt diff --git a/src/main/kotlin/pattern/blendedpattern.kt b/src/main/kotlin/pattern/BlendedPattern.kt similarity index 100% rename from src/main/kotlin/pattern/blendedpattern.kt rename to src/main/kotlin/pattern/BlendedPattern.kt diff --git a/src/main/kotlin/pattern/checkerpattern.kt b/src/main/kotlin/pattern/CheckerPattern.kt similarity index 100% rename from src/main/kotlin/pattern/checkerpattern.kt rename to src/main/kotlin/pattern/CheckerPattern.kt diff --git a/src/main/kotlin/pattern/gradientpattern.kt b/src/main/kotlin/pattern/GradientPattern.kt similarity index 100% rename from src/main/kotlin/pattern/gradientpattern.kt rename to src/main/kotlin/pattern/GradientPattern.kt diff --git a/src/main/kotlin/pattern/noisepattern.kt b/src/main/kotlin/pattern/NoisePattern.kt similarity index 100% rename from src/main/kotlin/pattern/noisepattern.kt rename to src/main/kotlin/pattern/NoisePattern.kt diff --git a/src/main/kotlin/pattern/pattern.kt b/src/main/kotlin/pattern/Pattern.kt similarity index 100% rename from src/main/kotlin/pattern/pattern.kt rename to src/main/kotlin/pattern/Pattern.kt diff --git a/src/main/kotlin/pattern/perlinnoisepattern.kt b/src/main/kotlin/pattern/PerlinNoisePattern.kt similarity index 100% rename from src/main/kotlin/pattern/perlinnoisepattern.kt rename to src/main/kotlin/pattern/PerlinNoisePattern.kt diff --git a/src/main/kotlin/pattern/ringpattern.kt b/src/main/kotlin/pattern/RingPattern.kt similarity index 100% rename from src/main/kotlin/pattern/ringpattern.kt rename to src/main/kotlin/pattern/RingPattern.kt diff --git a/src/main/kotlin/pattern/simplexnoisepattern.kt b/src/main/kotlin/pattern/SimplexNoisePattern.kt similarity index 100% rename from src/main/kotlin/pattern/simplexnoisepattern.kt rename to src/main/kotlin/pattern/SimplexNoisePattern.kt diff --git a/src/main/kotlin/pattern/solidpattern.kt b/src/main/kotlin/pattern/SolidPattern.kt similarity index 96% rename from src/main/kotlin/pattern/solidpattern.kt rename to src/main/kotlin/pattern/SolidPattern.kt index 4084c1f..f6550d2 100644 --- a/src/main/kotlin/pattern/solidpattern.kt +++ b/src/main/kotlin/pattern/SolidPattern.kt @@ -3,7 +3,6 @@ package pattern // By Sebastian Raaphorst, 2023. import math.Color -import math.Matrix import math.Tuple class SolidPattern(val color: Color): Pattern() { diff --git a/src/main/kotlin/pattern/stripedpattern.kt b/src/main/kotlin/pattern/StripedPattern.kt similarity index 100% rename from src/main/kotlin/pattern/stripedpattern.kt rename to src/main/kotlin/pattern/StripedPattern.kt diff --git a/src/main/kotlin/scene/camera.kt b/src/main/kotlin/scene/Camera.kt similarity index 100% rename from src/main/kotlin/scene/camera.kt rename to src/main/kotlin/scene/Camera.kt diff --git a/src/main/kotlin/scene/world.kt b/src/main/kotlin/scene/World.kt similarity index 99% rename from src/main/kotlin/scene/world.kt rename to src/main/kotlin/scene/World.kt index 4bfa04b..9f78a13 100644 --- a/src/main/kotlin/scene/world.kt +++ b/src/main/kotlin/scene/World.kt @@ -4,8 +4,6 @@ import light.Light import light.PointLight import material.Material import math.* -import pattern.Pattern -import shapes.Plane import shapes.Shape import shapes.Sphere import kotlin.math.sqrt diff --git a/src/main/kotlin/shapes/cone.kt b/src/main/kotlin/shapes/Cone.kt similarity index 100% rename from src/main/kotlin/shapes/cone.kt rename to src/main/kotlin/shapes/Cone.kt diff --git a/src/main/kotlin/shapes/cube.kt b/src/main/kotlin/shapes/Cube.kt similarity index 100% rename from src/main/kotlin/shapes/cube.kt rename to src/main/kotlin/shapes/Cube.kt diff --git a/src/main/kotlin/shapes/cylinder.kt b/src/main/kotlin/shapes/Cylinder.kt similarity index 100% rename from src/main/kotlin/shapes/cylinder.kt rename to src/main/kotlin/shapes/Cylinder.kt diff --git a/src/main/kotlin/shapes/group.kt b/src/main/kotlin/shapes/Group.kt similarity index 100% rename from src/main/kotlin/shapes/group.kt rename to src/main/kotlin/shapes/Group.kt diff --git a/src/main/kotlin/shapes/plane.kt b/src/main/kotlin/shapes/Plane.kt similarity index 100% rename from src/main/kotlin/shapes/plane.kt rename to src/main/kotlin/shapes/Plane.kt diff --git a/src/main/kotlin/shapes/shape.kt b/src/main/kotlin/shapes/Shape.kt similarity index 98% rename from src/main/kotlin/shapes/shape.kt rename to src/main/kotlin/shapes/Shape.kt index 8b739bd..d6aa6b4 100644 --- a/src/main/kotlin/shapes/shape.kt +++ b/src/main/kotlin/shapes/Shape.kt @@ -8,8 +8,6 @@ import math.Matrix import math.Ray import math.Tuple import java.util.UUID -import kotlin.math.PI -import kotlin.math.sqrt abstract class Shape(val transformation: Matrix, val material: Material, diff --git a/src/main/kotlin/shapes/sphere.kt b/src/main/kotlin/shapes/Sphere.kt similarity index 100% rename from src/main/kotlin/shapes/sphere.kt rename to src/main/kotlin/shapes/Sphere.kt diff --git a/src/test/kotlin/light/pointlighttest.kt b/src/test/kotlin/light/PointLightTest.kt similarity index 100% rename from src/test/kotlin/light/pointlighttest.kt rename to src/test/kotlin/light/PointLightTest.kt diff --git a/src/test/kotlin/material/materialtest.kt b/src/test/kotlin/material/MaterialTest.kt similarity index 100% rename from src/test/kotlin/material/materialtest.kt rename to src/test/kotlin/material/MaterialTest.kt diff --git a/src/test/kotlin/math/colortest.kt b/src/test/kotlin/math/ColorTest.kt similarity index 100% rename from src/test/kotlin/math/colortest.kt rename to src/test/kotlin/math/ColorTest.kt diff --git a/src/test/kotlin/math/intersectiontest.kt b/src/test/kotlin/math/IntersectionTest.kt similarity index 100% rename from src/test/kotlin/math/intersectiontest.kt rename to src/test/kotlin/math/IntersectionTest.kt diff --git a/src/test/kotlin/math/matrixtest.kt b/src/test/kotlin/math/MatrixTest.kt similarity index 100% rename from src/test/kotlin/math/matrixtest.kt rename to src/test/kotlin/math/MatrixTest.kt diff --git a/src/test/kotlin/math/raytest.kt b/src/test/kotlin/math/RayTest.kt similarity index 100% rename from src/test/kotlin/math/raytest.kt rename to src/test/kotlin/math/RayTest.kt diff --git a/src/test/kotlin/math/sharedtest.kt b/src/test/kotlin/math/SharedTest.kt similarity index 100% rename from src/test/kotlin/math/sharedtest.kt rename to src/test/kotlin/math/SharedTest.kt diff --git a/src/test/kotlin/math/tupletest.kt b/src/test/kotlin/math/TupleTest.kt similarity index 100% rename from src/test/kotlin/math/tupletest.kt rename to src/test/kotlin/math/TupleTest.kt diff --git a/src/test/kotlin/output/canvastest.kt b/src/test/kotlin/output/CanvasTest.kt similarity index 98% rename from src/test/kotlin/output/canvastest.kt rename to src/test/kotlin/output/CanvasTest.kt index 98a400c..f497a49 100644 --- a/src/test/kotlin/output/canvastest.kt +++ b/src/test/kotlin/output/CanvasTest.kt @@ -6,7 +6,7 @@ import math.Color import org.junit.jupiter.api.Test import kotlin.test.assertEquals -class TestCanvas { +class CanvasTest { @Test fun `Create a Canvas`() { val c = Canvas(10, 20) diff --git a/src/test/kotlin/pattern/blendedpatterntest.kt b/src/test/kotlin/pattern/BlendedPatternTest.kt similarity index 98% rename from src/test/kotlin/pattern/blendedpatterntest.kt rename to src/test/kotlin/pattern/BlendedPatternTest.kt index d7940a9..427cb38 100644 --- a/src/test/kotlin/pattern/blendedpatterntest.kt +++ b/src/test/kotlin/pattern/BlendedPatternTest.kt @@ -1,5 +1,7 @@ package pattern +// By Sebastian Raaphorst, 2023. + import math.Color import math.Matrix import math.Tuple diff --git a/src/test/kotlin/pattern/checkerpatterntest.kt b/src/test/kotlin/pattern/CheckerPatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/checkerpatterntest.kt rename to src/test/kotlin/pattern/CheckerPatternTest.kt diff --git a/src/test/kotlin/pattern/gradientpatterntest.kt b/src/test/kotlin/pattern/GradientPatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/gradientpatterntest.kt rename to src/test/kotlin/pattern/GradientPatternTest.kt diff --git a/src/test/kotlin/pattern/patterntest.kt b/src/test/kotlin/pattern/PatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/patterntest.kt rename to src/test/kotlin/pattern/PatternTest.kt diff --git a/src/test/kotlin/pattern/ringpatterntest.kt b/src/test/kotlin/pattern/RingPatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/ringpatterntest.kt rename to src/test/kotlin/pattern/RingPatternTest.kt diff --git a/src/test/kotlin/pattern/solidpatterntest.kt b/src/test/kotlin/pattern/SolidPatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/solidpatterntest.kt rename to src/test/kotlin/pattern/SolidPatternTest.kt diff --git a/src/test/kotlin/pattern/stripedpatterntest.kt b/src/test/kotlin/pattern/StripedPatternTest.kt similarity index 100% rename from src/test/kotlin/pattern/stripedpatterntest.kt rename to src/test/kotlin/pattern/StripedPatternTest.kt diff --git a/src/test/kotlin/scene/cameratest.kt b/src/test/kotlin/scene/CameraTest.kt similarity index 100% rename from src/test/kotlin/scene/cameratest.kt rename to src/test/kotlin/scene/CameraTest.kt diff --git a/src/test/kotlin/scene/worldtest.kt b/src/test/kotlin/scene/WorldTest.kt similarity index 100% rename from src/test/kotlin/scene/worldtest.kt rename to src/test/kotlin/scene/WorldTest.kt diff --git a/src/test/kotlin/shapes/conetest.kt b/src/test/kotlin/shapes/ConeTest.kt similarity index 100% rename from src/test/kotlin/shapes/conetest.kt rename to src/test/kotlin/shapes/ConeTest.kt diff --git a/src/test/kotlin/shapes/cubetest.kt b/src/test/kotlin/shapes/CubeTest.kt similarity index 100% rename from src/test/kotlin/shapes/cubetest.kt rename to src/test/kotlin/shapes/CubeTest.kt diff --git a/src/test/kotlin/shapes/cylindertest.kt b/src/test/kotlin/shapes/CylinderTest.kt similarity index 100% rename from src/test/kotlin/shapes/cylindertest.kt rename to src/test/kotlin/shapes/CylinderTest.kt diff --git a/src/test/kotlin/shapes/grouptest.kt b/src/test/kotlin/shapes/GroupTest.kt similarity index 100% rename from src/test/kotlin/shapes/grouptest.kt rename to src/test/kotlin/shapes/GroupTest.kt diff --git a/src/test/kotlin/shapes/planetest.kt b/src/test/kotlin/shapes/PlaneTest.kt similarity index 100% rename from src/test/kotlin/shapes/planetest.kt rename to src/test/kotlin/shapes/PlaneTest.kt diff --git a/src/test/kotlin/shapes/shapetest.kt b/src/test/kotlin/shapes/ShapeTest.kt similarity index 100% rename from src/test/kotlin/shapes/shapetest.kt rename to src/test/kotlin/shapes/ShapeTest.kt diff --git a/src/test/kotlin/shapes/spheretest.kt b/src/test/kotlin/shapes/SphereTest.kt similarity index 100% rename from src/test/kotlin/shapes/spheretest.kt rename to src/test/kotlin/shapes/SphereTest.kt From ecda5573b7a57d8188a285093a17c1c8763e761a Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Thu, 19 Jan 2023 00:13:53 -0500 Subject: [PATCH 3/9] Chapter 14: Bounding boxes added. --- src/main/kotlin/apps/ch07_world.kt | 8 +- src/main/kotlin/apps/ch08_shadowpuppet.kt | 8 +- src/main/kotlin/apps/ch09_world.kt | 8 +- src/main/kotlin/apps/ch10_world.kt | 8 +- src/main/kotlin/apps/ch11_world.kt | 8 +- src/main/kotlin/apps/ch12_world.kt | 9 +- src/main/kotlin/apps/ch13_world.kt | 8 +- src/main/kotlin/math/BoundingBox.kt | 104 ++++++++++++++++++++++ src/main/kotlin/shapes/Cone.kt | 7 ++ src/main/kotlin/shapes/Cube.kt | 25 ++---- src/main/kotlin/shapes/Cylinder.kt | 4 + src/main/kotlin/shapes/Group.kt | 29 ++++-- src/main/kotlin/shapes/Plane.kt | 7 ++ src/main/kotlin/shapes/Shape.kt | 19 ++-- src/main/kotlin/shapes/Sphere.kt | 8 +- src/test/kotlin/shapes/CubeTest.kt | 3 +- src/test/kotlin/shapes/GroupTest.kt | 12 +-- src/test/kotlin/shapes/ShapeTest.kt | 18 ++++ 18 files changed, 238 insertions(+), 55 deletions(-) create mode 100644 src/main/kotlin/math/BoundingBox.kt diff --git a/src/main/kotlin/apps/ch07_world.kt b/src/main/kotlin/apps/ch07_world.kt index 0ea515b..9682c75 100644 --- a/src/main/kotlin/apps/ch07_world.kt +++ b/src/main/kotlin/apps/ch07_world.kt @@ -12,6 +12,7 @@ import scene.World import shapes.Sphere import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val multipleLights = false @@ -87,6 +88,9 @@ fun main() { Camera(1000, 500, PI / 3, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch07_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch07_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/apps/ch08_shadowpuppet.kt b/src/main/kotlin/apps/ch08_shadowpuppet.kt index 49b48df..659fb95 100644 --- a/src/main/kotlin/apps/ch08_shadowpuppet.kt +++ b/src/main/kotlin/apps/ch08_shadowpuppet.kt @@ -12,6 +12,7 @@ import scene.World import shapes.Sphere import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val wrist = run { @@ -77,6 +78,9 @@ fun main() { Camera(1200, 600, PI/6, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch08_shadowpuppet.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch08_shadowpuppet.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/apps/ch09_world.kt b/src/main/kotlin/apps/ch09_world.kt index 9e9889f..40e66ee 100644 --- a/src/main/kotlin/apps/ch09_world.kt +++ b/src/main/kotlin/apps/ch09_world.kt @@ -13,6 +13,7 @@ import shapes.Plane import shapes.Sphere import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val multipleLights = false @@ -88,6 +89,9 @@ fun main() { Camera(1000, 500, PI / 3, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch09_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch09_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/apps/ch10_world.kt b/src/main/kotlin/apps/ch10_world.kt index 8995d5d..9b8c362 100644 --- a/src/main/kotlin/apps/ch10_world.kt +++ b/src/main/kotlin/apps/ch10_world.kt @@ -14,6 +14,7 @@ import shapes.Plane import shapes.Sphere import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val world = run { @@ -95,6 +96,9 @@ fun main() { Camera(2500, 1250, PI /3, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch10_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch10_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/apps/ch11_world.kt b/src/main/kotlin/apps/ch11_world.kt index fa284e4..ac7cef1 100644 --- a/src/main/kotlin/apps/ch11_world.kt +++ b/src/main/kotlin/apps/ch11_world.kt @@ -16,6 +16,7 @@ import shapes.Plane import shapes.Sphere import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val wallMaterial = run { @@ -113,6 +114,9 @@ fun main() { Camera(2400, 1200, 1.152, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch11_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch11_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/apps/ch12_world.kt b/src/main/kotlin/apps/ch12_world.kt index 67b7e66..898995e 100644 --- a/src/main/kotlin/apps/ch12_world.kt +++ b/src/main/kotlin/apps/ch12_world.kt @@ -14,6 +14,7 @@ import scene.Camera import scene.World import shapes.Cube import java.io.File +import kotlin.system.measureTimeMillis fun main() { val floorCeiling = run { @@ -152,6 +153,10 @@ fun main() { Camera(2400, 1200, 0.7805, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch12_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch12_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } + diff --git a/src/main/kotlin/apps/ch13_world.kt b/src/main/kotlin/apps/ch13_world.kt index ba3ba00..b902bd7 100644 --- a/src/main/kotlin/apps/ch13_world.kt +++ b/src/main/kotlin/apps/ch13_world.kt @@ -15,6 +15,7 @@ import shapes.Cylinder import shapes.Plane import java.io.File import kotlin.math.PI +import kotlin.system.measureTimeMillis fun main() { val flooring = run { @@ -108,6 +109,9 @@ fun main() { Camera(2400, 1200, PI / 10, t) } - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch13_world.ppm")) + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch13_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") } diff --git a/src/main/kotlin/math/BoundingBox.kt b/src/main/kotlin/math/BoundingBox.kt new file mode 100644 index 0000000..f9347a2 --- /dev/null +++ b/src/main/kotlin/math/BoundingBox.kt @@ -0,0 +1,104 @@ +package math + +// By Sebastian Raaphorst, 2023. + +import kotlin.math.max +import kotlin.math.min + +data class BoundingBox(val minPoint: Tuple = MaxPoint, val maxPoint: Tuple = MinPoint) { + init { + if (!minPoint.isPoint()) + throw IllegalArgumentException("BoundingBox minPoint is not a point: $minPoint.") + if (!maxPoint.isPoint()) + throw IllegalArgumentException("BoundingBox maxPoint is not a point: $maxPoint.") + } + + val isEmpty: Boolean by lazy { + minPoint.x > maxPoint.x || minPoint.y > maxPoint.y || minPoint.z > maxPoint.z + } + val isNotEmpty: Boolean by lazy { + !isEmpty + } + + fun add(point: Tuple): BoundingBox { + if (!point.isPoint()) + throw IllegalArgumentException("Tried to add vector to BoundingBox: $point.") + return BoundingBox( + Tuple.point(min(minPoint.x, point.x), min(minPoint.y, point.y), min(minPoint.z, point.z)), + Tuple.point(max(maxPoint.x, point.x), min(maxPoint.y, point.y), min(maxPoint.z, point.z)) + ) + } + + fun merge(box: BoundingBox): BoundingBox = + add(box.minPoint).add(box.maxPoint) + + fun transform(transformation: Matrix): BoundingBox { + val corners = listOf( + minPoint, + Tuple.point(maxPoint.x, minPoint.y, minPoint.z), + Tuple.point(maxPoint.x, minPoint.y, maxPoint.z), + Tuple.point(minPoint.x, minPoint.y, maxPoint.z), + Tuple.point(minPoint.x, maxPoint.y, minPoint.z), + Tuple.point(maxPoint.x, maxPoint.y, minPoint.z), + maxPoint, + Tuple.point(minPoint.x, maxPoint.y, maxPoint.z) + ) + + val transformedCorners = corners.map { transformation * it } + + // Find the new minPoint and maxPoint. + val minX = transformedCorners.fold(Double.POSITIVE_INFINITY) { curr, p -> if (p.x < curr) p.x else curr } + val minY = transformedCorners.fold(Double.POSITIVE_INFINITY) { curr, p -> if (p.y < curr) p.y else curr } + val minZ = transformedCorners.fold(Double.POSITIVE_INFINITY) { curr, p -> if (p.z < curr) p.z else curr } + val maxX = transformedCorners.fold(Double.NEGATIVE_INFINITY) { curr, p -> if (p.x > curr) p.x else curr } + val maxY = transformedCorners.fold(Double.NEGATIVE_INFINITY) { curr, p -> if (p.y > curr) p.y else curr } + val maxZ = transformedCorners.fold(Double.NEGATIVE_INFINITY) { curr, p -> if (p.z > curr) p.z else curr } + + return BoundingBox(Tuple.point(minX, minY, minZ), Tuple.point(maxX, maxY, maxZ)) + } + + // Check axis for AABB (axis aligned bounding box). + // minimum and maximum specify the minimum and maximum values on the axis for this bounding box. + private fun checkAxis(origin: Double, direction: Double, minimum: Double, maximum: Double): Pair { + val tMin = (minimum - origin) / direction + val tMax = (maximum - origin) / direction + return if (tMin > tMax) Pair(tMax, tMin) else Pair(tMin, tMax) + } + + // Check if the ray intersects this bounding box, and if it does, return the t-values. + internal fun intersects(rayLocal: Ray): List { + val (xtMin, xtMax) = checkAxis(rayLocal.origin.x, rayLocal.direction.x, minPoint.x, maxPoint.x) + val (ytMin, ytMax) = checkAxis(rayLocal.origin.y, rayLocal.direction.y, minPoint.y, maxPoint.y) + val (ztMin, ztMax) = checkAxis(rayLocal.origin.z, rayLocal.direction.z, minPoint.z, maxPoint.z) + + val tMin = maxOf(xtMin, ytMin, ztMin) + val tMax = minOf(xtMax, ytMax, ztMax) + + return if (tMin <= tMax) + listOf(tMin, tMax) + else + emptyList() + } + operator fun contains(point: Tuple): Boolean { + if (!point.isPoint()) + throw IllegalArgumentException("BoundingBox contains passed vector: $point.") + return point.x >= minPoint.x && point.y >= minPoint.y && point.z >= minPoint.z && + point.x <= maxPoint.x && point.y <= maxPoint.y && point.z <= maxPoint.z + } + + operator fun contains(box: BoundingBox): Boolean = + contains(box.minPoint) && contains(box.maxPoint) + + companion object { + internal val MaxPoint = Tuple.point( + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY + ) + internal val MinPoint = Tuple.point( + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY + ) + } +} diff --git a/src/main/kotlin/shapes/Cone.kt b/src/main/kotlin/shapes/Cone.kt index f78ea66..6f2d9cb 100644 --- a/src/main/kotlin/shapes/Cone.kt +++ b/src/main/kotlin/shapes/Cone.kt @@ -5,6 +5,8 @@ package shapes import material.Material import math.* import math.Intersection +import kotlin.math.absoluteValue +import kotlin.math.max import kotlin.math.sqrt class Cone(minimum: Number = Double.NEGATIVE_INFINITY, @@ -107,4 +109,9 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, return Tuple.vector(localPoint.x, y, localPoint.z) } } + + override val bounds: BoundingBox by lazy { + val r = max(this.minimum.absoluteValue, this.maximum.absoluteValue) + BoundingBox(Tuple.point(-r, minimum, -r), Tuple.point(r, maximum, r)) + } } diff --git a/src/main/kotlin/shapes/Cube.kt b/src/main/kotlin/shapes/Cube.kt index 1e01ae5..0a764e1 100644 --- a/src/main/kotlin/shapes/Cube.kt +++ b/src/main/kotlin/shapes/Cube.kt @@ -15,25 +15,8 @@ class Cube(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Cube(transformation, material, castsShadow, parent) - override fun localIntersect(rayLocal: Ray): List { - val (xtMin, xtMax) = checkAxis(rayLocal.origin.x, rayLocal.direction.x) - val (ytMin, ytMax) = checkAxis(rayLocal.origin.y, rayLocal.direction.y) - val (ztMin, ztMax) = checkAxis(rayLocal.origin.z, rayLocal.direction.z) - - val tMin = maxOf(xtMin, ytMin, ztMin) - val tMax = minOf(xtMax, ytMax, ztMax) - - return if (tMin <= tMax) - listOf(Intersection(tMin, this), Intersection(tMax, this)) - else - emptyList() - } - - private fun checkAxis(origin: Double, direction: Double): Pair { - val tMin = (-1 - origin) / direction - val tMax = (1 - origin) / direction - return if (tMin > tMax) Pair(tMax, tMin) else Pair(tMin, tMax) - } + override fun localIntersect(rayLocal: Ray): List = + bounds.intersects(rayLocal).map { Intersection(it, this) } override fun localNormalAt(localPoint: Tuple): Tuple { val posX = localPoint.x.absoluteValue @@ -45,4 +28,8 @@ class Cube(transformation: Matrix = Matrix.I, else -> Tuple.vector(0, 0, localPoint.z) } } + + override val bounds: BoundingBox by lazy { + BoundingBox(Tuple.point(-1, -1, -1), Tuple.point(1, 1, 1)) + } } diff --git a/src/main/kotlin/shapes/Cylinder.kt b/src/main/kotlin/shapes/Cylinder.kt index 412b4d7..16d9308 100644 --- a/src/main/kotlin/shapes/Cylinder.kt +++ b/src/main/kotlin/shapes/Cylinder.kt @@ -96,4 +96,8 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, else Tuple.vector(localPoint.x, 0, localPoint.z) } + + override val bounds: BoundingBox by lazy { + BoundingBox(Tuple.point(-1, minimum, -1), Tuple.point(1, maximum, 1)) + } } diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index 79768f6..4666a94 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -3,10 +3,10 @@ package shapes // By Sebastian Raaphorst, 2023. import material.Material +import math.* +import math.BoundingBox.Companion.MaxPoint +import math.BoundingBox.Companion.MinPoint import math.Intersection -import math.Matrix -import math.Ray -import math.Tuple class Group(transformation: Matrix = Matrix.I, children: List = emptyList(), @@ -16,6 +16,12 @@ class Group(transformation: Matrix = Matrix.I, // Make copies of all the children to backreference this as their parent. val children = children.map { it.withParent(this) } + val size = children.size + val isEmpty = children.isEmpty() + val isNotEmpty = children.isNotEmpty() + + operator fun get(idx: Int): Shape = + children[idx] operator fun contains(s: Shape): Boolean = s in children @@ -23,10 +29,19 @@ class Group(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Group(transformation, children, castsShadow, parent) + // Only process children if the ray intersects the bounding box for this group. override fun localIntersect(rayLocal: Ray): List = - children.flatMap { it.intersect(rayLocal) }.sortedBy { it.t } - - override fun localNormalAt(localPoint: Tuple): Tuple { - TODO("Not yet implemented") + if (bounds.intersects(rayLocal).isNotEmpty()) + children.flatMap { it.intersect(rayLocal) }.sortedBy { it.t } + else + emptyList() + + override fun localNormalAt(localPoint: Tuple): Tuple = + throw NotImplementedError("Groups do not have local normals.") + + override val bounds: BoundingBox by lazy { + children.fold(BoundingBox(MaxPoint, MinPoint)) { curr, shape -> + curr.merge(shape.parentBounds) + } } } diff --git a/src/main/kotlin/shapes/Plane.kt b/src/main/kotlin/shapes/Plane.kt index 0b9c159..215c09b 100644 --- a/src/main/kotlin/shapes/Plane.kt +++ b/src/main/kotlin/shapes/Plane.kt @@ -23,4 +23,11 @@ class Plane(transformation: Matrix = Matrix.I, override fun localNormalAt(localPoint: Tuple): Tuple = Tuple.VY + + override val bounds: BoundingBox by lazy { + BoundingBox( + Tuple.point(Double.NEGATIVE_INFINITY, 0, Double.NEGATIVE_INFINITY), + Tuple.point(Double.POSITIVE_INFINITY, 0, Double.POSITIVE_INFINITY) + ) + } } diff --git a/src/main/kotlin/shapes/Shape.kt b/src/main/kotlin/shapes/Shape.kt index d6aa6b4..deff8e8 100644 --- a/src/main/kotlin/shapes/Shape.kt +++ b/src/main/kotlin/shapes/Shape.kt @@ -3,11 +3,10 @@ package shapes // By Sebastian Raaphorst, 2023. import material.Material +import math.* import math.Intersection -import math.Matrix -import math.Ray -import math.Tuple import java.util.UUID +import kotlin.math.PI abstract class Shape(val transformation: Matrix, val material: Material, @@ -20,7 +19,8 @@ abstract class Shape(val transformation: Matrix, "\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}") } - abstract fun withParent(parent: Shape? = null): Shape + // This method should only be invoked by Groups containing the object. + internal abstract fun withParent(parent: Shape? = null): Shape // Convert a point from world space to object space. // Later on, we use parent here. @@ -43,7 +43,7 @@ abstract class Shape(val transformation: Matrix, internal abstract fun localIntersect(rayLocal: Ray): List - // normalAt transforms the point to object space and passes it to localNormalAt + // normalAt transforms the point from world to object (local) space and passes it to localNormalAt // which should comprise the concrete implementation of calculating the normal vector // at the point for the Shape. Then normalAt transforms it back into world space. internal fun normalAt(worldPoint: Tuple): Tuple { @@ -58,5 +58,14 @@ abstract class Shape(val transformation: Matrix, return normalToWorld(localNormal) } + // Normal at a point in object (local) space. + // Normal should be returned in local space, and normalAt handles transforming it back to world space. internal abstract fun localNormalAt(localPoint: Tuple): Tuple + + // Untransformed bounds for each Shape type. + internal abstract val bounds: BoundingBox + + internal val parentBounds: BoundingBox by lazy { + bounds.transform(transformation) + } } diff --git a/src/main/kotlin/shapes/Sphere.kt b/src/main/kotlin/shapes/Sphere.kt index aaf06ce..d68b062 100644 --- a/src/main/kotlin/shapes/Sphere.kt +++ b/src/main/kotlin/shapes/Sphere.kt @@ -3,10 +3,8 @@ package shapes // By Sebastian Raaphorst, 2023. import material.Material +import math.* import math.Intersection -import math.Matrix -import math.Ray -import math.Tuple import kotlin.math.sqrt class Sphere(transformation: Matrix = Matrix.I, @@ -38,6 +36,10 @@ class Sphere(transformation: Matrix = Matrix.I, override fun localNormalAt(localPoint: Tuple): Tuple = localPoint - Tuple.PZERO + override val bounds: BoundingBox by lazy { + BoundingBox(Tuple.point(-1, -1, -1), Tuple.point(1, 1, 1)) + } + companion object { internal fun glassSphere(transformation: Matrix = Matrix.I, transparency: Double = 1.0, diff --git a/src/test/kotlin/shapes/CubeTest.kt b/src/test/kotlin/shapes/CubeTest.kt index 8c9003c..c52c760 100644 --- a/src/test/kotlin/shapes/CubeTest.kt +++ b/src/test/kotlin/shapes/CubeTest.kt @@ -58,8 +58,7 @@ class CubeTest { Tuple.vector(0.5345, 0.8018, 0.2673), -Tuple.VZ, -Tuple.VY, -Tuple.VX) - origins.zip(directions).withIndex().forEach { (idx, rayInfo) -> - val (origin, direction) = rayInfo + origins.zip(directions).forEach { (origin, direction) -> val r = Ray(origin, direction) val xs = c.localIntersect(r) assertEquals(0, xs.size) diff --git a/src/test/kotlin/shapes/GroupTest.kt b/src/test/kotlin/shapes/GroupTest.kt index b88a3cb..e037f69 100644 --- a/src/test/kotlin/shapes/GroupTest.kt +++ b/src/test/kotlin/shapes/GroupTest.kt @@ -5,7 +5,9 @@ package shapes import math.Matrix import math.Ray import math.Tuple +import math.assertAlmostEquals import org.junit.jupiter.api.Test +import kotlin.math.PI import kotlin.test.* class GroupTest { @@ -13,15 +15,15 @@ class GroupTest { fun `New group`() { val g = Group() assertEquals(Matrix.I, g.transformation) - assertTrue(g.children.isEmpty()) + assertTrue(g.isEmpty) } @Test fun `New group with test shape`() { val s = ShapeTest.TestShape() val g = Group(children = listOf(s)) - assertEquals(1, g.children.size) - val sp = g.children[0] + assertEquals(1, g.size) + val sp = g[0] // Check that s has changed and been assigned the group as a parent. assertNotSame(s, sp) @@ -50,8 +52,8 @@ class GroupTest { val r = Ray(Tuple.point(0, 0, -5), Tuple.VZ) val xs = g.localIntersect(r) assertEquals(4, xs.size) - val sp1 = g.children[0] - val sp2 = g.children[1] + val sp1 = g[0] + val sp2 = g[1] assertSame(sp2, xs[0].shape) assertSame(sp2, xs[1].shape) assertSame(sp1, xs[2].shape) diff --git a/src/test/kotlin/shapes/ShapeTest.kt b/src/test/kotlin/shapes/ShapeTest.kt index bc1ccfa..b7fdf59 100644 --- a/src/test/kotlin/shapes/ShapeTest.kt +++ b/src/test/kotlin/shapes/ShapeTest.kt @@ -29,6 +29,13 @@ class ShapeTest { override fun localNormalAt(localPoint: Tuple): Tuple = localPoint - Tuple.PZERO + + override val bounds: BoundingBox by lazy { + BoundingBox( + Tuple.point(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY), + Tuple.point(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY) + ) + } } @Test @@ -91,4 +98,15 @@ class ShapeTest { val n = sNew.normalToWorld(Tuple.vector(sqrt3by3, sqrt3by3, sqrt3by3)) assertAlmostEquals(Tuple.vector(0.28571, 0.42857, -0.85714), n) } + + @Test + fun `Normal on an object in group`() { + val s = Sphere(Matrix.translate(5, 0, 0)) + val g2 = Group(Matrix.scale(1, 2, 3), listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + + val sp = (g1.children[0] as Group).children[0] + val n = sp.normalAt(Tuple.point(1.7321, 1.1547, -5.5774)) + assertAlmostEquals(Tuple.vector(0.28570, 0.42854, -0.85716), n) + } } From 6c20bbf9c35279946b268231250dbbdb8db0fb64 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Thu, 19 Jan 2023 01:39:57 -0500 Subject: [PATCH 4/9] Chapter 14: World rendered but not working. --- src/main/kotlin/apps/ch14_world.kt | 91 ++++++++++++ src/main/kotlin/apps/ch14_world.yaml | 205 +++++++++++++++++++++++++++ src/main/kotlin/shapes/Cone.kt | 3 + src/main/kotlin/shapes/Cube.kt | 3 + src/main/kotlin/shapes/Cylinder.kt | 3 + src/main/kotlin/shapes/Group.kt | 10 ++ src/main/kotlin/shapes/Plane.kt | 3 + src/main/kotlin/shapes/Shape.kt | 3 + src/main/kotlin/shapes/Sphere.kt | 3 + src/test/kotlin/shapes/ShapeTest.kt | 6 + 10 files changed, 330 insertions(+) create mode 100644 src/main/kotlin/apps/ch14_world.kt create mode 100644 src/main/kotlin/apps/ch14_world.yaml diff --git a/src/main/kotlin/apps/ch14_world.kt b/src/main/kotlin/apps/ch14_world.kt new file mode 100644 index 0000000..5a02094 --- /dev/null +++ b/src/main/kotlin/apps/ch14_world.kt @@ -0,0 +1,91 @@ +package apps + +// By Sebastian Raaphorst, 2023. +// From https://forum.raytracerchallenge.com/thread/13/groups-scene-description + +import light.PointLight +import material.Material +import math.Color +import math.Matrix +import math.Tuple +import scene.Camera +import scene.World +import shapes.* +import java.io.File +import kotlin.math.PI +import kotlin.system.measureTimeMillis + +fun main() { + val transforms = listOf(0, PI / 3, 2 * PI / 3, PI, 4 * PI / 3, 5 * PI / 3) + .map(Matrix::rotateY) + + val leg = run { + val sphere = Sphere(Matrix.translate(0, 0, -1) * Matrix.scale(0.25, 0.25, 0.25)) + val cylinder = Cylinder(0, 1, false, + Matrix.translate(0, 0, -1) * Matrix.rotateY(-PI / 6) * + Matrix.rotateZ(-PI / 2) * Matrix.scale(0.25, 1, 0.25)) + Group(children = listOf(sphere, cylinder)) + } + + val cap = run { + val trans = Matrix.rotateX(-PI / 4 ) * Matrix.scale(0.24606, 1.37002, 0.24606) + val cones = transforms.map { + Cone(-1, 0, false, it * trans) + } + Group(children = cones) + } + + val wacky = run { + val legs = transforms.map(leg::withTransformation) + val cap1 = cap.withTransformation(Matrix.translate(0, 1, 0)) + val cap2 = cap.withTransformation(Matrix.rotateX(PI) * Matrix.translate(0, 1, 0)) + Group(children = legs + listOf(cap1, cap2)) + } + + val backdrop = run { + val t = Matrix.translate(0, 0, 100) * Matrix.rotateX(PI / 2) + val m = Material(Color.WHITE, ambient = 1, diffuse = 0, specular = 0) + Plane(t, m) + } + + val wacky1 = run { + val t = Matrix.translate(-2.8, 0, 0) * Matrix.rotateX(0.4363) * Matrix.rotateY(PI / 18) + val m = Material(Color(0.9, 0.2, 0.4), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20) + wacky.withTransformation(t).withMaterial(m) + } + + val wacky2 = run { + val t = Matrix.rotateY(PI / 18) + val m = Material(Color(0.2, 0.9, 0.6), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20) + wacky.withTransformation(t).withMaterial(m) + } + + val wacky3 = run { + val t = Matrix.translate(2.8, 0, 0) * Matrix.rotateX(-0.4363) * Matrix.rotateY(-PI / 18) + val m = Material(Color(0.2, 0.3, 1), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20) + wacky.withTransformation(t).withMaterial(m) + } + + val world = run { + val color = Color(0.25, 0.25, 0.25) + val light1 = PointLight(Tuple.point(10_000, 10_000, -10_000), color) + val light2 = PointLight(Tuple.point(-10_000, 10_000, -10_000), color) + val light3 = PointLight(Tuple.point(10_000, -10_000, -10_000), color) + val light4 = PointLight(Tuple.point(-10_000, -10_000, -10_000), color) + World(listOf(backdrop, wacky1, wacky2, wacky3), listOf(light1, light2, light3, light4)) + } + + val camera = run { + val from = Tuple.point(0, 0, -9) + val to = Tuple.PZERO + val t = from.viewTransformationFrom(to, Tuple.VY) + Camera(2400, 1200, 0.9, t) + } + + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch14_world.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") +} + diff --git a/src/main/kotlin/apps/ch14_world.yaml b/src/main/kotlin/apps/ch14_world.yaml new file mode 100644 index 0000000..ad8053e --- /dev/null +++ b/src/main/kotlin/apps/ch14_world.yaml @@ -0,0 +1,205 @@ +# ====================================================== +# group.yml +# +# This file describes the scene illustrated at the start +# of chapter 14, "Groups", in "The Ray Tracer +# Challenge" +# +# This scene description assumes: +# +# 1. Your ray tracer supports multiple light sources. +# If it does not, you can omit the extra light +# sources and bump up the existing light's intensity +# to [1, 1, 1]. +# 2. Child objects in a group inherit their default +# material from the parent group. If you haven't +# implemented this optional feature, you'll need +# arrange other means of texturing the child +# elements (or accept that all elements of the +# scene will be white). +# +# by Jamis Buck +# ====================================================== + +# ====================================================== +# the camera +# ====================================================== + +- add: camera + width: 300 + height: 100 + field-of-view: 0.9 + from: [0, 0, -9] + to: [0, 0, 0] + up: [0, 1, 0] + +# ====================================================== +# light sources +# ====================================================== + +- add: light + at: [ 10000, 10000, -10000 ] + intensity: [ 0.25, 0.25, 0.25 ] +- add: light + at: [ -10000, 10000, -10000 ] + intensity: [ 0.25, 0.25, 0.25 ] +- add: light + at: [ 10000, -10000, -10000 ] + intensity: [ 0.25, 0.25, 0.25 ] +- add: light + at: [ -10000, -10000, -10000 ] + intensity: [ 0.25, 0.25, 0.25 ] + +# ====================================================== +# These describe groups that will be reused within +# the scene. (You can think of these as functions that +# return a new instance of the given shape each time they +# are referenced.) +# ====================================================== + +- define: leg + value: + add: group + children: + - add: sphere + transform: + - [ scale, 0.25, 0.25, 0.25 ] + - [ translate, 0, 0, -1 ] + - add: cylinder + min: 0 + max: 1 + closed: false + transform: + - [ scale, 0.25, 1, 0.25 ] + - [ rotate-z, -1.5708 ] + - [ rotate-y, -0.5236 ] + - [ translate, 0, 0, -1 ] + +- define: cap + value: + add: group + children: + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - [ rotate-y, 1.0472 ] + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - [ rotate-y, 2.0944 ] + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - [ rotate-y, 3.1416 ] + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - [ rotate-y, 4.1888 ] + - add: cone + min: -1 + max: 0 + closed: false + transform: + - [ scale, 0.24606, 1.37002, 0.24606 ] + - [ rotate-x, -0.7854 ] + - [ rotate-y, 5.236 ] + +- define: wacky + value: + add: group + children: + - add: leg + - add: leg + transform: + - [ rotate-y, 1.0472 ] + - add: leg + transform: + - [ rotate-y, 2.0944 ] + - add: leg + transform: + - [ rotate-y, 3.1416 ] + - add: leg + transform: + - [ rotate-y, 4.1888 ] + - add: leg + transform: + - [ rotate-y, 5.236 ] + - add: cap + transform: + - [ translate, 0, 1, 0 ] + - add: cap + transform: + - [ translate, 0, 1, 0 ] + - [ rotate-x, 3.1416 ] + +# ====================================================== +# Construct the scene itself +# ====================================================== + +# a white backdrop +- add: plane + transform: + - [ rotate-x, 1.5708 ] + - [ translate, 0, 0, 100 ] + material: + color: [ 1, 1, 1 ] + ambient: 1 + diffuse: 0 + specular: 0 + +- add: wacky + transform: + - [ rotate-y, 0.1745 ] + - [ rotate-x, 0.4363 ] + - [ translate, -2.8, 0, 0 ] + material: + color: [ 0.9, 0.2, 0.4 ] + ambient: 0.2 + diffuse: 0.8 + specular: 0.7 + shininess: 20 + +- add: wacky + transform: + - [ rotate-y, 0.1745 ] + material: + color: [ 0.2, 0.9, 0.6 ] + ambient: 0.2 + diffuse: 0.8 + specular: 0.7 + shininess: 20 + +- add: wacky + transform: + - [ rotate-y, -0.1745 ] + - [ rotate-x, -0.4363 ] + - [ translate, 2.8, 0, 0 ] + material: + color: [ 0.2, 0.3, 1.0 ] + ambient: 0.2 + diffuse: 0.8 + specular: 0.7 + shininess: 20 diff --git a/src/main/kotlin/shapes/Cone.kt b/src/main/kotlin/shapes/Cone.kt index 6f2d9cb..c00a7e8 100644 --- a/src/main/kotlin/shapes/Cone.kt +++ b/src/main/kotlin/shapes/Cone.kt @@ -24,6 +24,9 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, override fun withParent(parent: Shape?): Shape = Cone(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun withMaterial(material: Material): Shape = + Cone(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val a = rayLocal.direction.x * rayLocal.direction.x - rayLocal.direction.y * rayLocal.direction.y + diff --git a/src/main/kotlin/shapes/Cube.kt b/src/main/kotlin/shapes/Cube.kt index 0a764e1..e863d3b 100644 --- a/src/main/kotlin/shapes/Cube.kt +++ b/src/main/kotlin/shapes/Cube.kt @@ -15,6 +15,9 @@ class Cube(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Cube(transformation, material, castsShadow, parent) + override fun withMaterial(material: Material): Shape = + Cube(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List = bounds.intersects(rayLocal).map { Intersection(it, this) } diff --git a/src/main/kotlin/shapes/Cylinder.kt b/src/main/kotlin/shapes/Cylinder.kt index 16d9308..4bc9701 100644 --- a/src/main/kotlin/shapes/Cylinder.kt +++ b/src/main/kotlin/shapes/Cylinder.kt @@ -22,6 +22,9 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, override fun withParent(parent: Shape?): Shape = Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun withMaterial(material: Material): Shape = + Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val a = rayLocal.direction.x * rayLocal.direction.x + rayLocal.direction.z * rayLocal.direction.z diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index 4666a94..c1097a7 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -29,6 +29,16 @@ class Group(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Group(transformation, children, castsShadow, parent) + fun withTransformation(transformation: Matrix): Shape { + if (!transformation.isTransformation()) + throw IllegalArgumentException("Shapes must have 4x4 transformation matrices:\n" + + "\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}") + return Group(transformation, children, castsShadow, parent) + } + + override fun withMaterial(material: Material): Shape = + Group(transformation, children.map { it.withMaterial(material) }, castsShadow, parent) + // Only process children if the ray intersects the bounding box for this group. override fun localIntersect(rayLocal: Ray): List = if (bounds.intersects(rayLocal).isNotEmpty()) diff --git a/src/main/kotlin/shapes/Plane.kt b/src/main/kotlin/shapes/Plane.kt index 215c09b..7ea0764 100644 --- a/src/main/kotlin/shapes/Plane.kt +++ b/src/main/kotlin/shapes/Plane.kt @@ -14,6 +14,9 @@ class Plane(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Plane(transformation, material, castsShadow, parent) + override fun withMaterial(material: Material): Shape = + Plane(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { if (rayLocal.direction.y.absoluteValue < DEFAULT_PRECISION) return emptyList() diff --git a/src/main/kotlin/shapes/Shape.kt b/src/main/kotlin/shapes/Shape.kt index deff8e8..84b22eb 100644 --- a/src/main/kotlin/shapes/Shape.kt +++ b/src/main/kotlin/shapes/Shape.kt @@ -22,6 +22,9 @@ abstract class Shape(val transformation: Matrix, // This method should only be invoked by Groups containing the object. internal abstract fun withParent(parent: Shape? = null): Shape + // This method creates a copy of the object with the specified material. + abstract fun withMaterial(material: Material): Shape + // Convert a point from world space to object space. // Later on, we use parent here. internal fun worldToLocal(tuple: Tuple): Tuple = diff --git a/src/main/kotlin/shapes/Sphere.kt b/src/main/kotlin/shapes/Sphere.kt index d68b062..51d3778 100644 --- a/src/main/kotlin/shapes/Sphere.kt +++ b/src/main/kotlin/shapes/Sphere.kt @@ -15,6 +15,9 @@ class Sphere(transformation: Matrix = Matrix.I, override fun withParent(parent: Shape?): Shape = Sphere(transformation, material, castsShadow, parent) + override fun withMaterial(material: Material): Shape = + Sphere(transformation, material, castsShadow, parent) + override fun localIntersect(rayLocal: Ray): List { val sphereToRay = rayLocal.origin - Tuple.PZERO diff --git a/src/test/kotlin/shapes/ShapeTest.kt b/src/test/kotlin/shapes/ShapeTest.kt index b7fdf59..81bd4bf 100644 --- a/src/test/kotlin/shapes/ShapeTest.kt +++ b/src/test/kotlin/shapes/ShapeTest.kt @@ -22,6 +22,12 @@ class ShapeTest { return s } + override fun withMaterial(material: Material): Shape { + val s = TestShape(transformation, material, parent) + s.savedRay = savedRay + return s + } + override fun localIntersect(rayLocal: Ray): List { savedRay = rayLocal return emptyList() From 83ff57e86f36319e29d794c1678273ba23cb10ee Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Fri, 20 Jan 2023 13:57:12 -1000 Subject: [PATCH 5/9] Chapter 14: Added convenience functions and test cases on groups to make sure things change or do not change as expected. --- src/main/kotlin/shapes/Group.kt | 67 ++++++++++++++++++++++++++-- src/test/kotlin/shapes/GroupTest.kt | 68 +++++++++++++++++++++++++++-- src/test/kotlin/shapes/ShapeTest.kt | 12 ++--- 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index c1097a7..681d2ad 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -7,12 +7,14 @@ import math.* import math.BoundingBox.Companion.MaxPoint import math.BoundingBox.Companion.MinPoint import math.Intersection +import kotlin.math.PI class Group(transformation: Matrix = Matrix.I, + material: Material = Material(), children: List = emptyList(), castsShadow: Boolean = true, parent: Shape? = null): - Shape(transformation, Material(), castsShadow, parent) { + Shape(transformation, material, castsShadow, parent) { // Make copies of all the children to backreference this as their parent. val children = children.map { it.withParent(this) } @@ -27,17 +29,42 @@ class Group(transformation: Matrix = Matrix.I, s in children override fun withParent(parent: Shape?): Shape = - Group(transformation, children, castsShadow, parent) + Group(transformation, material, children, castsShadow, parent) fun withTransformation(transformation: Matrix): Shape { if (!transformation.isTransformation()) throw IllegalArgumentException("Shapes must have 4x4 transformation matrices:\n" + "\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}") - return Group(transformation, children, castsShadow, parent) + return Group(transformation, material, children, castsShadow, parent) } + fun forEach(f: (Shape) -> Unit): Unit { + children.forEach(f) + } + + fun map(f: (Shape) -> T): Iterable = + children.map(f) + + fun all(predicate: (Shape) -> Boolean): Boolean = + children.all(predicate) + + fun none(predicate: (Shape) -> Boolean): Boolean = + children.none(predicate) + + fun any(predicate: (Shape) -> Boolean): Boolean = + children.any(predicate) + + fun zip(other: Iterable): Iterable> = + children.zip(other) + + fun zip(group: Group): Iterable> = + children.zip(group.children) + + fun withIndex(group: Group): Iterable> = + children.withIndex() + override fun withMaterial(material: Material): Shape = - Group(transformation, children.map { it.withMaterial(material) }, castsShadow, parent) + Group(transformation, material, children.map { it.withMaterial(material) }, castsShadow, parent) // Only process children if the ray intersects the bounding box for this group. override fun localIntersect(rayLocal: Ray): List = @@ -55,3 +82,35 @@ class Group(transformation: Matrix = Matrix.I, } } } + + +//fun main() { +// val g1 = run { +// val s1 = Sphere(transformation = Matrix.translate(-1, -1, -1) * Matrix.scale(0.5, 0.5, 0.5)) +// val c1 = Cylinder(transformation = Matrix.rotateY(PI / 2) * Matrix.scale(0.33, 0.33, 0.33)) +// Group(children = listOf(s1, c1)) +// } +// +// // g1 is no longer relevant: +// // 1. The children of g1g should have g1g as their parent. +// // 2. The parent of g1g should be g2. +// val g2 = Group(Matrix.scale(0.1, 0.1, 0.1), listOf(g1)) +// val g1g = g2.children[0] as Group +// +// // 1. The children of g1p should have g1p as their parent. +// // 2. The parent of g1p should be g2p. +// val g2p = g2.withTransformation(Matrix.rotateX(PI)) +// val g1p = g2.children[0] as Group +// +// // Parents +// Check to see if g2's children have changed but maintain the same fundamental properties as g1's children. +// g1.zip(g1p).forEach { (s1, s2) -> +// assertNotSame(s1.parent, s2.parent) +// assertSame(g1, s1.parent) +// assertSame(g1p, s2.parent) +// } +// assertSame(g2, g1.parent) +// assertSame(g2p, g1p.parent) +// println("g2: $g2, g1.parent: ${g1.parent}") +// println() +//} \ No newline at end of file diff --git a/src/test/kotlin/shapes/GroupTest.kt b/src/test/kotlin/shapes/GroupTest.kt index e037f69..b65863f 100644 --- a/src/test/kotlin/shapes/GroupTest.kt +++ b/src/test/kotlin/shapes/GroupTest.kt @@ -2,10 +2,8 @@ package shapes // By Sebastian Raaphorst, 2023. -import math.Matrix -import math.Ray -import math.Tuple -import math.assertAlmostEquals +import material.Material +import math.* import org.junit.jupiter.api.Test import kotlin.math.PI import kotlin.test.* @@ -69,4 +67,66 @@ class GroupTest { val xs = g.intersect(r) assertEquals(2, xs.size) } + + @Test + fun `Calling withXXX properties on groups`() { + val g1 = run { + val s1 = Sphere(transformation = Matrix.translate(-1, -1, -1) * Matrix.scale(0.5, 0.5, 0.5)) + val c1 = Cylinder(transformation = Matrix.rotateY(PI / 2) * Matrix.scale(0.33, 0.33, 0.33)) + Group(children = listOf(s1, c1)) + } + + // g1 is no longer relevant: + // 1. The children of g1g should have g1g as their parent. + // 2. The parent of g1g should be g2. + // 3. The transformations of g1 and g1g should still be the same. + val g2 = Group(Matrix.scale(0.1, 0.1, 0.1), children = listOf(g1)) + val g1g = g2.children[0] as Group + + assertSame(g2, g1g.parent) + g1g.forEach { assertSame(g1g, it.parent) } + g1.zip(g1g).forEach { (s1, s2) -> + assertNotSame(s1, s2) + assertSame(s1.transformation, s2.transformation) + } + + // 1. The children of g1p should have g1p as their parent. + // 2. The parent of g1p should be g2p. + // 3. The transformations of g1 and g1p should still be the same. + val g2p = g2.withTransformation(Matrix.rotateX(PI)) as Group + val g1p = g2p.children[0] as Group + + assertSame(g2p, g1p.parent) + g1p.forEach { assertSame(g1p, it.parent) } + g1.zip(g1p).forEach { (s1, s2) -> + assertNotSame(s1, s2) + assertSame(s1.transformation, s2.transformation) + } + + // Setting a material on g2p should set the exact same material through g2p's children. + val m = Material(Color.BLUE, ambient = 0.5, specular = 0.2, shininess = 100.0) + val g2m = g2p.withMaterial(m) as Group + val g1m = g2m.children[0] as Group + + assertSame(m, g2m.material) + assertSame(m, g1m.material) + g1m.forEach { assertSame(m, it.material) } + + assertNotSame(m, g2.material) + assertNotSame(m, g2p.material) + assertNotSame(m, g1g.material) + assertNotSame(m, g1p.material) + + // Make sure the transformations on the shapes have not changed. + g1.zip(g1g).forEach { (s1, s2) -> assertSame(s1.transformation, s2.transformation) } + g1.zip(g1p).forEach { (s1, s2) -> assertSame(s1.transformation, s2.transformation) } + g1.zip(g1m).forEach { (s1, s2) -> assertSame(s1.transformation, s2.transformation) } + + // Make sure the transformations on the groups have / have not changed. + assertSame(g1.transformation, g1g.transformation) + assertSame(g1.transformation, g1p.transformation) + assertSame(g1.transformation, g1m.transformation) + assertNotSame(g2.transformation, g2p.transformation) + assertSame(g2m.transformation, g2p.transformation) + } } diff --git a/src/test/kotlin/shapes/ShapeTest.kt b/src/test/kotlin/shapes/ShapeTest.kt index 81bd4bf..82daeeb 100644 --- a/src/test/kotlin/shapes/ShapeTest.kt +++ b/src/test/kotlin/shapes/ShapeTest.kt @@ -83,8 +83,8 @@ class ShapeTest { @Test fun `Point from world to local space`() { val s = Sphere(Matrix.translate(5, 0, 0)) - val g2 = Group(Matrix.scale(2, 2, 2), listOf(s)) - val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + val g2 = Group(Matrix.scale(2, 2, 2), children = listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), children = listOf(g2)) // We have to get the new sphere to have the parent set. val sNew = (g1.children[0] as Group).children[0] @@ -95,8 +95,8 @@ class ShapeTest { @Test fun `Normal from local to world space`() { val s = Sphere(Matrix.translate(5, 0, 0)) - val g2 = Group(Matrix.scale(1, 2, 3), listOf(s)) - val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + val g2 = Group(Matrix.scale(1, 2, 3), children = listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), children = listOf(g2)) // We have to get the new sphere to have the parent set. val sNew = (g1.children[0] as Group).children[0] @@ -108,8 +108,8 @@ class ShapeTest { @Test fun `Normal on an object in group`() { val s = Sphere(Matrix.translate(5, 0, 0)) - val g2 = Group(Matrix.scale(1, 2, 3), listOf(s)) - val g1 = Group(Matrix.rotateY(PI / 2), listOf(g2)) + val g2 = Group(Matrix.scale(1, 2, 3), children = listOf(s)) + val g1 = Group(Matrix.rotateY(PI / 2), children = listOf(g2)) val sp = (g1.children[0] as Group).children[0] val n = sp.normalAt(Tuple.point(1.7321, 1.1547, -5.5774)) From fe13b76dfee8642e10e208df940837f652b4ac75 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Fri, 20 Jan 2023 15:49:06 -1000 Subject: [PATCH 6/9] Chapter 14: Narrowing down problems. --- src/main/kotlin/apps/ch13_groups.kt | 118 ++++++++++++++++++++++++++++ src/main/kotlin/apps/ch14_world.kt | 2 +- src/main/kotlin/math/BoundingBox.kt | 2 + src/main/kotlin/shapes/Group.kt | 39 +-------- 4 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 src/main/kotlin/apps/ch13_groups.kt diff --git a/src/main/kotlin/apps/ch13_groups.kt b/src/main/kotlin/apps/ch13_groups.kt new file mode 100644 index 0000000..15c8447 --- /dev/null +++ b/src/main/kotlin/apps/ch13_groups.kt @@ -0,0 +1,118 @@ +package apps + +// By Sebastian Raaphorst, 2023. +// From https://forum.raytracerchallenge.com/thread/7/cylinders-scene-description + +import light.PointLight +import material.Material +import math.Color +import math.Matrix +import math.Tuple +import pattern.CheckerPattern +import scene.Camera +import scene.World +import shapes.Cylinder +import shapes.Group +import shapes.Plane +import java.io.File +import kotlin.math.PI +import kotlin.system.measureTimeMillis + +fun main() { + val flooring = run { + val t = Matrix.rotateY(0.3) * Matrix.scale(0.25, 0.25, 0.25) + val p = CheckerPattern(Color(0.5, 0.5, 0.5), Color(0.75, 0.75, 0.75), t) + val m = Material(p, ambient = 0.2, diffuse = 0.9, specular = 0.0) + Plane(Matrix.I, m) + } + + val cylinder = run { + val t = Matrix.translate(-1, 0, 1) * Matrix.scale(0.5, 1 ,0.5) + val m = Material(Color(0, 0, 0.6), diffuse = 0.1, + specular = 0.9, shininess = 300.0, reflectivity = 0.9) + Cylinder(0, 0.75, true, t, m) + } + + val concentricCylinders = run { + val gt = Matrix.translate(1, 0, 0) + val m = Material(Color(1, 1, 0.3), ambient = 0.1, + diffuse = 0.8, specular = 0.9, shininess = 300.0) + + val concentricCylinder1 = run { + val t = Matrix.scale(0.8, 1, 0.8) + Cylinder(0, 0.2, false, t) + } + + val concentricCylinder2 = run { + val t = Matrix.scale(0.6, 1, 0.6) + Cylinder(0, 0.3, false, t) + } + + val concentricCylinder3 = run { + val t = Matrix.scale(0.4, 1, 0.4) + Cylinder(0, 0.4, false, t) + } + + val concentricCylinder4 = run { + val t = Matrix.scale(0.2, 1, 0.2) + Cylinder(0, 0.5, true, t) + } + + Group(gt, m, listOf(concentricCylinder1, concentricCylinder2, concentricCylinder3, concentricCylinder4)) + } + + val decorativeCylinder1 = run { + val t = Matrix.translate(0, 0, -0.75) * Matrix.scale(0.05, 1, 0.05) + val m = Material(Color.RED, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) + Cylinder(0, 0.3, true, t, m) + } + + val decorativeCylinder2 = run { + val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.15) * + Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) + val m = Material(Color.YELLOW, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) + Cylinder(0, 0.3, true, t, m) + } + + val decorativeCylinder3 = run { + val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.3) * + Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) + val m = Material(Color.GREEN, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) + Cylinder(0, 0.3, true, t, m) + } + + val decorativeCylinder4 = run { + val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.45) * + Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) + val m = Material(Color.CYAN, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) + Cylinder(0, 0.3, true, t, m) + } + + val glassCylinder = run { + val t = Matrix.translate(0, 0, -1.5) * Matrix.scale(0.33, 1, 0.33) + val m = Material(Color(0.25, 0, 0), diffuse = 0.1, specular = 0.9, + shininess = 300.0, reflectivity = 0.9, transparency = 0.9, refractiveIndex = 1.5) + Cylinder(0.0001, 0.5, true, t, m) + } + + val world = run { + val light = PointLight(Tuple.point(1, 6.9, -4.9)) + World(listOf(flooring, cylinder, + concentricCylinders, + decorativeCylinder1, decorativeCylinder2, decorativeCylinder3, decorativeCylinder4, + glassCylinder), light) + } + + val camera = run { + val from = Tuple.point(8, 3.5, -9) + val to = Tuple.point(0, 0.3, 0) + val t = from.viewTransformationFrom(to, Tuple.VY) + Camera(2400, 1200, PI / 10, t) + } + + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch13_groups.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") +} diff --git a/src/main/kotlin/apps/ch14_world.kt b/src/main/kotlin/apps/ch14_world.kt index 5a02094..8577749 100644 --- a/src/main/kotlin/apps/ch14_world.kt +++ b/src/main/kotlin/apps/ch14_world.kt @@ -79,7 +79,7 @@ fun main() { val from = Tuple.point(0, 0, -9) val to = Tuple.PZERO val t = from.viewTransformationFrom(to, Tuple.VY) - Camera(2400, 1200, 0.9, t) + Camera(300, 100, 0.9, t) } val elapsed = measureTimeMillis { diff --git a/src/main/kotlin/math/BoundingBox.kt b/src/main/kotlin/math/BoundingBox.kt index f9347a2..88f71ea 100644 --- a/src/main/kotlin/math/BoundingBox.kt +++ b/src/main/kotlin/math/BoundingBox.kt @@ -33,6 +33,8 @@ data class BoundingBox(val minPoint: Tuple = MaxPoint, val maxPoint: Tuple = Min add(box.minPoint).add(box.maxPoint) fun transform(transformation: Matrix): BoundingBox { + if (!transformation.isTransformation()) + throw IllegalArgumentException("Cannot transform a bounding box by a non-transform matrix.") val corners = listOf( minPoint, Tuple.point(maxPoint.x, minPoint.y, minPoint.z), diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index 681d2ad..58d7d3d 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -77,40 +77,9 @@ class Group(transformation: Matrix = Matrix.I, throw NotImplementedError("Groups do not have local normals.") override val bounds: BoundingBox by lazy { - children.fold(BoundingBox(MaxPoint, MinPoint)) { curr, shape -> - curr.merge(shape.parentBounds) - } +// children.fold(BoundingBox(MaxPoint, MinPoint)) { curr, shape -> +// curr.merge(shape.parentBounds) +// } + BoundingBox(MinPoint, MaxPoint) } } - - -//fun main() { -// val g1 = run { -// val s1 = Sphere(transformation = Matrix.translate(-1, -1, -1) * Matrix.scale(0.5, 0.5, 0.5)) -// val c1 = Cylinder(transformation = Matrix.rotateY(PI / 2) * Matrix.scale(0.33, 0.33, 0.33)) -// Group(children = listOf(s1, c1)) -// } -// -// // g1 is no longer relevant: -// // 1. The children of g1g should have g1g as their parent. -// // 2. The parent of g1g should be g2. -// val g2 = Group(Matrix.scale(0.1, 0.1, 0.1), listOf(g1)) -// val g1g = g2.children[0] as Group -// -// // 1. The children of g1p should have g1p as their parent. -// // 2. The parent of g1p should be g2p. -// val g2p = g2.withTransformation(Matrix.rotateX(PI)) -// val g1p = g2.children[0] as Group -// -// // Parents -// Check to see if g2's children have changed but maintain the same fundamental properties as g1's children. -// g1.zip(g1p).forEach { (s1, s2) -> -// assertNotSame(s1.parent, s2.parent) -// assertSame(g1, s1.parent) -// assertSame(g1p, s2.parent) -// } -// assertSame(g2, g1.parent) -// assertSame(g2p, g1p.parent) -// println("g2: $g2, g1.parent: ${g1.parent}") -// println() -//} \ No newline at end of file From 7ebc3911afb6af324924b42d8f6853dd2d2a82b3 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Fri, 20 Jan 2023 16:59:59 -1000 Subject: [PATCH 7/9] Chapter 14: Shapes may now have null materials which delegate to parents or the default material. --- src/main/kotlin/apps/ch13_groups.kt | 2 +- src/main/kotlin/shapes/Cone.kt | 5 +++-- src/main/kotlin/shapes/Cube.kt | 6 ++++-- src/main/kotlin/shapes/Cylinder.kt | 5 +++-- src/main/kotlin/shapes/Group.kt | 10 +++++----- src/main/kotlin/shapes/Plane.kt | 6 ++++-- src/main/kotlin/shapes/Shape.kt | 14 +++++++++++++- src/main/kotlin/shapes/Sphere.kt | 6 ++++-- 8 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/apps/ch13_groups.kt b/src/main/kotlin/apps/ch13_groups.kt index 15c8447..4fdb6ab 100644 --- a/src/main/kotlin/apps/ch13_groups.kt +++ b/src/main/kotlin/apps/ch13_groups.kt @@ -58,7 +58,7 @@ fun main() { Cylinder(0, 0.5, true, t) } - Group(gt, m, listOf(concentricCylinder1, concentricCylinder2, concentricCylinder3, concentricCylinder4)) + Group(gt, m, children=listOf(concentricCylinder1, concentricCylinder2, concentricCylinder3, concentricCylinder4)) } val decorativeCylinder1 = run { diff --git a/src/main/kotlin/shapes/Cone.kt b/src/main/kotlin/shapes/Cone.kt index c00a7e8..a2481d2 100644 --- a/src/main/kotlin/shapes/Cone.kt +++ b/src/main/kotlin/shapes/Cone.kt @@ -13,7 +13,7 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, maximum: Number = Double.POSITIVE_INFINITY, val closed: Boolean = false, transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { @@ -21,8 +21,9 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, val minimum = minimum.toDouble() val maximum = maximum.toDouble() + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Cone(minimum, maximum, closed, transformation, material, castsShadow, parent) + Cone(minimum, maximum, closed, transformation, objMaterial, castsShadow, parent) override fun withMaterial(material: Material): Shape = Cone(minimum, maximum, closed, transformation, material, castsShadow, parent) diff --git a/src/main/kotlin/shapes/Cube.kt b/src/main/kotlin/shapes/Cube.kt index e863d3b..9fffca8 100644 --- a/src/main/kotlin/shapes/Cube.kt +++ b/src/main/kotlin/shapes/Cube.kt @@ -8,12 +8,14 @@ import math.Intersection import kotlin.math.absoluteValue class Cube(transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { + + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Cube(transformation, material, castsShadow, parent) + Cube(transformation, objMaterial, castsShadow, parent) override fun withMaterial(material: Material): Shape = Cube(transformation, material, castsShadow, parent) diff --git a/src/main/kotlin/shapes/Cylinder.kt b/src/main/kotlin/shapes/Cylinder.kt index 4bc9701..4632cd1 100644 --- a/src/main/kotlin/shapes/Cylinder.kt +++ b/src/main/kotlin/shapes/Cylinder.kt @@ -11,7 +11,7 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, maximum: Number = Double.POSITIVE_INFINITY, val closed: Boolean = false, transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { @@ -19,8 +19,9 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, val minimum = minimum.toDouble() val maximum = maximum.toDouble() + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent) + Cylinder(minimum, maximum, closed, transformation, objMaterial, castsShadow, parent) override fun withMaterial(material: Material): Shape = Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent) diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index 58d7d3d..264ba6e 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -7,17 +7,16 @@ import math.* import math.BoundingBox.Companion.MaxPoint import math.BoundingBox.Companion.MinPoint import math.Intersection -import kotlin.math.PI class Group(transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, children: List = emptyList(), castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { // Make copies of all the children to backreference this as their parent. - val children = children.map { it.withParent(this) } + val children = run { children.map { it.withParent(this) } } val size = children.size val isEmpty = children.isEmpty() val isNotEmpty = children.isNotEmpty() @@ -28,8 +27,9 @@ class Group(transformation: Matrix = Matrix.I, operator fun contains(s: Shape): Boolean = s in children + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Group(transformation, material, children, castsShadow, parent) + Group(transformation, objMaterial, children, castsShadow, parent) fun withTransformation(transformation: Matrix): Shape { if (!transformation.isTransformation()) @@ -38,7 +38,7 @@ class Group(transformation: Matrix = Matrix.I, return Group(transformation, material, children, castsShadow, parent) } - fun forEach(f: (Shape) -> Unit): Unit { + fun forEach(f: (Shape) -> Unit) { children.forEach(f) } diff --git a/src/main/kotlin/shapes/Plane.kt b/src/main/kotlin/shapes/Plane.kt index 7ea0764..8e34c2c 100644 --- a/src/main/kotlin/shapes/Plane.kt +++ b/src/main/kotlin/shapes/Plane.kt @@ -7,12 +7,14 @@ import math.* import kotlin.math.absoluteValue class Plane(transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { + + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Plane(transformation, material, castsShadow, parent) + Plane(transformation, objMaterial, castsShadow, parent) override fun withMaterial(material: Material): Shape = Plane(transformation, material, castsShadow, parent) diff --git a/src/main/kotlin/shapes/Shape.kt b/src/main/kotlin/shapes/Shape.kt index 84b22eb..cfb5d9a 100644 --- a/src/main/kotlin/shapes/Shape.kt +++ b/src/main/kotlin/shapes/Shape.kt @@ -9,7 +9,7 @@ import java.util.UUID import kotlin.math.PI abstract class Shape(val transformation: Matrix, - val material: Material, + material: Material? = null, val castsShadow: Boolean, val parent: Shape?, private val id: UUID = UUID.randomUUID()) { @@ -19,6 +19,14 @@ abstract class Shape(val transformation: Matrix, "\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}") } + // We need to store the parameter passed in for material here in order to propagate it correctly. + // We will access it below: if an object does not have a material, it will try to see if it has a parent + // with a material. + protected val objMaterial = material + + val material: Material + get() = objMaterial ?: (parent?.objMaterial ?: DefaultMaterial) + // This method should only be invoked by Groups containing the object. internal abstract fun withParent(parent: Shape? = null): Shape @@ -71,4 +79,8 @@ abstract class Shape(val transformation: Matrix, internal val parentBounds: BoundingBox by lazy { bounds.transform(transformation) } + + companion object { + private val DefaultMaterial = Material() + } } diff --git a/src/main/kotlin/shapes/Sphere.kt b/src/main/kotlin/shapes/Sphere.kt index 51d3778..8724dc7 100644 --- a/src/main/kotlin/shapes/Sphere.kt +++ b/src/main/kotlin/shapes/Sphere.kt @@ -8,12 +8,14 @@ import math.Intersection import kotlin.math.sqrt class Sphere(transformation: Matrix = Matrix.I, - material: Material = Material(), + material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): Shape(transformation, material, castsShadow, parent) { + + // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = - Sphere(transformation, material, castsShadow, parent) + Sphere(transformation, objMaterial, castsShadow, parent) override fun withMaterial(material: Material): Shape = Sphere(transformation, material, castsShadow, parent) From 6b86ac7f1dc8ce1bf13c3e0744607b3da6d26c42 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Fri, 20 Jan 2023 17:37:54 -1000 Subject: [PATCH 8/9] Chapter 14: Fixed bounding box calculation error. --- src/main/kotlin/apps/ch14_world.kt | 2 +- src/main/kotlin/math/BoundingBox.kt | 9 +++++++-- src/main/kotlin/shapes/Group.kt | 11 +++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/apps/ch14_world.kt b/src/main/kotlin/apps/ch14_world.kt index 8577749..706d25a 100644 --- a/src/main/kotlin/apps/ch14_world.kt +++ b/src/main/kotlin/apps/ch14_world.kt @@ -79,7 +79,7 @@ fun main() { val from = Tuple.point(0, 0, -9) val to = Tuple.PZERO val t = from.viewTransformationFrom(to, Tuple.VY) - Camera(300, 100, 0.9, t) + Camera(1200, 400, 0.9, t) } val elapsed = measureTimeMillis { diff --git a/src/main/kotlin/math/BoundingBox.kt b/src/main/kotlin/math/BoundingBox.kt index 88f71ea..f198f8f 100644 --- a/src/main/kotlin/math/BoundingBox.kt +++ b/src/main/kotlin/math/BoundingBox.kt @@ -5,6 +5,9 @@ package math import kotlin.math.max import kotlin.math.min +// Unless otherwise stated, a bounding box is empty, as indicated by setting the +// 1. minPoint to INF, INF, INF +// 2. maxPoint to -INF, -INF, -INF data class BoundingBox(val minPoint: Tuple = MaxPoint, val maxPoint: Tuple = MinPoint) { init { if (!minPoint.isPoint()) @@ -20,12 +23,12 @@ data class BoundingBox(val minPoint: Tuple = MaxPoint, val maxPoint: Tuple = Min !isEmpty } - fun add(point: Tuple): BoundingBox { + private fun add(point: Tuple): BoundingBox { if (!point.isPoint()) throw IllegalArgumentException("Tried to add vector to BoundingBox: $point.") return BoundingBox( Tuple.point(min(minPoint.x, point.x), min(minPoint.y, point.y), min(minPoint.z, point.z)), - Tuple.point(max(maxPoint.x, point.x), min(maxPoint.y, point.y), min(maxPoint.z, point.z)) + Tuple.point(max(maxPoint.x, point.x), max(maxPoint.y, point.y), max(maxPoint.z, point.z)) ) } @@ -102,5 +105,7 @@ data class BoundingBox(val minPoint: Tuple = MaxPoint, val maxPoint: Tuple = Min Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY ) + + internal val Empty = BoundingBox(MaxPoint, MinPoint) } } diff --git a/src/main/kotlin/shapes/Group.kt b/src/main/kotlin/shapes/Group.kt index 264ba6e..f34a8ff 100644 --- a/src/main/kotlin/shapes/Group.kt +++ b/src/main/kotlin/shapes/Group.kt @@ -4,8 +4,6 @@ package shapes import material.Material import math.* -import math.BoundingBox.Companion.MaxPoint -import math.BoundingBox.Companion.MinPoint import math.Intersection class Group(transformation: Matrix = Matrix.I, @@ -77,9 +75,10 @@ class Group(transformation: Matrix = Matrix.I, throw NotImplementedError("Groups do not have local normals.") override val bounds: BoundingBox by lazy { -// children.fold(BoundingBox(MaxPoint, MinPoint)) { curr, shape -> -// curr.merge(shape.parentBounds) -// } - BoundingBox(MinPoint, MaxPoint) + // At first, the bounds are a completely empty box, with: + // 1. minPoint at INF, INF, INF + // 2. maxPoint at -INF, -INF, -INF. + // We make the space larger from the children. + children.fold(BoundingBox.Empty) { curr, shape -> curr.merge(shape.parentBounds) } } } From 8ed00a546570a1da60339c4a6dde139a6188ba61 Mon Sep 17 00:00:00 2001 From: Sebastian Raaphorst Date: Fri, 20 Jan 2023 18:39:03 -1000 Subject: [PATCH 9/9] Chapter 14: Added hexagon example and major refactor of Cylinder and Cone into Cappable. --- output/ch14_world.png | Bin 0 -> 198686 bytes .../apps/{shared.kt => ch01_02_shared.kt} | 0 src/main/kotlin/apps/ch13_groups.kt | 118 ---------- src/main/kotlin/apps/ch14_hexagon.kt | 40 ++++ src/main/kotlin/apps/ch14_shared.kt | 20 ++ src/main/kotlin/apps/ch14_world.kt | 10 +- src/main/kotlin/apps/ch14_world.yaml | 205 ------------------ src/main/kotlin/scene/Camera.kt | 7 +- src/main/kotlin/shapes/Cappable.kt | 69 ++++++ src/main/kotlin/shapes/Cone.kt | 59 +---- src/main/kotlin/shapes/Cylinder.kt | 60 +---- 11 files changed, 144 insertions(+), 444 deletions(-) create mode 100644 output/ch14_world.png rename src/main/kotlin/apps/{shared.kt => ch01_02_shared.kt} (100%) delete mode 100644 src/main/kotlin/apps/ch13_groups.kt create mode 100644 src/main/kotlin/apps/ch14_hexagon.kt create mode 100644 src/main/kotlin/apps/ch14_shared.kt delete mode 100644 src/main/kotlin/apps/ch14_world.yaml create mode 100644 src/main/kotlin/shapes/Cappable.kt diff --git a/output/ch14_world.png b/output/ch14_world.png new file mode 100644 index 0000000000000000000000000000000000000000..5f06e7e4d78abcbecb482dc7c33ffd70a2a1462b GIT binary patch literal 198686 zcmeFZRX|&7_C8E0Efkjm#T|;fTXC0SL4&5aJ4K2WDDF~R3ju;;?2~$;;K|>})hJ%AclarNHgM&k;fP;G$gY*)1r@1y2 z2>bKQSxrV9u6&4i2lhb~q%HSBNePYtc8vrF{|p=M`BM|vFIno+4CaKmW(|vzVubKWnjvVV&q$YG}J?D=7+?+S{@io7tOySUhYUo*KXj zc?iHRZ9y)^hlP!WjZy@e zoSa<9$?SuGnxyn!Ux$4XrnGQzaS&i-b$54Xapz>QcQR*X=jZ2VW#eGw;9!QeV0QMj zb20W{wsWTXvy*@HBMEXgb+UADv9z}%f9luR#NO3Kn3D2opud0qoTrQBhkuS_=ls{Q zU<+h@dcw-i!p8b{->|O=J>3;hak2!#Mt60sV((|M(XCGaT&)ivQ99tdWg1f})Y_ z-u!wS!G9hE)+VL(#QwidLfS}=$_U;IR=GgGix&P{#q4&?mr8|J^c{QvCX-+i>!!DcyC*DOq${_p$zcbiYI zm>ysLztjK6^Ez&L+KRgKgX5L|-bT^rn)g4Q<)3|2lwq;?*Ou8wBbxvBGllPO`L9Ry z#@{yb)2mWjpN~R(|50ZC7zZX2X#W$5r$hRmNc^Q!{}YLS=+ys2;(sFXZwT=JLy_>; z3vF$DXftGy@!OcUHx9S!`#<3OZ+KfTg#_Ir^Zn+1vs2W+Ha`5dqd_XAf#6>d;?onh zq5lhR{bs=8ySrajTcSVNb@yA%O5n)tYS;tqR2M0lG|B55EdI-QxG?y}eMJ`ePjvl_ zjLCOlx~#KwH3eaQJS2NO~U>nSv>G?bagY9YRD9B&XP2G9?QWo#}Z?R z{tq_s$62sqz_ykZgEg7w@69-TkAUvC%$scbc<-JR>js93ZA_BggZ%(gLxwCwdqkj{ zK@9oM5=|Z+9{EwfzIRr(E)Af=X%Wo^Kyv>{0b@Z^xsatL$@Qu|WA%~WwD6aF*7-j- z$_~%pG}>?DLwG|vR{b6f99^fEO$@XDUg~cvEWVqc%fPvhA3EOCc%GlN+y+)LoRm1P zTG8+AjWrXZZ6BOfW1&vT>Il0QmwIpNyDX;az4dMgXDoOTMk8~S*I3k_L)UUVi9UG# zr?9NBNUo?r?;qb@ik=B?M%sJ2>o1nyKDc!A1b~LO#S>Hx5va&(3D$t+D1wkocw60+9X3I)-kLf z{VwSG@6@wc_86@}AmyNG-R}{h9h5ch%1J+$ADKPZ?Eh4U`fV(al}Nv8q2FCC2F+Ik ztjRkD_6(0}WPS!lPFJg2GaXd5J`P?QhR) zdU=4_qG@{I=D8S1*Ox4EI_q?P7ChEeReI(fK}bM`cG_l9(rp--fQF?R6lqtbX@CXX z*Qn?4<5qeF@{+YsicI_|`rEvJ{}M_ybpIF2za)j1A$u%7A@f;hwI2JGz=;0{N}WBW z>l-zVzWnsdWs36X%=xhydMYToq~P9xw4m^`eaVM)kja1UA3Z|UL(li*sR2n$b(}oY z%~iyxWop0uWd7D4Bn>D7Ah7&ize)_cTLRww4=n1JLg8iJs=dVhyuTelw zPJTMX^Lko%EovgyA6s;qcUl(SX5DPGFtFYoha8H;cuPqep&*p{*S>hG%<)fk7DkA= zD-fk){#MLJ5agbgnT~q0+qn&VyRDMRvKYL1W%M$1{i2wcG#-aB(Z<^Vje=G@9IjI8 z+lQcz0{yj1u1s#3ux5+FS*c9jDI?X4F1X*4c5#cjlD{Z_9&gRWRE|ZHxPC5p9^y7`o zx>H)p$&&@~l#mCI=la(NGyV&1KkIL$DiN)VUtr!wsrSn1hd- zlr~URnNxjy(_ccS|61L=Z`Rx}W_DTa3L^d-OcQb#q_ybD#$O`4=QCL&Zx{BvJtD?o zC+pGos=NKQqpdOhnz8Z>Ri*ApI5(-;-2Uy-to0byUs}qE#pkj1QA+>bBQ?eMTe`

-wq%mk^rTmQ&4zNwetLHole9p zY^Ca&#Zkc3zy<|IkS6t%tUHyDi6*v!AZcA!x4gAiF7ciHqhi_CUm&$0hD0oM>_b`p zkj4d$N_>tOoow12l^jo}k`x>qPMN)xFXgI>FqO=m7?ISQDx=WYlqYo3L)2G*Qq*6d z-ooWwkVZyJ*Hus_&420~Yh>&G$7P+UyOHmSbx65b*SP_qRy)-SA~NV?LK<7chD=2*C;B=#p0$+? zv*oN;-7TLrW zKi&l?!@$4s&tV*C<(rSovZAF@&oed@8fzCP0#gN)R#)lH zc~y%@npo;%+VHu{sM}n8TzU__wfHj*Fpug#8}cZQ2{4VX2ANp-dH%Y}18om2ZYmhw znCJi-a`6emDa$4K%AneK+kT>4)xRw#Ni*p4OuhV_t}Ifs1OY5Um(k@-Rgtc>IV3il zA04f%%v0yCxjA@+-g0&@QdUZul?^7d`WzF~I)2+d1cT;0&qbz|ZhPc!TC3TyUKEp!>n=3w-d$w1bPB*jLeSMq3RTOK; zahB&31tSUVj)KN!`hlheOz%zZ{5cZ`BFsj-J0Y!CeeGZia7*7)<7M0J+a;=e}}?z zRS0Bgrea0Op@6{x@o5&iloeYF-y5^N@cSEjO3hpDepLBAE>*cNWAVE`e$|65l**xq zGP{Tu5oV0+c;Qt@y{vb3%lgK=ikM0vi3%BsPTnSfRJ-WJ7U$hd`^(=8Eq`EQl+4Nq zVsm8#=?u-9{F$nSN)A;pycT#L&(rl$#8%1o+A0Rne+n2kZtTfF2U!A@q80QMqUJ$! zyysV=F?f;l z_G-}TSj*ClZjF}qw1Bah$J)fCp%fe$8EKb-<)&u}GE;I+lr7s<_N^oqb`u9h zB_b|ei6+KpJv5iiyOdzn|9n!pkVQ6(%OUBNFbVrtXNboXJ9USsr0*?zsPtj=n#W?jnTeFE17S_JgRV0-$5jIoCH zX-(4`UP^)myW0LtgT_9ekC%xwsVJHJ*8xjk`d>>lK405IMq}d2`yC(L!XR#8r#T1~ zdr{iv@mgcHtk0H%D*d)rCDCX3#Ff4LJW+Y4ZUaNXl-Ro0O!Wh$oXKeROVqI7t>l{S zxsQcES*JcPwP)S`>ZV`$E6!&~gYbdu;TaZyYimY|j4 zPq`A0rjL+r26rXgSSGtp6747^m9G_0kwxk{3Oz0)_r+3i&!J4T zD(jzwz~cZj#YeJ3lW5J#5EHPz9uI2aDlaT5><uC7tP@4l;TJ z%b0xWxgfNK(f!MlB2D=tclpV9iHBzPU?tjTyT&DUb%BtoZGNEBLzaNaLzGKrl|nFvf@rKK3<) zUTtgoYQ*6Nn%Kukr}J$ll8vi4t=+lhAp`{Tq04NtNnUzR9rh$RXjZjo&ZVH1ux>>H zsm{uBi71kK9VkBP-E|(z&4#lMrF7Sq?8X zfgBy#Ys$5zwWxQe&tp*y_2NVsVgZYcPBXsB&V$K;P{J$$t-1AEsQ@`f?KC~p_3^QV zH0Gj>TdO9pz#@2>%wgbp8f^{EYJPe>D^uxb0%L%0WyEu47G;J!~HHg6|-dY>t#~b4IsecQL@ZfDs0Iv9CM*=Whe*GLeR1fQAA9Xelynm<+YD3?YD#kPI2a#?p;=E2@X;CZbJJn zT~R^CYbm6qAH?(%MKW@Sm+V&KmpZt5gdiUJy1Q|wr6#Fo8-(Zktj;mMla zzjzB&4FR3lPh2uy7W`@efx9TQ-l|{pU?9o>)N)6ALb<*WzAZ^@U|Casw$Z|3_<0P9 z%porV5tt*Xhy$47RF5q*hz>rSIT0=cZZFAVq$jO(>b>}DAS@XVe&vGtib$z~0vX&* zp#GF+QEgUdC~~oglnQpP{BA@}O)QC}e0P|I0O%Q(P3K=A(A}*$J<;0Z48x zjDsQKc%_7^q`Z$}Jum)azo(K1w~?Bx_NZAcvnU2z0Cu-Rb z#_w}W|8(sxIpF!M?2j^)We$uvLVtckl^uE2#4dCCg#>XrNvr<{%tc8IcP^khL5>%@ z(-)&I%gBf(dE}8EKbq|LiKu?dfCq1dBp&e+f{%F?n-$E4(jioV@3EdU{1nSk8${X` zd60o=P7`x~v5&Wd>*jM5cxy#hJ<9JqT!=x3hj3wE02Uxd$ty>QpT8{Q{-S^NCPDE9B;m(3|G~={3xh~m z0r$88GLUJ>R|4;WT=wL5mV#k{;Pws@`cZ(5Y}afnAPVB zoMPX>_>5J--5uU#|JkA3nN!HImE5s_?EPZ z|H$Hd%c=QLQa^re2LFe^IZI6Q15E80rGwO0*Mo#d{C4({*6f(J$eOgswcRa9^h=!0 z*Nybpy%9yJqFR}asxWG9xEHGOTzcIrHHPo!_LB1XDY zCS$~@!#(qSL&KiG%{zY}EzVJ%)6vXpa2q7BsjU$+O;zlY$(?qhM4GRaf^G$$_WGHt z&d@x#OH*S6a3e^+xzV?I^10Z@m?P!2mh!vAjxm%QHsOVw@BCz#Snis>)xN7zN7~ws zXQMF4Y9#WPP=Pi^9DWU3d={eklERhRIS@LQwEofUkfpI!%2Osh`qX?l@froqlZJz_ z2V7nQ`RzBEf@K#`a51^@vs!o=@B>UPIl+kZ6}&0QQ5t-nP*c(Tp6Js%qp*s0vSgcX zDHFL{t?rAS`lWhK#O#oQa3YvzQyj}%Lgng8a@DDeFLt@c`mn|LrsB_#^C21ndgfL3 zMv`=bBSM0OX~m zhZBGqJil6$$VL3-wYl#VP4@~cyygaD6?<;W&n_R+D1HYi!XYlj8jht_X`!|c+;7jjit#M1#@Wjv59 zW2IajkZY26ni#CEXk# zHHB2e&V=jEM68eG?$G|Nx#-QYFJ081MGNnCumE`-z?bK~d&;^Wmp zmX`EA;7t&ghUOkJB*@DW)ibybXG0T!J~8^CWCK^+&-+HKwkVnDY9!zdP8c^#Av+`@_}rRLpMrhq+%lvG0Af{DyTib{G%8Tw_|ZnOcusC`o~mB1!Vrbn--| zA`uZY69JKxcQ_%sGa{~Z-}R}viGYCPod-WXwym#kKJmvpKc->f`@jd#zvSraze*WZ zw=nGHlywN?l;c{Jx|hpFPQU0`@$AS`+;UQma1|&1dMdA2aG{x`sZ}Gaomqg{fv4;- zTG#%4GEe=Z6tFCaXX;4bH`7XDRiqd{Ynp}js)sQC<|u8q=DbU|yi^CN@*Pzl?;ieA zPyA;cim0W>=6h2#<6RsWRXOxVV2l1KC-}kF+s4f!tL=fP zkq-?X58`nE6kVpC0rVJL~Mx>X}2TB0taWpctpp@ zT+#fV{K(xNpUsV_4?(9b$2(P*izjW`kT4P~SKEEAnfJO^KMB{5%L+WDO(7ea!W>#{ zl{u^p_D3;>OsTR%%`jHyJLg-uCVkZpGa4TW{R8Qm0ZvZ2H8Spdp+8S2>aA*JYQf7I zDaeN_1tDC{tiXo1hDwZz6`#{kt><|2WQ z@s~HsE9beMmxoPbDTK`C!|73OBh3viyUBJZnQXnKA+IQ^GLz>G292ravh!(9Y)xHc z2GlXlt^144C?#1rYd&imPDhLkPA`s;sAs6Lq>F;BXWwEFf6RtvTer9z2V^uZZx}dK z&WFHaCi5XpAAN(7Y^1a%J0SYXSe0T4kH!^KwG_J8`#@npul)B(#V9&t6B;$& zaUG>D-{)Fd$3S<#vCIKDy-)(DCQG=$JYO-oexC=|;jIqriq94V&!zPgzmGLcgK(Yy z`WlQ3;_I>?hx|%CrLJOQG?V$6#U6{Ree<++f-rkac2`IalI|iLd>hKx`z{QM`HPtC zJS8n=I4(o5KWc{>vNdFgTHUt!f|;wFsjxWP%n=5gk|`MSC@7-Z+}S?CuMDG!^T=Lo z>_t9z;_p`?6;(}Yxig8fcc$6+E)qDjKp%joCFn*iIb^*wdKEV!Z~l8Q(7QXDqBVWfRMbLEBRNdx8htwjF#Z}Sl*&ta&Tv`!$flx zyxe3W6RupxcoUET5)R${Qja{ycnviv5*s@f4!$O>@-(@r-?&o7(zXER%qB*y6mioX zjD4U4K=fljOb6_SA*t)}5}nW+o(4@}?xp?g;=*^ye}(6m`no(XlKVV6v1uxQO6g5 zYk$W4M*E2xodtKdouW9SjUn{RzqKLTeQp?R$z8A-niA)=X)QE`r{t9{^%X5lssVZ8 z;@1}sr5w8$J$W$<D%$`W^;F#Au~`rqi+Jv8rMr?iFYgubC8szZBtljgA+e`*xF; zKxD@QFdRo;f3EN6CBE70@Z9Jvc}En?*>DIUZyoZ|+}kCL7o&aE zk&SNK(5Xaq)>-Q0xgT$ z1~(?JCY~!9QbNe1&zPbvGz9{eiO8!1jV&B|T`(#43!ifUpS3d(|4^WEmn7^YDb|Rz zu8y0Wa>6w4xeMFDp`6SNuUs4Pk-&kUGvRZcPA)9>e&w{f7M`ZS)0ZP569D~qyicIH ztR5wF5qr|kN%rO8>9A3r4!b|2zPq~;S0`Atj6%v)cs+$)Vn={L2qLN0!ga0GR7k#E zi{8z*oB$Z->!S2mWBBQb#y!P4Un4~V5?2$GUhTKi+?bqJJW8%UcUb^t)K1sEjK(zL z_V)Ay(uUl|^^D~s@Z;JCnz&ZshIM)cV-@Qs3?8_(5&G$hx?e{kf(|lLGZrZ73*l*ZY*ehCbyXz` z%^)24jSNp8n*Bi}0D%yR9RrmPZG&gD5S{kRs+n(gM_=!0(;^ZC=B()^CTU`8raZTD z#r_ba@1D%QGt*^;fW~g{7%5xY`o+=e8CB8SRRD_k?}6c&e1{b`KG?yw8hnN&m6qw& zkk#A7*5p}fC0Aev`=}*&R%PntrF+^gZJ}>qP_L^6$p7(L7eIuamBwhsQO}}8k5n|0 z(*8Xmv?$7rkeC+or6x-fKC+jqQn*BcAGzSZ>xt5J2sCCUYd@CL;R=dO0 z?)|XAbw`8CvG)K?Vut|LsWdLjAKBBpanUo5uI*gumD;Clk99aiZyDa-3SL&r=dguc zN2*spgUk9gHl^inAw2BV@dVQHNoa*Cu!Z`3Plu+!>g#)DI%31L`o3pqby#zNl3_=K z4qODhLT|yjbun^d5m{2pX_O6&mpv>M?WpgMR9}x1F!Fw}6dNz;OiY@wij^o-j{8l? z5Xq$Ea+Kv$t@x&KsnP+{s2i3PpV2}hzAOLUudf;ocU^$YOc~2vj5R6ChaR~=6bv0R zF-vwMOivv%kqjE%{BccQfj2yy7 zai@qPH>QLaHKK?>J%Nac^uVSsI!ppL?ZSHgwB_Dq_H>B%asbYFr@rtkgeHp)(SL8h`B?R+dTeKRVNIlF7byGR41ei90( zZ-0C}G)r+5UvJf$V6UI1#d#80FOgN}Ey~QwS|o7}swwB9`{ns{+geUu3EvCYEs2o@ zNJ0ColSy1>UDQSm$+RAL*-55Es+F~`{cTXn0SV$u(a%Pfp0Mtwh16&WocNCaOmT6p zu}ESMd9WZe&kczn_)5BF$NP148_YATjsxhD>!-F5i$L&(!l1gNlwy*&f@rxuxMZLOoD3rGZ+F4;Oud(ydRLRwbP>8>8_KKXO!49+9Wa*E1#;Wv~G9`|iDQl7C!#%gb~^vwMp zic9zZsHkWq32pUXKT5WV7lK61O6k*Bz6hhvQlX5M2AS|OA1uI<=q4d^QQBWUEhU)y zmT?OM_r|P{>FGU->1<*}GMc9}C3rgR08-xv;rpw8B8UQ!zg`54?dhTZYMNWfentL97WMOT!Jr4KP!)#R zx%w?TXwU2y6l=P6%?OBmQs^Deo}$$+ouRJql~-vQpBj(Wdm}HGdc>?F$7 z{%WDtfE70RdW>S%al)*#<-H}h_wfF7-{>tF!(`hZiuw@?N#=lKN1+VnT6@=TJtOij zs+9^Nhm>q3F6FD_dD58d?%pMiKy~^2ieYpiY!ryRTH1%HLuo%JIO7-tZDvA00Fy_i zi7>uCQ?fwEUY$ljDk6HhdqVJSo|lUFM_20Y=CWtuzGru>t~S!GYpS;cs-Jd`Q>^!u zpky0U+O{r%voKHVxoqkCO?JG{#^Oq+-@?nPLl67i_I%#ve*zxSQv?i)`n{mrZ9mBi zkY5=W@caZGP849t5;`@xF`=OkFH(+Y#u9pgE2(N_K!L|C8q`a!3*eJcscsvA#{IlF zoC9szQ$o5f!-?nV#$X=#AmcgXNy*!&ot1<;_LBgRt*z$JJS!c2oxk+7!T^EZr@@;S zyK<;0yLqmYljq0S64r3Qk+&$h<%}n`E;jZ$da?A`<%sPzrjmqa7QwHQ(VE`0K{3Ux z1AmA=;7z}um|Wl}%D~?-al26E_hOPOEs)wQZlbckIzDM9d;En*5QubOpB+v{8wa2V9c@<#o8&~BM{RT)vdHD zEg;1$7i~?^q*QE;xutK51(vLPfwr#xLD5BrVLku&f+oDP5&v)%XDm&kzdvf}Sr<%5 z{2@H>`*G9lS>5?~aD1fQW^8!RB7*ACBNZjLB74uCSGQ$&eHCFwV-l`R4(mJAQfW57;AUSa#DhZKR<)~Ja617kDNgE86znfWgO zC8k_mGbsV;Oyc=@R;>jJKbY_DfJpAJauttln8)EdPcdfksXm}a{8X+3*SDMv-)vj- zPTv^sdtC#bASw$p%ZnV1+VYZK@(tWAA36PkR5D47sHsMh9>xQ5k!;W4;-z0RzF3-L zZ|u6nmeV>+ykR+{9G^+NFt?qt;Hs+J47kazwS}WF4X%)gmvTeZg_zeowAErf z=f8fTz0}p_D>T%b^pM)HBtM1~rMx9+KnyE5Hr9$#cQ^P-n{p*HJHsTYzP13BWeN_A z(Uy@k$q->~Kaw9HkRiL>dkXGQpTNqzn&)8g)f+1+-;nc5u><|?pPoXOJf`M=M#8mJ zLFF3+)xkZo)3ArMZ7&-A_`-y?8te5`?ByFmzP(AXWgy53oPWM1K#^&w1TB6Y^6)~E zK$)}2QjBhT%6v4tSgO^6KV~BliNcM<+rvr4%J5jLs8^zYSDIp;jC8PMw>sGC%pWi5 z5W^?x+pvDoFMX5*s6vB#M0~J(ga{q`T52IZn4(G>E;dRFZ!7D}ROX1cL2wB6(#47v zrNggYdsG=1P5qwOOTGri3K0WFMt!M?dk*A+1VHEBqRb_EAD)E^027B9`Bytza5o}0 zDuN2}P;Nr+bOa5R5T$@Q{u4oia{$>+AQJzasEmj`x?TZ=kG0Xlb)T`if%1B_VE?w6Z#SWF@AWmthl-k) z0vMgfGIaq$f>an-t(F`qf@oE@Mpm)~!(fnQ6YxD@8esU+8(1sto-lfRd%K}Ic?U}) zr!sw!&GqZyB>u^&-^758supn}xH-0C=zBdw+5h!Al!gw0=sTTRT7*b+145FbrUvkWnhyf0wv-yE?)A%DoaF7?UeX zwkT2lxxQabbHJqG*(m(#7{;{7Ax164&8o-kBc))RwH@iL?Rh_X{v>-bTk%A1^xEjV z78}SgVOZ7sZ}g3tL_Q}BuNb@yEBB1P?-OMx9x(PWx^{`402xN8Y|%b%1IiO*BtMI( z<@5$~URD63U-2o3%Ug=53Ann_x&DgRPFOwUIVjPe>@qoAJB&ezJr}v|ZtuP8hMH1j z^TWvGZ$k1L|~>4dvc+wHqCt z@mm@hByQnuB$7^16pcV(#{K}T_>PERiC`ZK94b8@=atE_r?)hn7_)_U=x4-l+SFwn+)F(O#Nleo&DLYM4eEzdoko7Bd zi)mpFB(%<3+nPSuF|{r2Sa^_0hluJC;Y*sdZX!|dRMBvRm#f3Iz~_VhTC><@UG`$0IS+f zR)z(Eu3QOIf&k4AIG!|y0Mm6uNuXGM??whbQjTkIMDj&q7YBPzG7|?$5L1KKxx9s; zn0~?O1t8bw#eUrSa}MD@?ilEosN9=cOxZ6CO1Jy_3^u8>f%^B z@+j3#1Bk9eUIEFJmGlW?0vvQuAwRiId430@I${Cu`kJ9SDVsrhnv6#Gje&==Z4qn` z%r|Z({KGb7LnCj$<>Tg$c|e(Ev3T!IXG0qR1VajE1}P|1jMtc6O!GJzlvHqot0rQ_Hg zqpRljJLTiS!a$z`Mm#dPm%&D;s~!q0-={-$qkm-6mI}G&C<=LG1=V#CI5lpaMdt*+ zkcKiRzbn$lbZWOTm=5Yy1Z0gAf+)nF`8X}9HENDvT;$x<3)Q}#*C_ZMUfWEGctnXm zd^Z5OL|px&)_esV0du!0%gIBeLAiA5J637Qv?D#xuHnR*a)-F6Dcr(M3u9Oo0v7NV z0fQr_aEAv5=`!=rz4dC#r=-+^-89xKny894z4XZWzswpLo+9(}3plEpreRsqaxim2 z*a*xXdKMAc_kM0q1p%v=AWR`!fJ33T+Xu;y?|hmU77=l-es6v|#u0z6P@=Z)w$z1@ zWabGtUGz%9LgqC7RD8D)B~uW5-p!6N!b;@#F4uoidSA7FVa&pqy_*f=8}rg9B=K17 z6}T{0Ro}`FlPchWUIHHg0)BH#6m%8R%Q_Xk33qRc05pXa|2K8P*-vk^T#n4It=6gN za${5yItUG1ycF?BYL83sE!BEAu^Gr=5`E_zPQm&W&)P22jtgT#Y=VpCn_d_Z={Jgx zRh@IJFTTFU9jZK2TZ00cle6e+ZIRE{lv>W;d7(AA$|PdJvUws(aF(iZw*I;*;1Tr(8x_wvW2t+{?5uS-6nSX?$T5xd})g z08f&v61vFCEA|Zb@b~Z=ewAUxc zU=6KT*AlEQbi+>O@Owut<#GzAqMTQ{iRHi^l`hEQZX=kd*1M@9#fqNnO$v?|4qQCo z1z*%4anxsgmCuAGABPoFu!ov2Gd2fQ9_I?%ZYG`_iQjXS4!=7PX~HG@vR8wQ#KH6; zDXn7q$8E!t-xLw(+WBu2cuwBLzr-7!m(!QqlP~<3Ay&qSCdj2Du?Ndnv+bZ%i$tZM zb0oi;vmo7x1;~6wnNoNxHr>UCmG}W^j1!&TIPv<}D_Jp(b8=ElJL0uaVp~h>!E^oi zWqmjx>Jtoi;u0++6Sb#92CsZd!pEkvHX%HKghCr(YRo{;VW?6 zTNC8~WXzaTjo>fF^KB@$N(cjCIbk%|dsV+x>mOU6rM@QB@H=qYSqpA|*-}=ON9?&z zwHa(K^7_f(0TrI)G3l4x&pJj&ZbETGC)Ni?YHCNvdt-C@u>L}!O2QtA7^faLp6$3S z*J=_g@j|l|_Vf{NUM3WCQ|uw))4Y1h{C9Ju7A6!iGCgqu(y6}kWeROTA*-_%Su)f> z+t?vbW*o;1wwGgk;`)w3=>aZ3U4A!@7PH}Y=wl)tCKN6VreJJo=N9E;Ajyf=dex@K z5KzRU^c2t=OBhWC$Kufx2X`WUy_N9H&|1K8J>d|wlkujfM=nq)WfC*gePik5J=_MV z&2tyqfK{io_RAPu+#ewy6lG4~(gw!7O%zg#M=Kh$CjRj=7awb{{m6JR99&3s9ZL~ zL-iC;8!6ZIOe*WerDV>NSWY8qkElp}{CtuY&lXmiW|h{RxA9DoVCv898khtH{Lh?WB^yfeKVPjOn;-PBDI!onW(*u`UAcV>?qUI+Z)lnWIn+R(dl(UldceI@V^BhJ2rTdJ8G$ipB=?OS5NYiV!(DIo z8py=b+8x=q#ah5K(m0{4>-Elhu1I#O4l}kpBTLem)(BNHq|#dM)Cv0y^FBRu{=7E8P6;tV9(BP=(?$z(UA{-`yT4q<51N&V z)wcMabN=zHGAqIPlFS!(daFcwr2zI8&iUAzMO+#-+@PBFYj=xDS-7G%TPGZ(?AZ30 z9y}p%6yyS_NR+lK>7T82U+?=ALpjoRV|5$B0V$Q33~kuQvi-XB<<>+*ApL#6!Brih zQ0f9Eqo2xx$D{5K5OXfU*KYw5!1v?bgkAv0`3Glmx@%_ZTK&L!J}=ZIyuVnKmOOKL zX!&}0Svq0FlO@n)!v58xHl`z4tsm3s?voS_k~{o*?leK#Tz(c~7?vWhFPDk6_G!dY zT(6NoG8`&?YV~p)vj^NcSylR?ZL%L`mQSrfa*fPk(16xg%c>K}D;pG~<89mH;lUn}!Br~(&gQ9EL9?=aTNdVz+`LjjWKYlGglA?sV=8fs}+OAu|~-BYoS z(`vGB@>mnwWtb?#$K_zWr2I+*&y@T3>DCdIdrV2vX8-tK07}rgLx!OlZB+*)W94s7 z*Gs@y`8f6;9j@%MJ}@LOaQxCir_(W0;wF&(h8c1B6m4^V*jH%m&-Zoe6Ok5D>f^kp z4g;`NvHcEyI|r{jc~h9=hVYCuY^=n&T?MkW8w+d&rpz3B5wPRKe2u0&*hFx30Sx1q z+CTQPxga&;Mlk?+Ls&`GoR$Sp3I$|y64S$Y4XZIZGiWX-k=9N@mtTLy%r2W(Oy6WN z>ir_Drh+fq2bWOWX~}mt8OZKZNgrm#i6JM@&0Td$E)mo!{-K&h48pL&OoS%ulr?%pooxxvx5Mz6aSeD!y13)A}i4bDYu5 z7hBQ4{+l;Uh~QEppQ~4gW zEul&H!E~NC6Q_90=}PnLk%phydm#j;sqntRNnTq?Y4qH*4WItSq7Zk_?^5xgPT@k6Gm#X^W6ji7*tpUT_M&@p%xyy%!3DycIYPqwdqN1FoI@kcRy{S$bU)wV2QIuWmn(7N>Sq_cP{T~;o_O00^rz*d zeFKUC_D_1%B1P>mUWa5y`?@Gm+|{OATINu;P+OSD$?l1!p%80obf)^7Ont*~;u>|4 zCE4tnS?%$##d39JuENnt#!I?4c}4k0#ri9FL{cxv7CcuG8}5SqIOPh)RmhU@rkoq>fH%C;qdnfQJ`Upk zIvNWT`6TOH!@RV&i);Cfe!+eD9<;pF1hbJk2e{zGpu-C(K2OH(}UqM*~ab z+0mrUmzEs~-qmpw@II5%Kjve=dfm>l9{6)}v{s}By^we8eLz{(G8elB(u>m$iBL5nhIfK#c$<4Add+rQMic%6Fj9G4BbFM@y9&*4oQUz6u3pV^ zLE|d=#SpA+h@zm3=jc+Ln=DW~)}L}3DOeUe&ZO$nXP;YmVOp!G9%(lzQ+P4{i}%iy z9+5KK`tiEj?F?Q2$&;amJ(UJr*zXKYGox-Mrv8;KD9Kaw;PC`^=b+E9RxwG=2`(QMg^lJ2Fp32L( z?l+RivdCzkKSad7UMCyaz8!%7tG&OGbF9T& zY9%i_*WgCGAwd3$zm6QrSAzT-^X61M{cRSiK*8dNsL(_*`ibeUcNtb6LCi|XAYG4( z?JPCVC!vHf6WB|45=ZggV~5q1freHb6sF`=ExB>EY8 z!39+TKa5oNkO>M^dboReVugxbU--W;p1hQ*rQmE3@hrecfPq%t`eitDt{^82S|S4B znj$81;&CS-1`UmQ2kTJjzTfu_ePx)!WFq2m9sk8pM(^?i#Y8Q6+ea~R5;VpwAv0lw zq{2^pAsy|!cxeMro0q4kJziTxkuS#EM!K*s(l||;T7}?>5&4VmCR){Wt66Yl-eN1H zSs~wEE*oC#&t_0hU7wD9(wp!&%ar#r_HH5%k;LU!BhdPvA zGzJ{e=6w^*_O5Uu-zG1rsBR{{mVpPV{lyqd%+m3-7|MP3%5o33k`{xM1z5DLIqx5@|i4bV+!XTf0bQ_o9napAt{C!xihN0)8 zJ|fENcK%2`b!&YEn=l0;MWH4s`RaG?Ia;v1dP=yv2ta92LQAzZ-U6<|_f>q`vCn0W zz-d3Ara4a~AD3+K)kmH(1gYYC@)!e{k}+?Ew5jqmx?tu&`^^Tmk<|`R4uVp zO5QZWqHef~5sn+NCT8YngT#7H%xYjk_aQ0;e=SJ*wXxC7+j8AXw=i`)4rHdN-{IUG z>{{r)-xZNXt>W9^x}FN~q13QEMT;Sa+$sE=R^+Rt^Dyv4L;Eo9Lm7E68xg9rn6UZU zYm!JPSC-NJkpKHq3_+)`EQacZW0Ms(yDIxi7T+NW0Zb_ir zDT5oaO=ulCBw!)LMZx?e#(JECq@xtQ`yhs2tYoz4CV;rn`aZ2p|q= zZTI|`hFT|?_6lW+u01RRY5pWSI0)u+<6!2NA1 z>~N3fdsW54px81E$6z@VcECXkWPdK$5(I`t8H2ZG8@3McXoI8gU9UTDVu`C@Rin)B zo{$~Rv60LF$4Uw*8(l}0ff2N_O?HTxd=uJfmbZw!Ss|R&qM+^L|1&f zME=0H(u3=;K(haS;O+lM(^-bK)pgyvIK?fvI|O$tuE8}xaVhT5;_mJ)#oeX2TXFYN zG+1#cS~z*$>wG`r{&HjhqKj&qlg?qCHUW-} z9oHs&$!uuvgn660EB#zxXoQ$e9GL+DvX6IGECU zdJY+z>=U{zrR_vyPi1qWUh@CJ{G2;_HS-CR(@(ZQPiMz^i?a>!a>_|1oY)qhk(i?o zb2P8imSxr(`GiNKpAnYWWP+~81}@ATUqHZn>^*%#UdlPN_S%X1ASXy+As?a&q~Q5~N<0Hm zq*JoB%IY?jt`Z6@9LHOOfNfOi{l{5Ei*6;H7?drs_^Mekc^=a1%@Qm%(*NAY*#Bu} zRPzIDUYc;j0AeMDx03Yoe8$%J-H?LnHl^QY-z01dl_UF!(*y-gwNJ$o!N5siv{R{v z4AVgcB!~ldDi2Zfj5(^rc4EviaItmRwVwjNY5Ja7C&9Koj??Q&)rTSpa1 zK29$07hlt9ww*0+b;IRx8L3vi0Z#maGh=z8V{JTAYEu((8&QX=3`7=j36Z#J)h9~= zV|_Le*Kd|8gbdiVG*Dji_svLd^J$ zbuY$|*if|DTF$|N%eTa{eK*lrSHxv?IQQ|mvj@d51LX7AL?V046-oZWS&2u2Wsz1q zw&;IBMrreDYgGzbQNB2~N>S@yX)*k+zjhk;8W9{eA7(G?e<@n_XK&SNYs7vlx6S(n<7*r6bgpLDABBpgPx(|^N&`?WDgx#)bNd{MBXl9l~Rle)KCD9Xa310DwW z)988t&&IQ#lo9?rA}`F~G5l9IhZKB-Y%|aXNny^bGh>OVl5kbVbC|h;;LXDL&usV@ z-|6-7l#Zu`UG23|)55CZvcbTc*`P}Kd!H~7+m?C($%C4_uH>>1?_kHv?B3qL%l&KF zdWwUwjSx>Cn`ysE!79OZU*wK9%grUM{kz zdr&pD1e&BZQnA)}JPlRp>+WiZd->r2g`@1unoN|EmfTf>5j8m1=Kfhg*5($?6;TDR zF4=4{B%cI^qW;D7J(BcHIuHL(nTBe3$zoU{etqg`A3!%qIFW*|>qVHuY2)})2voHk zMAC4m)8tAwIxU-I!vHyXYSJIKcX0-iz7CdLYYcTfR^OwRAG-%_d=kOb8S74oHov)C3U?o};8Z3hqA{;q9S{`$prSJ2u0}kBX2JC8@^hHX*g*M(lxoVz z0a=-od_ziiG25bP6isy8m9#^OTEgU7l3@xNZnbd^>e&)03F8&KmDkgIjn1AIfi)fJ z{$*`?QUrij*N}(?1JnE+SP$AVhzEs^4=w&sc2?FrMp$fN=^fCeV6J<#NRSCpP~-EUTUpf3IOP z6vp_-p#eU55)7XC>S9j^t_Anxqjd}`m%6FxbB!Xg=(6du#sPKdu50a=>FcGx*)`i0 ziAP7~Agc0lTOo*L?5wAWtkyoUIru%NdOBjA8{3~fqS%@h} zUT^{pKKExt0wcUR*`oNHqD=K`38{afBVn6;fj`lM^K@Fukzyux@{HAFXX(_tY;joW z2q+QUGScdtXTYu=^y@Za#0|#Kmh&gKWU?;nKzS4Ml*n8TWAX3D2^jbsS*A4eUu9Nt znVQQgK61|^wm%K*tz4lGSeLUc7uRx?e(kM$~x^(niypxX1F@5h$Ca+fSYe*@da&73}PliOn)G>J)_0 z7DZd?@sAcCmbbD3{#FIx?>gt(L0qL=k4PgwQt1|=BYM&PtU1H&DS!H)ff@(x(#kwS zAHNvuQm?f4UWGyN7elFPtnih94V#6T3BZ>b?uth)g248&L=FZtWfpXh-^|pX#E^#< zGg9yYu$qQt;T5M(SBwq%JXg6m!#%J;H{OdmpZAhT;1t3bt-U9*j1n|4>x*BXuyS94 zioTmqkgM)@^gdp*evPHbX`z-v$i-ES5rAo-))J82xZCokOAXFi_f0?S0!uP@<^SR= zGcr170Ak*)-xW+7rflOmE#ye@7;`sOzx`m#uhDNZX2B*VR%LJ2gO~n9$44N6Ui8SY zPk0nbRINm;~PExrEapdp=FcrE=c^EXNsuQ?Zzb)175pG)6Uq(ZzZ zEhd7zbaAsFK$C9OA|9!gyvIXx-^E8Ex4h3E9l-J$?f2&og^b_-tOZ9rYBsypj~nwWkF6+xmG*5T~ep?FODnr9tt%u zH#Q+Yyn|ABI=tiiOp>sGjd9vmM7KV`WqBYW&BE8#P+gJ>6C0p`zlXwF9^Njw8SQCp zh&|@zbHRgLnVPNsISGt3bP4e93=TwvDrF|uJc9fm59byMwpypR@c9kd`&_^myJC!p$X-O%c@*Ov7s8Z8(uX@ie!5N&Wh{w&uvZoq_Gy0TnF0J0gEUZlF#}MIwS~QL3O>IGT4X zRtcl~86sN2XGaUAPYJs~R$E!Gi93`q4ir}@gcrHPGaFt699FHM%42bDSApp*dMSo~ zJRmGe5EoiH8FM)Gb$mIu2yBY9<9U7LcuBqPm`kb^!hz=!DdCl&iET6M;J7FSP~hMx;9b`L*4$&kJSK zTHd@cKA-awv`$ci=ODAYOy@TGN_a3tg17xTnu9lj{V|TClFL#p^*WvK25Q=M>ABBK z{|!_^##ZdMG}hNm{i|(!TNPuj|EYw)Mpr@$+HCbtl;2UduFQZJ2VhuhrXO#dF$eJX zw^E7VrW?yGwo(In7O}o6LrijMoe7a8U{9!1&^8Qo5z^eb{|kORWi=g<8KZ{KqVeb- zbe)F(3EMG8FLIwkUQe$gk(;>wd4xO~4x^$75d3nKI%}OrLrt}i?XuV=X{{kGO&``N z_KE@J34MqDi0xKb0-mpMIEc$y&cHnotP~egVdxa{Q;1$pB(oB{&u|(~LnZ-b<&{%w zGz0(%Ua;-#T>-#q-b!R1-}BZde-Ca69^R|q6T0nb!up&ujc8-#u2eEfe-qtUj)=dS z6l#zyT=o|Luk*PHGuc6}YagnmXX%W6Nb)$jWABmei^${%?jAHyJ5~guxhgk_K@_Ht z`7I#GdW`=ekrcOo$UY0>aIGpUQLtAlGVoF%vm*<&CZQ6!(!J8;`BZpv9}PDe0cg_& zgmYKr<{G|y{lob3a30e*H9Lz+k)_7P`?EYVuM526>-&HI*3laZkvqOD)W5uSSoGtV zQCeh$h?3pI-$VlJYO;!GF!OTNRSG;k<8J+lK%qq8!}yr}X*^VQMIx&FPigu^qpd-8 zj3iX!Nfq0w)p3>mYI^t#&c!kBP) zZ7*AU{xG-|Csr+Y3takD@>||bK8(5jYt&+C)+tyU4DzjhW$22R^;xQakX|%>;s>id zc+(YqW9xkHMrU?q3lO|;x|bQzzA212rW3|v<_ zuUIYaR2OL(SYls)eWP4^?Abdeqn;GT4^9ceZg0jCxEt;o`n`-Se2PF-QcO<=A&AE& zrqZxfKq*WmS1^i8SHD)r^}ZUjd1=RIk1VFG#~*Ze&T<&fxL+~I1}`6JC+bGhz^9Q< zUEjLIolx3XTSz#f!+#}5#pPljl!vuh`vt)N1rN85VX-t_t-Jqil?Z*!2Ux^r; ziFUI`J%vE`d8MW6Pjn>ZI53j3rD2drkPAe4bCBzX;;K+US@)a`u5>9aKLi=vj{^ls zoFeN$MkAl7lpRv|J8oSBoeGlVl@{fc@lPA(DT^1w$3aaPwVXD_%B3AN>{_m1>6UuR zY03s6*X_4Tf$fEtS<*uF@?k{me~XWtG_*ZoZGhWGm7&&csSOnT1wsggfiH?ugzx9n_9z`GtEd{7C`t|Vl~^YyYBA%j2v*6{rX2*%f85g<*cYaKC{D#fr?mUB0m?RA_3r3Q8`AF!AeQL#d!lBf(#D}@4_0f@ z2Rd%F<*10==@VtWWfJDiJDyxbSRz|~FY@BJSfh`C?@G{QyIIVF@|PAsYtES+4qv%u zcL5)krtEpS4L!!{ZS#nAv#-l+D?o zzzV)?suwkvyEI%Cj%CmAamosAD{00>QL@?0DVQSqa9P8NvP|^m?*bFmHDrU4$l-Kc zGUE5gzM)GD>gvz_*CVrP1OJ&S9b%yvrEPUt`c}0pymfdNz`@6W$LAdA!D@XhW-RSP zB_s2Ce)~-T{@rjL*DPMa_J08ZC&jG+GWu8Uj^Yvh9R()`Z#&c^~ z2y>Sp{QBtHv$+iMGUriaC69nV9TiDEfn(~Aju|1ySL*XUNj|Gn-@^`6%e{dYyfe%l z@0qQmfU}dOLEC0Zg(<1T ztN1lbC^JhKNKl@Htbg}4@(>1NMMg7JhC%?n9UBf`iLF$yy;}D<o@V8(mE&&Mqc zCEkP4u|ZfT&$gYHY31@t0_rC18ZCyK*5kGQ`kLu{+VoYcO+t3AId{Whe~bhH6-8++ zMj!JIyc}voTdZQ#`)ow^lp7=YKjth(jBzD?=C%b-b25a!>AsAFk4au<3KtovY$mN3 z`2B4u{PiYu*q`}zbFTnLPbKlkM5r>!^Byb8^&bEaHD~|yU9_8&2$Rc|5rhy)aYg;8 zCz6yd2^0|ZApS(3;Aq27;j(~+FLxPxPtki!hPsdcG>L4_4gVj+Yau&5isC>q5&SA~ zCc($>{k7N;UN()yC`1nDPlZ=1;9Pyc*@yVg9fYar;?-iKvl@(3iu%QCR6f+6| zQGduZpNLD3F7YTExERYZwynM$&f`u+5z?annoJC;#5_S-A6LgNJ;RN`GVHP!ElQo? zC%%d9CPn&I_A&aHk3OnYz+48`ZZ`i{=MjyiqoYw-ddvc$reU5_MFG^+{xL~6ptNnL z7`y$yKCljT`h3#!_n#cpl4a?H_K6?G$d1y|0G(khIv4vJLf$745!udANxC|<>5*0B zG3F&kTv|ZQCyJiZG&GC_(8uv^BW zNcMQH5}`8|cJQ}4mbsRFO`B$=o0G-;<(|%KYm_=!a!j*m=fq-0B)1K-L0@3Z)7uSI zIQpGAU^kIrzGm1d+X8RrIzhF0+iW4%NCxkbx9mL=F<6At9$Gh5tT)jJXv51Eneirk zk?RuA7$0wJsd(#!mQA~SjT)Ox61pWB5xnRGVUtRuVhoYoU?*LZ`iO?ePewi6rl?fi z)}%_24*!(J7HYOsdczFx6C!RLKG&m{IMgkr?h#{OSvYBXd~baEosPf4zk{l%im)^2 zt9RzQorxo9=5QsO|Ch3sr|h+XQIq~&vz zEvJcbKJ}xI}E9>dluxk z0}{W%3MGf_j`y_~_L(%N8uQ;=>7mRt10*OSaTNygl`cRH5}bm=>>(#14dY4~PmC8_ zNB~AW8c)a`ZP(Pbc~+DCEQz#W%cAvFeRG)s9(>zSbt7S*uvS>MxjdtSO!i#jg3Kxn z#fi?VGYZ8vr53unsp&_8af+wM3k`Wmxqg6tOkF&Do@J`)4^4BBJEUigmdkZn;Vxq0 z&S^4GWIum~PNnt03|`v?F*ADx4)b4Gb7~Jmgk4}=yTfY?KT~tNxeCpsY#601vzOTN z*0*KI#mNx;yZLuLXUx68xIv*vxn@_Ejj7SwR(zPh=dEk9^kA6iY zj|nZ`5?#ZGQ--?EOi*+J*S!pF#&+}dYZm#F=h*)!R6A`n|4sMOS7ON*NC3Hv%15(^;1TdUG+LUb<+>qKW&@YspE>)ORI#U0NziA%J3$qu@E6!j~*qlicYXEa`-5s;h8v6VoV5w#1?9U+Yn5K@YfkV?U4 z_)#2U%bc4kA}WsXwz|;w`zhqg=%3`ms@G}_g-xF#E+*gzls=9)Hl{CbsdPJm2o-Tp zne(ioclxch2uB@aC8Q+>C#09U=3ABv0*XYGM;77a;^b!WA4iC@=GelhrDor@7_vuw zq1C%d7xjPn#+dHJ4$pl|=8t5N=+g+Vep1fje|WAFW6L(p@*a!Q)^u!^*G*vZjqEoz9v<1aLKNKyaAm$p1#yVul_1jZGl=tAP7NW zLw^^E9eGK#D6*yKzo=L%3A-y~?|UEQBIj)1Q$cN;T&%72&^uJ>GRJhmjsE&c#tb~; zRe!bVrUi_I?uiAQX2aBP7kBmh85EdX8ju-*&J>-~zJ&OE%yPuN4Qq5syfCXRR)0TF zLT82>c9_kR4a2)^)*)#ilaZ}TAq9<-!|6nJma6JcH#zA7$Dg$H?B!WTGig_5bj#zX zT2p^~zh~grr%)D$re_td%^ydeqk?oud13va?#TPb_S!}UAOE^KCpJRd5ZNe>qgex} zfr7gC9sn~i*!P#xi*9$KWB@+6)6pIb+?83@Vmd#Kiy&u7;%h|zLH;WBIG03E6iS=t zYW-Xpg50W~{}rI2tH8q)9BBI{hhd{^i?3y+q>P8Jm2HGQS#+l_W14DrV_U!e zbus-W@_u4O2EB~;P|Kvkpe2jubUDtyxmjK_tR|1{QRTIHNTmMIruqcfYUR!9?1m4u;cS!t3XyE9s{M775w57;h#V`Uq_DNaxvkwQ4zJv{ zcr~0AuLt35d(XUzOgKI=GsS~Kf_PHBCSWv+LtkIOd$l<78;erIs{?$mEi&n&)0?}E zsw$ebp-o}%20bd?foQ<#tEt-(m}bz;_kLD5n$fA0q&DLUKbyf zjz=4D4&);F6WOi5+6{k=t*W|<3Y!MK5I5QcOmRciM@^O8n@bVRzuo_o z^;@0UmpD9%RQ=S+lyp}U8AS8~(S$sXx}Q1h%I*U*C(x+IB&37jH!g#g?g*Gz9Py~? zeuI*2`k`~+&UzYi>c(vvXyN;E&2sQx%(D#u)gWWqlMz+0gs6R=p>CqIakPJix9d=T zLoJXeRXVYui;|0C8W>4FHRd%+0Ts&)8fYSpPP&-jeM7?Xsjz`m_K6b7dTH$_IMvDE zwDG-MRa+zUWN@|OtW1uCQB=(Y0pbFDzCYtDdt3`WZ`)FLaBhs<{`09-$_eEvg4akZ zr=)y4l)hT%H?l@p9bX@H`*)ii;c3&iN(8@+ko$mZgl2oC3wP4C?y1YV5 zgGO_-mB$`#PkI!*AR)V8yVc2@p@snw^0#TgbB5~E0TXLWLq%o#s1#E;fb-W66*H=>zQkrjqT#DiCsA{0~No&Lp)H4G8~%7;e{UqzaK#3 z#7z4*9Q!!Q)`r&!OjKcfR!s1Z^l^zc2bTJ*DNG`?AiT;RbsJ--%^X=S3a9BBG4uyT zMcR!!GYn2J#tMlSj-;}EPiW%yGK4Ty#RvJtWYZ0qIP_*2F$M;{BwA)%AqZm4@pcBJ z@PD!4Ww&GcOf@N;=gAmfl1=#Uu^p3^&5< z8NZLLwHsZCr2!%aW7mRJRm}$-!hQ;NE=yO?fZ=}uhIM)_DilIm`8dPA#CoxFi$(KA zHqI$DOD^w!BhbEaf|a`@!?xRQ}cTOHmVY+)nXLUbDwDo0n( zRi9H9G7aZL@*A9Jm?NqB5`7kVZ55Qb?DbKK*PF|ivK4e0R6!* z=i<;21dFZAw1zw+-_eM#BZv9cakVi=5`;Z{ zD}VokVQv1GidH?o#6xFF5@d(^(lQJc$PWc84d zWgj_PuxT*xOwO^zsMd_`5o?Qk35`|Jk^VTEiAofIJcat5^W0X=k0i!v@>7TiQLy9r)0iSTkyzqA_prRs!_c}XfGo=tsD@SBS z*J%!K1@lAzOLv2QE0@6t3I|->EJH0R(8&UGQGm7C^whYG=xzK;GOFwI%q7h@M``P1 zEL5v#hd$Oz&j^Ylo6FGkn6{YJi69=YE3mBm+6ZxQ~S3>8i%fGFs5ytsfZG{D}2Fn=r3WW*&( z&+xB@Z|pc#OeGA0buCM2L7FlEQTEM7hl-E3qz_e4uZoIOMyZGPPascyC$#_v(Vryn zG~727gwPm^7zq1~FaZI`C*rH|vR5Mb@uw{$ja2%!=S0XmxD3B4xGdjER0mso@pj>C zePwKbE9AOxVPYo?r@{7c@2f}PnEeV7R} zm*7YPJM}Np!9di&|QcySYUKRPR3?KX0d=rWow8%Q}4r!w`r~P*20DZL< z$Dvh#tHG?B-)?!iq%^#4=%0z#-r}#*sNO3z2Z`{yxN>J8`pCRV+vvX){PIR|F*Vuh z#2WJ1;1oGUAEg6r;h)vC=cgWL*&P?&)1>>}B*sSBL-ZwnBllEGSDRt)z?S+2=>*RE z*8gCamdgR?k_It|Si!9}#x@2to`o)HD*^_J?|M0$UyQP`C7DZugC`tT1~t}`D0f07 zy3yvtTQk>eq zu32)F@z6erS5NlQ(issp>I0(k@{|e838N)PV_eL`!w8QlWZpX6u}v;}m_MYF1%y`}Eb=_Qo9>anmgaiYru14qIy!_OJ)tT zHu%Tx1h8`Moe>>~5$08OUrJC^FM6hJR%R#Q{@L;J6j&vRK6;aIjo@p7`~vespx=*r zlQl$LN@%a-kl?vU92~1zy7O=lTBr{oIM3j zJi2x)3?oklVqS3){E&sD5)0z9E_58cq*(+cXi5@K>-Y0pLxhCR$17fH8 zHdMLxqXi`Cyh34b}!W;j*beiYu#l zC(r+i;m?O0FQx+|JMLprEfouKmlUDbN7CeAjXe3Z9_{3!`nE6DOMTW6X~^<n4VE1g$qqCId1Fd9^xcNCEYe2UtX?8*Hn84J_#GYDG|8egFn{b zw99=o@ZNjjA0xQKSww2R#j5HE28IJUr3Jm5(;+VHT+AaJB=zAAA-;`Y5IER*smDA= zbR={PUH&ff64gIX0WtnMz~QTQW~HRcqimX*7;yPNf|0z4S6+F^wv29N(wL3vdx_$1c$ z9`$t4v-ANiU`dC|{mE=O?M`&_oTH8fY&g)HA6Lsgr#)xVUIozkyXgraL`)&9_dW#Z z(<7Gr8wLKOe&|rt_qzYCgP$-8jQzRzy}6Gq}szxef{0C;hTP9;cg%OSAHXdNiFQ zI3&wLgmhag^SXprKGUstWag&@ebBRKMNb1#O4K#F&!b*{J>4|ENcBUy1bnrcm;6<`H3Xw-+0s%#MD_7$- zXWkhIBZEG1e!voDJ?!C-4+5``#*_d8EPIhfLF)}{h6%|X<`bX-I7Tng3_U=36y>A6|`Y!i-_D{nIt_cIsUpvodMDsfRb4_oNign2iy#_PisU5#rTEedQ zK1;xN(V?zjkD(xQPT$%SU_0`eB@wb|8*sa~WF~x=PyX??OF%Rj+dc!rR^hyb7_dB( zTh(Lx-|QE#lvV!#nWXhA(m`pGbvy!-k(zMY=1XuZ=IJguAp>?@CiU%R-y$HG32rCE zXkdG6tC2LY;WE{ES9M)Q*4>Bb4>G23YC4##Xu3x0T8Jzp(Ym|Cy!GA5da@^aUiWew ziz0k=-6&46;4v;EvgW;-na71;q-(9d<8SU@l55+u@2Ah+KK02_D^&p#`28#_y|Z_k-$V?sVwEx3=3P>!$DnGTMH=!nfAtBQ4fTf z?Xa+<8pi4|3g9f!IZZ60s09*9N;iTQuLOe>$}6l*6)VptCPN$&clP0HBd=$u1N(t# z)Z&*GTl!awMv9`Ttgk=YCAASpw7!|V3K?}VIyhH?hbnb$s!^E9b=8+XkD~!Q8?tX4 z>o(nG$b@8YF1a$?M`DusSOp`@!lw{RXP6F2sw#Nq*fj^gC7FG(QTp8!>RFci6VjBu z7^{XS_zgn>>vqBFDQ?$~-Un zei{T_EQ3qWWq0DT!jvdB0d=ry?p+IJ}A&-y&qa`(kycObB%fUY}G;zMuL1x5^3Vf+Buj z(~vuJZZsIG)CUj;F05S>#S(84GJ>8^$J*gX*D&j5JIr`-Hc#4B8c#u<`J0~Q$J*d^ zHb%IV-|TZlx;)-@F|FDmd&fv51spZwgoC!gZ8;$kqIwS95e^HQq&V=FInAr z_%G6Y#0yQhHC{^UCfrhfD? zVO;wX6bKV@WP$fa?bTo=O}3gr!Tma3#t zh<6lev7sPX>LSE^SC(}{iHNo?LMKogu}wDHn<%=q%Z`#%+QAhuz4?>5=Qh_~=Dw{R z*qAz9t)>}evMw;eA*kv(0F%=klluVWldSD+>K@5??GB8f;v@C^Rtu@=(`5{*fi8`7 zc=ETP2d}Q3YcDd%n4t1F5%2!xgIz&dxM*y;)msG_^u-!%71!C+QUgxm)KDxmpu$PuCC$pZz$p`|?S9Rg8P z{7n9aGyP^tz!|uVWd*uMv|%6UgXWz9=F%W;2U_%3eGl#3bxQa;bX_5F1;o@G z{>yDokBhf&wtxlu?v1w*iPap~lO}ac^zzQz>JY;7HS&{X@HS#VOGjDAte)RUM;u{; zfj?0U$F_n0Grz$~45s|Up6n;V9S4wWTL6F@bdH8lv2qwLk@Pa0`iex2zp}E{N`HSA_f!1fq##plhG7 zdr^GQ?99{FAFeq@uw#qVHAVaU7cd$>3iDWOPPd|0yW_tT#P78|5b5zWVM(%+8LInX z)c0=s@=foaduHUhgY${lA5Q+if@(PKb-46$BR6eO7&!86B03T26S@}W2HM&dZzNHV ztER~OiP14rzUd`A`Qwhlq%`i^u3*`%9#uB7W0SHtF+3^eWHAhC_20WNcx6~q;{&4; zHF@nEL5s#v%yQ?!~YziWC8&E#6?h7Hp^sjx67_Hr%G zT1xO~3ho*n$}8WI8GkSuUJCKBv1u7RW5;)#{1(?YPuDLfHTD4AM~(AaZ$PST-twy^ zd&#{wF{tLKzrubO&+I1WBOcdXGXv|J9Mmvh7>MiZ932goL&P=_pPac2&0@*hipU0i zJ$G>o+c5GI)T_}xm)=!7hpdKo)G02TF45HOtNlQ5G&!V<{Ish~5PWP$7zv-%95)Z7L^e(=jD@>c zmOH86)_9C5z`H)x`LB~ofp*}3;D{BU1x(@4M5|*jmYqj3SACR-oOLZn#-d2){5OCO zMD@qEXkb6J7Z0MkM}Z5ejVH<=bt^S{4XRjY#~@USebui)2Ly=b=P)#FnIEhSYqJPfCKzpK+L{t}xEZ_Za@P&+QIPa<;*I&%m$)x=l73Q?kY zC#2$(1wH5bF_Le~RtVm;m_R=|H;xaVU^Z&Cbq`A5emxzhy;_zu;jni!I%p71BtIbT z_M8%ks2w_NN1wn&+1Rn^6E=z;ch<-1I(aYVEYX6B&zeO2=lgOA^Mmsi+@@$QpI!1| z?o&YRo&3@8cjV_uSF)9tllrp>$GO#uB0I}cxKA&BjN$en*I)lu&5jh&iJ26W??o zwLk|`fE46L$9)IRBFebT&`dEj1}FY*la9h=rprn4&ya^gKQ_G>x!vRbzw|5R`xCoP zf3JTdgXxgdJ{UUZlaSw+#yw{t8(H7&yZ3?&-}#JW^l%$Pzx0p>{pzf#|4ppPXw8@j zCvZEliRcGerrKXl6S@UOv`u;*N;$PIK-wVR`AnYv02|xp^{F7Y-&GEw6T%KUhxpIc0q?6; zMV|_h`W}RrFUBR~Sj=_W02sc=$cz(zE7(n6Yxy41CCQ5d?JQSP`WhUWaxJ=cgW&#iqighd46 zb&mQLT(-1o&w0~=jPE$N8G{y+@Ox&eatZ6)x6|5Cn&K{ayuataco}Ev7^dw;KcH;0 zdnR0EvQ4WoYrfj;z&&)yk{MF(V{ZQB_@LmmQ7}~FEEzFc5DPw_D*pL%wAv}nT;jX-$$5c35jjlWp|s`o87za;ek>OFee7{?a&gn!s=_W*@aZ+zRvZg&P_NQ~8|Daz3%hGmXGf_^ZFwvf(l6xMs$`3#aiPvzWLwHjFf43l{4H=ptor*~ zPceKZUUVy2ZvJ3D6|N(tn#qiU+1rp8-3P$)7{Gz)^|+|U?%*B><9&W=R08jBvBAbz z%FfCPD1xbq>JL!*n2-O*ZL)aFQxyi52(=m2riQa~H)VG4n&amP?VyeXmDAaojpoyU zChn{<6VuiHDNszl_ynZK_QX7TFqEN*Rb*Z?csNp?+;K6fpGl5;xf)EuJq0T38Z98(v&F0J(2-sd-$)M zsKv#f?2Hr=NsUI0cILf?!je;+y1U>E_`yaC{-JD24ONUd?Trc7@%2}T=aJm@ZrV~_ zz-}14m>a!lMtaGw8Vom}Rc~(+BF65o?qFuv1)idXAN*vbm3QN7FqV@sy05^U4ZX(?##$S`e4C3p#8tDkU1#1Un;?m zBOFGD@K^P2v;^duj3)iZltOv)Ld9&yO`8OfkZ$QdC)2-4AF2Az8yDJVXF0?m=Q)U!@Qk>#T=03;c)%Ng2^_j|oj8`HxaTH$7U7t_50%BZx=ATzhR#!^oG4A5+74AW&9IX~1b)*a zTg()m^@FZ?TkMHJkS*44iikqiyF+>w>3X%p6N_RV^OhnGdM=M>m&}!=2Qd2a%nPqV ze~nl8THQKcE;l`2Apah00C_LWz{dOss^{+DD%lbpPUtkD+;aGtB+vcccjvMJVk?BGB*x1hyE~z(qWu{ zY0mIQk>7AzxJV#H%&^a3AlCvv4IT>1u5nFyqI-T|XQiwE2}d4dT7}K6Gc&wEC?$bq z+5-E~Km;0{)5_L#N~jubTw^>cZAQyEdg2Z}Pj~;G+LrlNqDd!g8PkTRxWRczJx>v) z$j48dndBAD*y)RTsrj^6S&G*zSjcIgTq|KS>q=nt7JjM%c8jro#^zmnB_eNm0c56= za49rc7U_R5u~UU$5?tVhQAaV{rj~VR>oI2&W9AOlxvG|U0YFN?C6-@egFlh5WA=2L zXs6bOB_`lDj029@?ce@(5>ImJIn_Hs=gyKytp%&lOUo+^F&m8riX%SVMcp?cA-Y(o z(?GE|U-%3VW<4heymgFByZ2TR^}I$1yor9^9dXxl7#Xfi01d(SlOd}qi z;DqZOV-k!Bv&NS-$+aW2#&gc^d&f%Bh=XoH*1l+Zq3U<~gCEm>_S-W|LEF(#>&pjA z6dEzymQ0Z`=HPi}5lmD;*Oe^G4~=iNpGS`9c+H}IySH~i4I0Mh{s5`pHcm{##}B2( z&r6J1yxpK3bOxvs^O_1>fe5F&NuDMNz!r`AU7ef6Mg+ub^pAG9UAtn46ImK;mTEn; zY!k=pE(~l~8!+vm`uG26ItRbXzcfA)UXdaw1k$kEG=r~~%VDc3>ODF~m+JsN>DgaU6jeEL1mv1X=(e-GA6(22ntY`&X216@5p%$|RC2n}) zkml-1@jPd4-B7Xz%+x_fx1SfnFjy+KK*KS_=-{?B#$HQ1<9yzuXNNt^_B48($qmpk zv_QhQyEqE52aX#-W4|+9W!R10>K^`cR#ZxT;(jVc4Wg+G6q266jneuX6{|^C_Horg zLb1F${_;Dv$`EjlU1%f<>={353MkB$|6vY}L&^>_@E+!;%!HtCm2(e`(@wa5hQftVnqY^a0iKe7hUH&9h zaiP(DUA8=F>_SBymWMnC;+-(0v@U=gQ>b_fvw|H7br0F_+F;mic8E}&JYDhoGUgG{ z{+fEFn_aH)b&c;#X4N{oeYbCb%wyvTO6JOn%tOj#qhPNc#$DlfqF`b+&3FB+MlS8F znz{AWg1spBgs!x|LpPS*UoUZ*Amb;vX-5baS?ZqbvmL{uJAmcsG<=D-+mR?y5 zr=73*$!}L8d}Fn?XdjdQXL4Y@Pa?f#x2S}fn`Dy^Cnk+<{NlMP=m}r!b53l#uC2QJ z(_?u_^NXol4Lia$z5R&A!_w1+uTlqq0=PR4UYNer9>Tioq+GbT`^A()Kj<*NrU|#= ziET#Yf?u=wCJJ6I1|Uc=NMEDr=T;m8@J2w7V}8(=2@4WWbAuQMRiDV8icZPlmfM4Q zw=G^=F&rIu=I?r#TlI%2gNo-_uLqCco)=4NsNBBp=>MW%?x39wBqC`u3tK=vtA_mG z6m3RN+7w9Tu`oIf6N3}$>C{?A=P?`5+EV$wbyOIyf3m2Ay+ebe6w<-PIF4EdQ6k#;P)RsT%$3yJY1$ zn=sxCJk8SGFW_8?!GrnhVY3P6hTS{4(F^v4gpkn#LnnpmyhQ|z3;(SQp->kTFV#|* zODjZ++7CBQygG;-aVmQ39w@Jy0@VG|&3nTBZO*2MT-v;O%^m)YLiX)iuY+u%=I8zG zciK{YhZ-S-O1MfN+l6?`4$@=!>^|*_{KBAu8=&KFGih1|&v4>|;`O*6he;JCch{G$ z%jjWn%hxnj{7)17hph3Uk^LBCm6d;#>~(NPrz<=$ito$n$K*{>C_Kt3c2q?mf{yby zANR7kTN5zQNL)^%l#iwz6}e?qd?h%XoV9^!ET!&27z%AF!pZ3^2z+VbqXzv0ps|Ei zjO}_SHystz;-0rsf4Kpml8j*hx`sw%GT`ZFC)V$N%*fXFS=f}fl1}yAS(s^od7}Ly zTjGjbrS|nOSrNrJ6r0LEIbJHbOxJuH>j6xe&T;)>GRUWrFe{o2??~i;GI0Ppf5u36 ze-1xrBh-s^0`f(PrJUH|W1!{Z*-b8=vaWq49)`&;51@AnnXFhd&?d!YoDYMiQH4Bl zMU&L(CNc3OQ0;q1V@A9ylH;NTwnsG#7-Imoj5p09{!R%%0GmGTC40@ixOE5?$xmNa zDcVVh?*vTYW_Z=tBUK^%2|fg5PsI1+B%A3{-6CksouNu26&C2?SDc(sPrR*ut5AiP z7rAPcM7)s0;d}}I$_=LI7{E>p@%;p9XAuro^p?cGuz{K4G}0~~Pj%3(42hD+656e< zNUn54A|+~^s#2o~?oViN)P?^>aeN0TapVHkZ$$Xerg^q-&}C1{&{n-gAPOZnfEE6G zm9#ArXTkCUd!umk z(TNcVkeDdew4YVkv8pxAA>zkq;EJz#78~H-cl?|;`O@b8jAZYikyAGw)z&*qzd57- zWS+fFy$_t}9Dn|4w-a3#=3Q)`sAw9dJIl(d|L{-7j%aZNFGg!p`K^DCGy9v{CaA{P zv(VPgH=6al#MIl#|4aJ4^VzT1`Oc`|f9)ZcYum@&qtmRL(V-ema^!G{E&_sH)9odnX zJ?HkBw#X=^r$tEKGQL%&J*gEk5!(SE}8>vKoeokp@=Y9CsO3K^FXP~@vlTyEeFcIDT>N8oM#K&DTt}I=p-#Yvf5I5-W6#pijtV&vHX}GgQgrI z*`QRTGkErQ^+^zWY10k3cBu93cisRN#WJS=$NOfDufp%8Dhy9Mu>bnlL~SvxM1os;om%{}m$dT;D+o9(6id(dgHs9zn`w`3<+Dm@-szi}Uo$ zOiz0YD;v#aS98ci;<#3qyJOe)M>CH9XqE2b4)|{YehV7KBAq(bwDOv^8WXaT zN*vc6_R;<}=9C-=c@8wd$wITr)zBYPa$(r7MH#~P$$DG_Gfb`X@Lx2DZ$cC!H1xnd z+0l{{ty8jjxaBNmlW-)+>X9?y40xi}4?uvvmXJt@InS!+f9wvIzS+7Vs%=YRyjXuJ z&}tM~OHxojamm58SB^>Fk7^%_#JcP&XiD>R;H`Osx6HY`GcEUHitfky-Pw}1bL(5o zjx)|y5#z?=U(1DL-Seq`naz7io2I=gzj@aKB{r+dCE!u)dN=4CHH=OL0Q3$H*8bP) zOjli{?lb-Ufo@=fk8+X#;v(I@Y%zENX`Ud_XbtPl7acsD)_b&`Oi8jJXaBI*U65r! zZ&pp8$zFhb!1+U*@!@yBkr=C)^}^8Tm9BfYSs4GB0#@Ja6uMq&zWsMg@o^k{OY63A z(eMf8IX=ULmI%lp=9tHbMk5$5eaGj;V$^h;c$Kajw3G(4M_M_dY;L8rDq4nVNMds!hf&kWe|NOwzM*ul zz-CKPXD(d}rlw_tF|xn$_0SeWI_8%z>x7{&8<%Xul&fdS?05W9mHfRON3x^HpX|)j znrk8;{0D>oPIt#3|o%Jd9k*nEDP|30lJ|rcrg2I( zMsj5a95Y^N$7&`moay^uwB8SFgK)fwG(Zj2_YHymUt97;(GG}V&JHeL@7;L$yX_C# z`nhz`jjs>P5$6xXN@qI3vhz+~$al2I71KuJ3BzU-{Ut3-Ue6W!ntP}PX2IL{HnnHX zZ5AcBXEN=O#&@vM+o#yo){M%Q@YrhgfVG^`T-F+_)&>>r%5FPPi^Q)|)|t`x&e)Da z6vnL86cD((G!x*qZ$M#--pZeSEnEO)ft9KO)2dizt}@8krO=)PMfzz=!GJFymmsEL zbg1+gtMB-R4q<|8HXFM_zJbJMEDwtgeKT>U74{xYXqCO*Y3QzxxbYA2Y3EcrRKspX%a=I%>e=%ccuBqL6K*&(r{QAwXsa(Hfr&2Fnr2{*VZMUC zYB0lJPicQUoPL@->GJ5d1}HZ&0ueR`7dtB}-ImC>I(A6=vwdK=9JdVo%JduzghdN8 zI~k1vg`4z}3srSG6PtBnTyVahh*oi1E>Gs{aBH+zrCYM2cTKmuM2OG5vS#_d{GfHbk|#np zU{7H3`~J<-SQS^|n7sMw z7tk=R!;0c{JX4KnLz-kOX!5bz-z0lMt#r1j-yXjOcz~X`xOoF%(J3l9g0|tIC>|ND zNbA)@8bxmfCHFfk1I|>l*`YEz^}a~GQJ*aX8%j}pvC2yrPL#=2$LZ@H`+_UY-)uPi zj#*gj_7bk2iG#E)MGU0ZQC=z>dVN-kJ+Da$EX3yCbPt4#e|i`xmIFGf2kD+`wroV8 z(gdI0wY-}Al=z4QlT%5q`L%IbbYs#yHzF8A2KlXj1~CnK4o#pM6nd2%Z_xW1Uhf$B zv*|yK(>oAMMxz~<9<0C(r9rQ^J;ke#vxL1D6Zso>nZGQFEU-GhR@^GjyIT-@H%yPs z1B_PyZiSb+mov9S^{raNNN)ux6h(7X zx>%j}F*GtBDwN8Gpf#_Lk}7>J?uiiU=Lfoj>BU~4s)~N@(%zofBu3&@-olg-oAdZB zmLkS$GI}&jr&yl~q$`(e;|-(TIS+3(PFX~N(y{S-KXslyc<5TGgfVU{P|P)Kr7J{& zCX-}FbT03swi%<&8auu^Jmy8ai63$)Lqs=skex8ZItj8Dpu~JHDRGb36?@DWocg|FFF`}uSj^Xj1zB&=?vbyjM zOE&Be(^W>RRukn35jq{Tf6?F+Jbm$UTSLgk7S21!|-*H6@Kze z$)+p&q)j;+Aa_(XfgMh`RD`ASOr%%cUNC(3>-LR0$FB42uG+YB9tnHVyS}q;9Avd_ zNc3k~0|^i3j|R7Z@Ez1(a@k1PEg3yi2qq1*dH`NCdRP#{Q{xLAv`%C*;X@0uT17P> z>|dK`U;HbSh|4hU@pv1dy65iEfPn9US#QWXAqqR}F35?ucN-Q@{@u}(SYBtJPuWu; z+j^kdmjkyNXF7k*v3Z4GbISVTmOu8iy*RTYs382rJ-XoMudbh#QOgZOWtoyazqmRP z5krzTMD zOV=>e;YB#aD4wK!Z`NA_Yz*PwrJo0H&26Yg2P(Vv@$QH0~PEMeI$6wQ8UcL;x2 zQ(JrRN*%XH($dcaqZI9eTMDkzD#IA+5~6+ znGJN_Sg$pc9+>zcA946Y{*{8b{s7uwj9>hWo;EqZgiKWrF%#wt#_$LC4yg)!*|B$c zOmlU>g1Q^5_0!)Fq>V?bz2F6oNcBG{v-F9dn5aMNBIC(6q8y&Tp%uEi7>|ZKs&BpN zpni4Dz&mnHRa|4zr^n`HyhT<#y#F6n(}! zz)f~%{u^&u_Fc86ANc?Z91HD zNR&vHnTIM!u5k4OjlSUe4v%#1McwSoS!C=4$D@mef2k@GF?dWB@zpRk8VuZuXtWVF zsl64sh`tD(-=2qDo*^#7>%%*jWyB^iGnikPrf#w*g3Z{}c7or?C#+b8pY;>O z9=CE;p!)4rh%WwA!zG?PuGJ=+Eb1^yQ{%TAoS3(6;Z24<{fsK;#UY5c3qfc13oG9+ zPV*TXQsz+CIOJDYl#=fFc3pT|K^%+GQqSH`g~0upR#y8m>N@_$U)qtriS_*y@x9H@ z;qe#=0tW}YuSdn1XIoDNzjT8LSXbIkwc*0JMD!gHDF(>}$9`lgI)7&oFUb8(Q+7hz zB8IGm>BDJE7Pi!tL3w?R=GUU3JLXud4v@l48V*@IUFiIMt@tO%Gc?Gtqv56k?RhC3 z%*36ovFe|mCWx)^)Mhu-_)3n}-j3@`kAE48{whfaf`s$XJ3%`~aAb4V`=k>}XDV%7 z2&CSy3f9|d0T`$+=@|jE57q?<@tY5t2~^P~wQHVMi=8+B1tn|#R2WCoJxs2`7XQ}) z3Mg}_-TFvPC#er=`?xYE*j4>30~jTKKdpq|lMB+t-5b^CT|BRVX^Bb+kfX1=*Ev^+ zBa1dDJvbyC8s!JA5OuX*|I2fwE39o8uOf#p=ks2}6wVv`hRh;zn4k1jHmlZ1#{`s5wgVy6$wLONgih*#ZHy%Rh<#|uo&8o1Qv-?#CSFHkQnw6@lw%3oWmL{i5HPqH)paz>B;hNw? zIMiK4XQkAyw9b0)YIIAE6?Dl|Tz`Ad2cW-1+7f^C5@6n@V&t$iU_fxV;~M*%$U?+U zM5mhl$;)$$h|Bb1H{|aoS227h(ZzS?we;pOI$i7u0=#SU~eqni+?S!dZTnPsr|lZLS)`0g73%YCcuAX_dmt!oHj^~WVQly=Phh9q>g9z zyVg^4Vo=?v{iag<7Yia+OQWdSF29m*rln^e57m}$(2>Z8={^vAQJv>8HGi!KCNMAi z&mMPD#*lQ~Mk$seiw&FVp+>{;F6>Qooih8aAGOWAC7vn>~IzI%sx8ftS$Vl0k5=-=V%NH^l z6wSN^lGMwH!)Q07#zX81_kU$!y^+s^z!BaIHa>LpknnWEn%dK?B>4}qFlYe_H%or+ z|GT)7G`qO4fVY&zzLAsmvzUmL`2UR(eT-8U@^|Sa#?{`iV^w5gW&g za{Sy_YM)7!vEh9UFBQzleAOW`R(xY5g}{RYyU--q_pqoUwDB+P#|)JR6nL{}w2A8i z^z}&^?+~p(_qTOHYB>@@~7#_zhm7cXxj1rBTX78b6W(LoJo z^WW3L?MKX?=m|39S|(d4ogm&H*lZ7t#Hbh#`CqN5_gYKV|8)qIreP&7bSp^8zJump zm=%Z;gj#&TkcI+|&^Nt^oAi70o|`9j{@op498_E*-#?SPc>3H6l*sBIk5A_N{h`#% zikK)CA1-F?%puePXX(BVDkjguI+6@wzu8zd`5d0#tf{Svuwabm1-G(RaJs+}`jfY8 zpn)czaR(`=x;emz#Xy&BTKUWUu?F(tKBI=7G9Fq%3?)Zs@-wWZ;9Uj4pzby*!n>@~ zu2w_84m?=YG$Kw6)Awn)ElJW_z6*D9H+J3rBc-am(RH;Y@?G%hkvCCMOhq<~(I6g` z|Jm1)6~jWfm9r#7x2v(4wz!C~B1jf?Y&q-Eo~x9_N5$#yJ5vsmdSO`Uls#Hvv@Wr5dgRKHHKww!^jdHA4$#z*vU-|RgRdU z!x?VuAfIu~CbQQ5QHsNAWuEHR{yd;(GYV5IV1?;3|L*U)|Nc@-h5cjsrM4H9*T3b7 zRa{&xZG}<9XD2e7gSC3#^sf!$US5}%%ERy&*6Yn#-(?E}H==UIYHhzEJjLK*J*iEs zRUqQU8M5HvspXt|nbqZDNpn zQ&2RP&kr!{Bpc`3cH(lst0(0{Y=wpGDwrX0@wc9)$MT(bU$#>hiOmDg>?wF&nI&{d z>Wlm{Mv!5}SsePr($_w(szKhu29c`{Pi2{%Fm#*!s=Oa&mK@tDfmv^me-l0a7bX0e zUU}I>9`s`)a}_MT%N^@x-|_%TPryZ%XNJ0z*tu}XlVmN_0~MRNsJ1xsjdzX|5zdG| zjgB{dJ+a$|!>4u(`{FhVC8yV%7er1)jXz|aOL(!}^nxNfec_>zIDYbN_~+4%BwT1X z%5;VK;uS{RR^1W{_ppUC0`sGSM2DIbE!Yp3e80$djd;MB7ey63-6<-3c$CCt6Glw? z6=8_m-V>OZ3x&oZ@eK&jhDqJ^PzpW!5%$yp_s3!Eq)KIZ`$4H7G~4(a*&eR4nnyu)32-3fpI8^N zhG1ithQ5>J7y6pFyFJ^6Ued6+D996$vYS8qi_kyo`kMmwK`)22Jg$4}J?~zncYPPQ zKZk>CD>kP2I~^Xdeny=nptUu+@!3{2$`+;?LWm9|g>RTqIwTBBGe)_bfgpAy{h%x343 zZx9Qya9%PjzPmQSYr$nSe1q8*oB9ElVab_Pb!-=dAET-71uYb#hJVps|3W7T!mZJK z2uemwvUpGrhzByY2f5}iZGceUP5b?aU(elw?GVg=kWcEg^Ln^z+ zYtD0y;FT#A4|W=A-X=ntUdGu9m|Qk7zB56mKA;}P|4rq@Gr=}KQJ}=qOJo9=+EJf* zzMAL1iS?{eX4>9Qe;4>}_=FWJE_+g>J`i-jZe2s3?` zjmJZU;ADxbS0Rbiv`f@lVpctn)jd#Z`=M?MNAPius)qAl1+bT3Jn#Yiy?qCkk@I2a zfknC0?D~m0WQf(fNf%CKfB-wds|H5Sa0oY?otgD8z&TqAT2go58rr~kx6?2ED{OUC zzNM`MbFpBtMga{4Cc!sp=XyktBWWJgAE+NMFahHi5K~<){Jkekebk4g^~Ay*A8KYu z4qt1_GoqeI+Qf2UXsj9=L{b(~dX)WBeh8>Tj{sHNTt&$ip1sg!IpZ z{mA@U`qk}C_>Xy~MB&Z{T1|-S?>W_e;+;*7pX}QlGJ^ zS2q%Auc8Q4A!j9)17WC{P)5b3Smj6xr0S2Q{?VAf@jpJdbV#<}Rt!EEB=o>~89*E= zE$7L<>OaTE58(lv+sG8GMSDBKKgTRm*=)Q{3Rl@R zOfLZGJwa-rUw11DU-7a6XT~rZbb+7d4Y90RyC0)!A(e`1LFn-(9DQ!@5ASFQCpVaX zY9-Q-$g=EuiMolmZzRq;!Vb1+N#-acx{ij*QX16jc6pZE==#JkDwgcbF=;o(EEzCC z?bt2-oSCuRdh5QLoru!sGTjwQs?64w7beZ>kLEfwv#0}peUF;#;-fTJ@f#*(d1@0- zA=Ia}lGPLwBN*6b!gH%++k&G@bn{5$8PDQaxIvY{8F-e6HZDWM5>ICKWowmK|k#?{n>`UXAm7i7Z5wkYzf+RO12=R-&fvxPJBPn#&@1EdbH zeN@2Ze(s0oj1e*B(p?Q)ve0AoXC+V3cMrw~QZi#FUj`wJAHs`V17h(7u?a-Z1F_$!(to;zx3Ev1qq{LO?0ENBIh0Apvto2sKt3iP zbb0(| zg}v4y11yRfr={nHJd3AgE&h>+g+vR%?|Jx+Ru4}`Cg80`6+QKP8?uNc8w*b{Nbmn4 zvpt4Pw9Kf4uYfB`8?jExgsY(`P4i7jzi0iHb;*(_HrGdk*qgU|M97y5dAXyRWw)X5 z_BHu?C$)L@3G7S6Z3Ww9M0=Z&H#?~0tO^>#(uy(qR6@h>oDs_3>o*;QI)N(5$ z$&ow`<})6~wy*EE&H0&7VKM>*AjakRlodXP&;zc~eeUqChwrJ5e=3sWh&XRVM?4qm zw&z_)9L*`lW&8JOXaWoV#R;G>7=yQUAe^GwNo|+cR5$iC-LQVGG&p!z@5$!tU z=ND$F5IQ+=4-eNTmBeLDuthVTw{mSHjcO2XnvXs&7O@Zi9L_80J+A7qzL7!>bDXAs zW7WTrQ|BMd1wx)v?qku{yxjihc2(9q;hXYPvj4FiycM3(tP(kQvXKivA`()H{x_T} z_`{CK7CIG0hZEc~@A@=3fTrMllh;I;Fytum_l6JdJ4A1X`*KjhmO!r`#$Ua=j0soL z$SBX!kd17NXCscS_b8s zD0fwVfl!8CjsOO0Q!YoPPgtqq&(*IG%hNY@dFj(;E-7i5>C+4xk~@E|Pw($ebs0pu zimxmDi?7oHnp||=)uEUpVgBJ&ul3FkC$E-9*b`JkHFVeMD}G7<3O`x7)*Y#`yEd3K z!|5OVK0F2cx!qJjlz!d7nKrP_7cwbO({5bErazQSFG++)H2cuBhR6+v^jb3Dncr%J zXyZ0Lm08)@&ImmzBtUcJHw_ z!tGe@*KrbS(_Z6_V$+EcMH0&zH1e9 z1T*0DvyTt~*ZI3TZO}>++T%S zT@O~OozG9ma}mmV?P(=#`wuwuXlTvU;%$kGXiTmGEDBV)p#fInHdI4{C4R#+Ur)Id z#lKnLs(yKfak`*xJ{DYbLHoyq8&j^YVotH|^M$0s322ALJKj}~dyQ>2lC6vW>G6p) ze^DAadS6>Uy-%sCS@$;cG<_Bvjo-c)8{<@Qrg^&IRu7DAU|Y{=So%*me#(f_IhyD;LC#15@6Fw zJrLRW6}K9U*?&JByLWk~-8Ai-U-8kA*K&`~)Gs?6_N^@|XtuGDIv!WE{#8;mC+I zfmoa!lfqkSY^gUz(T5x5*`M+S@+&>0g=#BE51q-}m7z^72=c>x9MT2e1N_|e!-h5r zk?N8Kstxcl&=dZJ#T|u}?X^Ihz(#zW+|^QY9EkQ5M>2>%FPBHrri++rXcare4E>P* z)($nXzRuh)@Br9!5r%mHC!&%I_mGM+ zg6|W-m2_tp!H=D1QGF#p{7P*?)L(-~%8HK}`ngUF2P;}*b+kz+dS+aCWhVKC>7Dpp zKG5;bFVnh@C+Y1Y&rk3YE`9qKZ0ob#rH2%daWRzW`OARn`%#6+f`prdj}IpY+ncxU!)r;A+Uq{sKe&!yHfSu7 z+NMs@<}EM8Kky;;Xm$g6N(f97^yR;;CRu6I6hd%xs}V)$v_}E2k!pUlpI#x2jZ%#$ zDtu)Yyhudl8;p>2HXzg zKQ!Yvggh98ywjLR;KhE!A^%oRC_dq)CEXV7KrAm0?AYqXbZJTx6gJt=hu5j(=3jwzrKVT0_^nTr_(F#)cWcj;RF*H6h#C3itELL+J2|6z=gRK3e3 z6yQ!I6!@ld8TNf;K_YgvQ&WXbM3HTwInP5#Gh=T)e$U-g1A+~kP7F@8I$N{@(sg|| zH+eKa+=g2>ZM1SO-N*o;``f_E+L25t%9vITZ4_n_@A+}F^Stq*=d+C;0=27eTO{e- zaGh4eIyK$A-3IH_3~Bx3@u_2AvXXi?!+gC*HAAa$ofd6`>eFTWR-OP^2}w#{qjqTV z26pJ<%bsMeJdpZ*ljsO_U|l=*Kx{UVN;dUCITf`fCyd(ej}F4BbJ?&$qKvGNDHZmi zlIEv9T4UKjIDu)`*}?4WkAqe8|6P!zZ?Mq1ae_ol<}{W?eQ0`#Jv_n#8i1u0p(s+F zfdQd1#;~62Nb`ml5X_|v7{H2J62AA+$l2)F9e-a6fg!#(8b;}=XZ!GJ66D514%J%Z2DH9jT_|{fWBK;mL2KfF)d@78;TJi zd<_#7I@e75N}{rRUWyGGX!VcA-aWH=MqGf7C}wiO&VAw|otBzYJl=YXCMLM5Mp#J&SN0cPuLXz7X(JH1;b6hLs2Hy-mh!4c zdt7up29XzP{&uFXQ=tZUiy|?W=Jonf_e4kA)xhulJ{=Gt zW)Kjw(7*^Y2`h!+xNGt^!fcc*bhgZ;CPg;)ezuzK59JbMTS6oL?TGb0W7J>S;$km) zaU!T=QfIWjr!zN&kTisoq z^H+Xa3!uq~YHiAqfz5?7Fo+J{w7|+9RQ_4+&|a8Sxbt$nXZDs?xLvU_tiD|$WOGbh zT=M_B0OG*saUMDcaGoUVmm6Zsvw!AIvRsX&xyn_>ynqXJUvPkvd3($bqEqC**$vJe z&}95&gW7&$vC1nAMw1TS4?ges@|)ZWqq;9-zTchEi-+)km2N|Ab}$Ocgphv@_H`Xb zau-9N_B+?%0(|b!f1}qP1!Y`D`k}91bJn$&RtUb^Xoo!jdIpXwi*x71=iOC#)GX6< zAU+;yg&e5VavFiZ9;`E=gsb7BFYapR#jS0UPU;njufZF$8*KiNZ)xI)R?Zdv@I z^GG2w!6;aNz>II6&~PF!(|8lL1Yk3Ur0DoK6wxp=J2xDUH@7Z~^BXJKPjT8Eo%V9* z!x4|LY7PiCtIB|qf)2%g#HmBHzfOV6-4(KSSe!&0 zA6qJpZm(5hb(jD||Lp6OwbwTue+W`P7y~YbfNPCM)NL{$Hmw@4xau z9)Rsl`gEHLb&*3!=5#m2+eFa+KX=1l`>}E(ub?u1m6z<0&zfmLT(JdT8t=$vw_~P^ zj;G!uD+!!5#Ip1>2HAR=rqIoOnW@0~!fvUoy)eYWw1v?BOe~A9a<|omg^>!sNqi8= zKV+$x^#0d?P4ad1N*E36Z!MwaK)DO*n9MFsOF4d{&Z7t!xQqFv=ag)^=Z(@e%1(1% z(!i(`*ROQ{yKX{=L(%--N7`Qt$ZnQvTCP(qWV^t27;`*?oth-?8w+xCSO^P)P3~f5 ziO#19ku?7)v4|JJZ$-YhlWCb+lIg^Zbo(+_4v)q|nqn;{P9tpZ9^1E1M?KHu-er7j z+M7tp5Is>xKvKbTS%V8jFg(Ok<$v@sA$${-#+uMq_=YLedKR_kS$qF0zoqnZ*b~V7 z91VWU)%-~sX;rdlxv6MDS#w+(rq_=-vJ2rdH0qs|_yaAW>`57;=!Nk^w=o)=iy=LS zo>+D8Rb?Q!<^!XBK#UaIvUEaXHs@NyIK0{XSx)E&V zfU$JwzoUs4#(T2RPR=h9J&Sikd;AQ2KI>|aCOP8LM#?m8(0g*T)kb_b=*m!xXev(; z(L8*d@dDl+V(Ijm_XG=I@Dd9|B(dXCXPWXp&+I>LwY*LjWQWlCP)DL+u#3ZZ%Klip zC)-v1+QZlWfJq_e*En!{ntY@e9(JJ);LZ0*uLC)sI_q>C-X{08teB^IfVu zO6Q7?bMc9vXyIqXkAB~nh^ZlSChv}Sj+gC2Vs)aZ<4*x{o{7*nYVykOAB{W7A5}{^ z;mat0)O@ZUZTM|hAM+9QPBr(L9852ZfUiHteN}R8%6$M#@Y0{~MEWl|h;hzU7ij~_ zA_teiSKr!1@F{)G+2_yAzf&fD`r+o;p<#e-A(4p85dx;wK#K6Fn+&G*$tCQEO%nmG zG($u?DHjQ<#HRcI06KI2x-W&f&wA+|;(BUWOYve|9W#0Rj4GDaSlDoLV<(}1wZa~1 z&KN2`$s!~8;21QliW$``CSsmN@8B*9Z0dls^<)k&EeA+juD0};8(*KJOsQ}y^U1ax zpDU2ckCQjC+>5RG@?$iQr^^*Z_Pg`lGyyf1#ckT@B~`k~xn-2O;57$Uv8>>%0FO%5 zr8;TQN+;xbq}=wQixv0rc&f3R49K0g^>dHH=3&A0&yYaXPF$yDg5GFMiOxP73=#gQ zMOh?WOX!GC5;B6+fj}spEpaBl=?gP7OR<`jk`kA(1SDC6PHB;_eclFpbzLKg&0^u8 zGO{h>U*JsOrIVUb&}wrQc@H1uyhfZ(K5a76Kr;g%!wv{DJ4CoOievW zeSfXUxYckZAdgvL(R&-J-YhV0kW_8*MUocCIK$pD3n7RZ*ut2AJwT;AQOnsG?urfRIXaQG8Pj9Q8 z!Y17PjuO-hhpb|)BMNW|d*iW76x6(MqOp$NHQbSmX!)#|W|NLv;HE=P{F?jO@s(o& za8;bxx=H6PGwRuH4jQRl>us|zaTF`B7kPi?0Oo)bF>-#;GB}Xtme5{*J8lrdSyIv% zLHEn|_Y6~x2}_APBgwpymhNlh#V|ql1?ZPg_A?g7)z?jhy^CN5gxQVbo{QZnV8F{k z3~A((l-#D7B>BY3`Eq(dNE(vC8bT(}f*AAr{UFSJ4YD(T z(Ar_DuWQK!oWope0R3<31O+}0^~rOHCS`K8z2MMkYJYqHR6qFPU;95g#*H8+x7cOFL5cr9ozE7_)u1qKfyZrb zH!#-7u>GmFxAj+kVEpv>41LYn3ap&e@K;HLRJ=e7_fwS|JIQ_Zn&ELeGPq#A?%Oy6 ztr7-I8AqR6ftNGAUS564h%p>rZLb4$Hry)zbrwlhZuDppBSB}~@aM?Oz0B>xHgg;L zCY7v?tC^``5bqsbI4D)XSjqO}S#8#pO!Eo;!HT+^86nxP(>| zpNQ~2&AaLbg|pV!bUoRY#16Nlb2|q*@LU1aCJ0d#?Ux&TO{MHF^xK^Tc1I=X*34>H zWlujR5$kj7LyfzzDNzCzW_f?uV`i7bq~#h=^LO#kQ(*hHf27d&@RtiRw@2m*>aa84 zB0r4s1=!w=^t`GL**$dUV0g+C)Cv|{1miRoEutM=uDPz-@#R^zE7B zP_Dlyo#+}3%XHKyno@a_49`&&^G{0Fhlm=4&544jQ2KymN9e4&^9y=ypW>p@W7V*i zs4K6%Si8iZa(W^!ZjonNG^L)mR{hxAiJgy1nI#ZXy5;HT3turjpMK0c>F7YN)ebGO z`x7HBJ2N*il~IMO5`W}z;ukTV?RaS?$!~YyJ6BYDb5nLFzvlz+Uu!Stb7l8;Ig;1I z^vGy<8AU_Hg;iYYd-eW}`v8w{`jds+1skt_QPane_X@-GB*yg6s}DA~RnD6UGg_L0 zaxWC=(ZGVt1qTe|h~>A+tFT(z|MdzFu#frLJK0w%1=`UG3R zJjVfsHj-?@ld89=<;q7W53X>)pIzEj)R_=k1Ma=I76a#3MNnOPt9nW@kB1kZva*AO z;!)iy|3mrqG#x`s%zRpw(;@RU^y{c-AV=>@}$&6D;P2{USKv}zw(*1pKUTjLcupFaCdgAq%s+|PW2ro&o!wJ=`s=}@w_ z4xufm5K^H{=C~^+$mENTdFEK}i#hiyKF@}vMrJo;$eusYWc8XV=ei)9D7@4+h8^lD zc*#@*cjfX_d3$sH${*hq{hf$tvy@Ywbn&t{)%MjF_{Dkm3)NR&dfgBD*#M(H{i5e< zeCRnw14U{1@&2g8+>cm=<${%rPh5p1BNY3|X&;wWdGO^!;q+(j;^X(tc$49Xtks{4 z$6%^d38&YRyH{|fFlI5PrJ5|m(l@s)SHDikuP&eETs864jcaoSuGiFmU0Tl}CUDNm zS2B3yT|c42-G5?>=Nf&bCGeUwV0~DIt23HJ&hSM<9nMpP{d8@FNQiF8&3?y$l?%LM z_`8r9I}aVhCca6xopL)Ks`+jnFX4@8gFLNtG~LzTTjiv=zV?6Eq4&3`=*}PjMp$VRlO2(%;3LEZJr>rrRgKrrYsPBvz?L#yg2; zWa-Zx3Ik@c{rCxbQep0z$;53LIFx?KbBiQV-60upQ?y}b!Q=+1BAx|0i68=n6kOPK zKv4y&^#?y-V}*vt91af{g*UuH<5ZyqZTK!MZB;x~=$>@j(3Et|bM~E|i!0_-);aXp zP;97wwq=IqxaC2an9P;VPGf~{va4Gqa5oZQo6@QK)ML=Mb!iflBCfv781aQiSX}Yt zUbyj!2X|IE)vqq`1-e#V`5<7;2aFfL6!8_BGp}T@@-GffE3dSrf|XapxmS9+)f1k^ zPWdk5q+~R1dR&TTHgd^$^{4!dx~r^e^HulQTL&qF$E3%lNp^3@3ArIN%SI%lznfwS zfmY6WCHRWmE#Jx)ZR0rYih8k%;YRpqbMf&f|m#SpfisV+kp7r^l$tN zo3H-;Umpn$hP@&QIdkwIZGo~K*me0+E+OaxBJqayn+=RF9Y&?iIL0h6&>5Z1q4J zR#@FtAKTh^1zcf&n1j8XpXW8+SRK?Wu6hL){%mYgpI+!uj1_PoUl-9`-VZ#c(^!)q z!;Hr6WVn*iK<^LFKcx=~j4g#@Fk!@op3!R~7Av(F!8e{GqdPiQUhO)~Q)k{R9pA>Q zdE21njEpTe`N+LwpScn#Q@P-%tJ^{qZW;TR8?{>hG}{L;UP<|&WsqfW9c25Ujeu2G z@YI`YJiPt(t!pX1(6d9x1Ayq3yME=1TaXtSrEH@kd*_<~LSxE+$W^d{R1DCqjoxp{+6yIeTZ@nmNy376VAfArM-Ei(Vw@SV++c z#<-K%rzVd+{*#+8eD=rTw7Rd81YT1HtPfs#&*rQK0ow&9!<9OcI~=}*$Q_@X{f-X; z4txL<?&^we^9@BoC-Pn1O^1I7J0*YN0Tq*}#VSDx043$KfKbf3PV2gLnhCB{ z0YdXbs%n5@;S$VB%uke6R(V!rL&;ET!4?fT2U}J%am!~Hvn7pz6{$N1!doanar>B} zmA2hM1`rlf$@Y^KznN^Cd*XYt-6h*2s=^@CN6n$an%-{GcX?a@@hU5C*)EYYujU7MpRqz7ycB7U zu<=Spt83%xE4|e7qX>RMVqEF6(ZYtQgk1o#_N5zRZissfc3P3~tAmW$yjU=PDXpB? z9dgs^E7j-tf*v@PCh1($Br7(qydec>_{e-8Y{U~DXnNb@OhN^>+#b*tMWXQaJUb*H zSoz)m7v8>k_S2u=n{*$LUo-SMSVHD2d@&k$?n}21_SCOx71nqEMVButU!(u~1z{?r z8(4iuZO!*C^sbu`9c-f(ne$YlI|#NE>W?PorJNG>`6m{I;_0;tz)cBFLln1Nmnl94 zL{urpgbFW>STI&jlo^E%p810`oNIJlh&mUG{>3?UPk#K9Lay%hE`f*MA;T311n9ga zIz;+~;>`T+BlKIsbe7gzQzs&Y(*;k=ohvY6^gCaqZT-i2gbHsPuIiawiQV^Yypoa1 zXItS*Uw7DsD?jrvZWs?-xEhnrn;OYcB>SQ%+BRIxd~R-@``T|RHG>3R@e^lE6W1j_ z#;d?yjPZ(9Se%)3eCnd+1s$?Ioq44(i7`v&c53Ty9(uOAe^jOc_w@{$SfCphRl8hg` z^w)G2)<1OW@g`2v!IGoWSQpcObY(Ws9HJ0^_Qh)YcmL7U99^M+sS#cSftRpv@(Y3g|INIV*3*mXg5VWn;opJ{<0T>*ljM z7wc~h4AYTM3`U9iaZ0!MZO zt2vKD$tHeXz9DsjyNH47f@CM@4&Xr9O}E2!C-pAiP?{m1Es{iaZ<4|C?d?Q@J88oP-<(M3B=DHS6A$RkgBz2t9!{=@j>#57EM?%A2w5%Sa||>d z($|G?0aHq6bl?__6hxN>f+t|IR8sQi(Z~;qM8NA|X+6~afH2?SV74P2ozcT`ZyD6X zk5|Fkr&Dp!*Q(DG2B7~JYrM)9S`1isHeUJH2HS8|E3eo#=#^J`S@QWvHd<*k@I;7_ zf;dXR-W(p|9w$XRZ5>qQY*^EH<=}SJJUlZ%_xBj&HLbpie2~#}IS62IQ*;Up$d@pv zsxRFD*vT4zW;L}pdS&SnM`jOv=VP0vf8+O;=o4ebi;3G#ar)K^a5mNJOXcC&gXaPT%%N`-Rj)@%>9>t=mmDgWFiXY>bD!c< zK)^~dCREscY)Vl1%hOar#=_5rh&~sKEHF4f{DV3d>lZ#jbak&o2|VHAcFj8`1i9>bMo@AjxRu>8{|RLpXvX0EK@t|c`M3_~F@ z_SV7r)j@9^l>R|xS$&nm+dbD|tSH%xb)64yr|zr@8fBfDTfSRDxPJA8|3JqUtG&@H z&wh4u|2O`X;OGPB*1e8)RE?+~m;Nj*WJsSwAD7tZ8&uC;&D}N_iJ%X2XB#0(-g1D4 z#|JB!_Bb)prb!j8$B;5YhLs_y(e(Uz;*g<>TDU5-z0>!3SQxX?%BB&I7`#LV+;MzG zlpi_!%tJMs%)VRO3Bk_W30awU{(OXx(#VK){hE!NBIDmBjW0SC?fB?{^;|5;I z^#-gT(RRVp>cD3hVAa7j&MFjY{L${Z#S?%+S{SvhYxXsulRjNIt z;Z=ws-I7#@C7?OZMH$l0!0pf$lWp`a*By!(a45qek1di!3kcZ|gs-FA%tGyJK#i4Z zZ;(tXl~mClO9JM9hBb}^upRs{%u;6eV`X?#TVdOP#RJ(5#=vmkwDs~b{hX#*i~%;j_=$(7p7d$@wJ*GB z5O^!oUCGabX&O=Pk?xn`e@gw)5AOBw&Qf_l#4FCr68ZJYs~E4eb#PdHHJ%6J2VOtO z^6;ztD(r!-D07O2LS2l115F1lS8pApn*Qpmv5WBvg9Zt|_-kxa+3&{HSI7>TMGug+ z+z@Qg!oL$9VpoHT>~ROcdnk87`|=)P@a}kr<$vRQzuntip8R$Fy4Rgtr`w?m;$F59 zPOGr!BhugQD{500u`E+)N&1?Bv^RZ%sKP|u?g$k9uNM*yb2l+G1aayE@FvRhooz{q z+ZPOua299LWc!wBKA#(1OebH&;43@3^P2;YIC z5&hS`@{ff`It)+_n@{*=^bKJQR)tsLlu~I7qkPYk%N^ZeYe|Zp@>g~yLrEZc< z)w5SQwM=f^yt22hQwyl5H?DqpFU`vXoN_Mq#NFWXsYmH#`+4v4N2q!Zh|!e(f=o#l zeslk#z3IQg(vLB;>c|<6sv)1Os$!gI71qOAh4ti~RallM#;4@rcSXq;nPw&-U*?*7)TZlS&$E$G(+zlNV`)dV5aAYY zk}4Wuf;p(0sEf8C)*KZ{{<&|HK@!ZA^6mmViFN?HWulOK$dX|ph5{~0_op4m_vE*) zDod_QiUqeNY7sj`x5axPORxZt4Y<522nft9(yxd(sy1(OAl;9QHv#FIcJ?W99%6X^ zkpWA6jt7w=wh|H-9vB8(w$C#0+~j-+(eO|RPB+-d&1C>WwwV}%&twR_OPEPZQDQtK z9(SRYkQ6@D{hQvo`FmgeUBy=SMwI}+WOgLOmCw_W;mQUq3@>ebDWCpLr*rS759!RE zyLRrPzB!5eq&Q_uz#WWNq(J9uT(t43w+-^^gExk4gFdjGm-!k$q-!Gw!>Q@qgvY{7y# z;eN05C$L)PP5aHvqm}|guEg=*_E)|`2EI?9#C5aSMyNEcBNZI8IFZMQynRD$rZ$?- z!ctif9sF?gU7QOo{VhN52xv_?^gAyoJoF2|HbRoHpN2 z9K$nvws6OAmx#xxAfikpu|nn3YYh~iINVZ&$N zc)?z*@#@yCt9l=qjaOQRrGm$JC7~(rx`uGe)fHs)y3$lUVcr;fi)KFJuys(zD=ryd zNRLjLS6}grIG%llewgLJ>MQEs7jiu0Ze07yA=JwZKU}7iH*K9dC|qst{waB+e@g$V z+H|RK3mxx;KFl41AQydDm!GNq3_Qzy%$s|I+W$e<_Di zYEMefR>I6KN>gQ|)hVk|)cgd`q?^-IC;AZ>#?WlU5+DwgTpO`ih(+Gr*VsD3KW-!Wm zrYY8ycFt>tcj)f@F{dfj4sZv6SeI_B-~v-=x9De%yY(aaL_P%;Ot&I4w=KlTX%{s? z4rQ{%cZ)1h-60!r=_vv>%t1x|fA-$}XSXD~>wC`qbWiv6jORW*9*;eEW{7PZ5FkZF zNo>Fiqq$nhj67YpfBL0FzfI&e@5K4Z^<(FWLNg_p%01g=2Gd8}? z*fSn`rsvY9`}Dcw`}uy?s;d1w&wD$4`t+IUx88T}s#>>Nwd%6>UQg}XCRIkZn)Xvq zfZt(B3kbOT=oP)E;tL~BJPPi^>dRh*<(t3!vgR8<3GY7>4@Hu*+$1-u10RPH{OW|R z!-)ltF3z{OPF{w$tOefxC$;KIt2VrkjvidJ6v{V;bmY0$mCQq)&P}IMc-_t`$?}gq zDw7AJd~J|d2iKKXTumuVb9_6mYSVUBU%h7M)oXce(EDE1FVc2GX@Ir^Sh2-0c1SW{ zeUR3{y(u)sH(niFbiDD-=Td)>;b1-~+<5;x?7Xr!gYiA=R)#x?aiB-yB+BD(>Opb( z%l=ma_#Z@vHQlh2V)Q6_lC(VmP$fIt_Vheew*BDm`i|XaPFbY}Z$b%lQ95-wLSASU z){oU*Seje23QGdU1)DVgdYowa(i2tJ6+%xhx1B* z8SA?9)|}}NFzq?6fcZxRk2A&^9eQ?ZtAiF&;VWNY|Ej_dI(V38C!9Nd=aK128yu7O zHXlBfZ+`Gezi#K1RxdZ3D*e1VsB0bd%Ee3R*ef=|PF-(4X@{M*;ojZf*aDtp&TUMAhk6Y) zTp#MB8sQwJEkz!4VynfzmLnwp@YQv^SL3En(@t{k+nnOnwIM5=uI*lU-@mbY>9haK zD0(EU0}rJcTiz3kRC`|H&N68HQ!(Tzz)3AW5GDnOM3kff5ps-C;rt*NDCNP>Vf+Cy?!$ zcnDU8slcCyCgx9rje&Rlx*S+sRDBUJQn)y-80@gSPCD5h1rvA#90UaDl5#fm4HF`^ znKUi8dAP46j64|;jeIAH#%)qepe)ZcT@t{BNm&AXhtT2=$zCR0&9g{v>Zzn5O&Rps zj|wvRNG;?}9?^Njx6lJ0`kcUL@;#o&+dmIU`-sY)CgddCp3FV6WOZ(`!jpBf^1=5- zOIX#lx}7N1qGr$qgr8ID;tLS=IIwh~R=>{8U@q9XDJeN3?4ks5alQ^3m>xsYeNOn$ zM(JEOg@j9baq&i<%T3*}$y+nL_m6&;9amnhp-rH5N9c!>cD&@qoBqNSxnXD1_P%jv&NsCNQJh!zwwt!gk(HHK`Xw5tuOFDy@9l`v81qeR zVvihj@u5?t8DAcx74yKwtAn?{_5GM|`gVY4er)vslEud41d#ML^^s3Xdyk4^N zD}|r^%YVDLE~BU20`LCtAJyEWwkt=;>vvqyEz&pWAMU@(59d|gV|#PYU@W>TB#NE*sm4)s8FdtiRZA zw!v%%95Pvj>{kcnjKGP-sv&snB-D8B&D*`gt?WGQSM{|WVl(VPy2pctRYHNdDfis1_MrW(m(sNw%ha@~AerLBZ*j#d z?S&JC9Av2$6FlSX$Vx1V)PWNV=jW|kyw)n2uAWY>>d-5vtPkZd)YJ{%mlF6%*nR3ZCf_y5keg*>5>DP1MH%nD1}_i$yjqw6SyKg7@B=tD&VjM} z&t7*(m<&9$c_W6qSb3wh>^63cCYc7k0Ed!^t%QnZB^F>o)mWFCy4y%^nbZ6J#P9K!@!0Sg zXA4d%uePe2Lw+1i|5X3*zaHRuH7S|%*@^z;(24Xx=Il7Hcs=E6Ijpj2`-#pg{p$Rh zHYa7pl~-LYE3fYB)iE4gesz#Lyt>gP_QAm8zhcr1#^jAiy6J_!@w~n)g`U!nBOAAi zO*AaY|NP(osZ?$6yubSD>TMo$FktLhgD`g7@Cr|;sN){BLGGQY$9cke>Goxz%it~j zi-x&q=r(Aek6k@iRV5Jf4Zq`CcfauuK2_W^I{F%6%WGRq@-tH}{n$U-z2m$2BB3+gB08hN<0g3W|Ke z$T}3fv!#%-`v?_g#RjEy@yY?X3(&tIyt&gHf-+$ic{60plJg-{*e# zM;D=&$*0W%@BZ-jYTm(lmDdKdK{$=eQwA}uTu&TMSAL}FJHY4leBX)H=#!EZ#_Z?Tj*Hu?{zWkG7G1G|C&lec@q9Ja2k0j<3a2$3< zDJNJ)8EHmaaE#VT(`g(4a^&8>Bf7K0Nqb(w$(~nNuT6*I-0zB&JxklrF1CHyc+*az zqy4H)XiJXRpA~O$T>16EvOm?s&b%_{)mMIXaDVj`^~4qrc4PDQ)gBUZ|FxHde~JRl zm@aCiXlE=&0L9sVIOOOD6$6jsO14u!%_=A})CpSk1|4~gP*QPr;fxji1NyT!wNLwa z*4L`WQ&(X<(7lg%yWQw#tj5AAg_LZ{E*QCTI=N4vH_x$jQB$hjN=s@Qrx*M&h4V$g zWhsNvm$NGV_r_IJhR?D3yC93efDx+tZCIZBK1@` zu%`1W9i_U8y9_7t!E8+t_zF9O1lt`m-O#Whm)rt(&J+iy*5d&21OZ(WhVdFYw^|?< zNKHUj@i=R+Y2xrjk&CV`45~SH@DUjBTx11g%0=ba4;?G|5h&C$%C;q4F*$Rl90Eb>=AlON$(X_Bsj4-o~~7qXvk#yvTE zWXTo~(iKly^)N^{;vBi)wi9bytozC(qNa-$i{OzQiI6O3FdRAO^2O!XJ8-GfU2f`b zdbmh1rUSA#L1{6K*ykcUO9Ii*Fy*|jrVXVG_CxfnRQJD>Fi`rud@)&)w z9iKxBxklX#9j~}^r}q_UVS&57kkxio(im)_zZZVnM|Us%!=F8&G-Qd|QKgf)&})PU z&lmYrqRP!f0DVEyQKs$9CT{BiffEZI^n8_K&_{KGj!L6H?@X8Yz4wW*6b6)_WybsN7!+GU-R}c7_ zlX=i*-t!(q@N%f{d3fyK;_A+qe#&sKx>_6#>5z;sI&U8&8G)5waB;#+ zcxW`5ie}<99t{glT8Sn{r#pWA&MWl60m4egT6M*1x^j9j=%(|kY-M`~Zkx9aX^TEg z6MEa(*TXoluH(egfwP6+Rr##Gs#gcc>Z`?})3#mhgFWuwi__}2uw;b`P<;MuDADvp>6kB1aRS)7%4auv%7s?IgT%m8D4PD@E%zWK5m7 z7Z#K_p|}Q!PWoi-=VG9;z;%1NAJVR((H~es(l{OApkiQ1g#z}*<(g%-%;CL+nDPUb zJ>}mcFo9eb3`vWxb}ASaOBhlNHCBo&Zp{gwPi+_r)3( zee8*+#({bg2d{<>3Ee{r?bCAwn(TGO%2AtZ3l-Bl?;L8q0vpkN$U+!l5W(-d!f z+_jkXNtqMA3m*+FwH;^%T3*7 zgs;=USIPVk%Y!Vdu5e(r!%DH|RXT+aqDNSDh3!ViOQOel|7sfNRqc7T=e**93VP4~ zJRl9{6|1j$&nxx-;Q^|Sa#+QAr84Wrl^9(ZTMiyzff+=yVEyVKbTL8S@Jb&67&pF& z=0=+Gj)eWySJ>8dXMpwLBpD|XKjt%aIU3pwZI7cw=L8~)YB(;zWSO@d*9qJ8A<*;R z`4QXitN-K|mFv*LI$!Y#;618?>`Q;|pY2}w?r-;`=3lVg#^@H6!%%II3b@atGGJ2& zJAu%FHh25!R55kRWwyqU$bcD7S_hAxZi%i(*+$@mh9Bhj5l9oPB9l{;6yQNBPaxre zgo0C7GyqG{6{`bGCl)PDCny+Ip#Qm(01J-1 z!H#P#(;UmgJP)hP#aVSlx>j6)vttH%^reTpSHAMI8U~tg84H?sv+}B9!LsTKrxFL_ zhkiNZ^uq<_ewjSDrr**rdYVURI<@mW$ag;Fd8GLPA9&-@_4Ta0qP!G&#VfCHD8_-+ zZIX6joavCwh;4n^1;Y~~!)NeIyFh6>m($SKLR}a2Lk>Uga4p0xb`b>2(omE#)o8!vH(5Y9}s;<`?(zhO7oH+x}WYB9D=pn}%@aqaiD z3X2Jy`P0P_LvvlLu>OGd!unrZn;E-Ytz@+`2?bLY{g6Gb$mhuwv1-?<9@mKQpbI+E zH=&0^JiwGTFCu{oWeml!X^Y9|&n{@d+t>UBapv2Icdu zmo+dwj=9x|CA_-ni=X%*?ThtiDdsXfMhiR@4lMS(y2(XF72|MMOoHxL5Jv~;K#O>i zEGhLWhn7U=o9GE`s)_5=p$3h4a&gzfBXF{3#v;E;g4MOMvdtL#ogXmpoTG4M!gK`G0yvdtu&xP}o2x+acdCtahh+|Y0l z1i_GM>Op12HUdn$p>E;;N$3Hg>UK`3SOB#(^YxmOXOO)tdru^lm?uT~!dV_eQc2-^K!IcLWJeDJ7 za`L^k;+m*f#X`?YAGv@vMt4lY{9-@TdBww%ZrGut|L3M%E3eqFomU6F-xYgaW#v^I zSh_ykS2=cADeZy9H6yU0HL4g#OwuRw*~75V^VLDFCH}Qm`t-)_$ZQAJ7yjSFSK(Zi z__nLBuqPOwOtK~eSny5O@gz7qMIBc@@@IvWhV+_0#@q6t>vYp$)9yP;_iWW<H|2|d8@%Ie0(iX|KJr_rSs*@>T6=oK2~wsXXQ_RH0YIAIIfz8e&`iI z>f>nTd3I=rqNXs8bTlv6$k#j$pJ9x-nT`ycHSj~rC`%q^LOZV>-21BF=L_4{29aBV zV)b`btKrxqiMB|4WF=!9R*XBG9}G6xmWf~)Z&+PGy0Uw*%rG4^~@_B z?ZmQd$} zJf|H;cwqYl0X-JzlHG-oG&>vAkv(~j%Z?s>hVMy-u0`t7!z@}%+?t;>jpxG0Uz}FC zc;*)#L2F{N1Q{U%Qr1zT4+CvMsHV*@FY`ysAb#fy@2GORyp+)nGwxxn3;NVC^5Qq% z@gDy^{X#>zL%yXidExf)!wv^HF5r`Qq$zuk7Mza-7nr7Aa0h?01%(!GjChhDO8$|V+Mf_5f>vQhYR&JC$ znK?EuJod$k7id9#2i1E$7`(tcjPyttWBYAE&G85^23W(IU(+ki zl0HQbn^>f|p2V(gXC!;Bkv)2DZmG|H`!_ntK3EB>9-Z#Exy~1}3M>6X{JBUUwEyfM zU3~Y68K=+CM{;BZiu!{3Xv2uR49wK^^#hjbow&LJcG$1NeicW4B#NUr_XJ>93kB|U z@`f|E?W|VJxYwR{SD(4*eX$G&rWKj*`(xj|uhnJ#30fe|D>)UHFWa%o$Ig(^WA^*Q zF$L)a$AbFw?)L8f!&`EM+|myMH+HYyy|%ma%GKRhURD>;kGWnkYWDR1o@f0EH?JP) zxW{2<3XUu}RWu(y-MeNyW3j0L45PZ&GC@{+d=)2ei}zC{ZIFcnv1Spdv^E1kNw$wOI+q3 z(E@M!3an=|iRl)=(~;*PS0q!|TBjOI3+NCMBps8m$PnqIUf`xO@xdwdlk0%s4F+^A zGI+OzO=VpC63<29ctvKj8T&E zOac#g@A-yz?LPM#pO12_hjZbyce=1GD_P3&lvLQ%ff8Jp+w=$?A+Bva-=+tou#vBf z9q>U3hb%KMw6i`FV?TOS7&1j?qioACk4~7K5E-0RmZId13U;GxGLr@kw_)P8@R1t? zBQa1Tfl#4CQJI0PM1fO&&Twp`ZdkXJmfmcOY)n>-xj<*K?KUCjPer@xAJk6<$L6Ck zEXsgNr3cIswhC`^ld4mZM5&^5re&ueUvBE&miqdLz3NJvH~Ya=531uqmWNt(?A*8{ zO@Hv-L1fA3|9EJ`UT<@D!jJ(aW!NE9H|p$hWye*&IyjtHB4)lna9;ImgK=Ozs8`1EFIECI1zBQc`-&J6$)#e07k}*AP40r0=-a=ti+uUlAV-*t2YmZFy$7itXEw zWwuyll`s%1@a*(7b!dl}{W;=nQ5T}1?{wM3RqnnF?2GyH%kH!-blBPUSK3Z2(cS;D zo}!NlJEaU{off?BliJZ6x=WepvgqH+Ap~V9A7|H6!C5@l!{GQe+^ewc%gm$dSmlKj z+e9b*^H@NSfhrH0k^uqs5oCl}P(UL;5=Dw|!76J0VN>8n1~bT1yo;>|XEN))SmZBX zDQs&#+4$Wb{v&$G)XJ+p=M`T^I}H2%;gI5>a}1L3((^nIIUCp)U)t;nZI0)jjl(34 z6Xw9I{)rPsQZyGLD-Yqwaju3>^6BT57Cz&w>9+6#2UkDHcNylVOpPdy*F3;yTqS+p zFAY+`+s}R6I+I598rb-qhxaR2SgELayk6tX1AaZ|GnN?FJ*I3`aI!9gjl*NH;>u%t z({PQ^^)C)AUK>o>E91n;Y9nm#*XY}6h|RH!59~=Edu}&xH_ck1r!HLvC&D}qn@j`U z*14Xf?x|axd|Ab&R?v0nL49?$Wx1DO8{3F>(R^&1$L{oFV1A~uI<$=Bp0WyyenQ`1 zW`{ZtXwr+-ROEHk=u#5lYAf{*bQxb2G%rqHs3||eY4LEGu?zqTrb86;lI1p6-mm`o z>Y~0PmH5Pt_rx-PcoBX>&MNk{V}o~e8huk~?Cs;@uUunu_aE4O z=BIz4gkDCEYJoS^fyKU8H$7R=tc3NPDg~1CNsx(E?@~y|ccLV2pky2lWgpX^sh>=o z8c^0ub;_r20&1OBdXhE;2$F4$f1CP2YJFV}-((y(WEdV z;dz?QtAhtwcRWBO=n_XmBldcivlE64D7o3<#?rqs>qh-V=M`m%eLAo5>L9NR_TlC3 z`~uC&7q5QN+>5iJ=5oiCM;14X3B;9lQYXez(cb&@IIsNbptNr1RW@!%f^O?XdtW!A z+~j@fAN}))UC!(3tG=POZ3j)+t<5*!O6dC)OCx+8-#e_aZ zMyuku>A7eX`|z%ApK;hxCie9|j2x*{U!%>}^#o1m*vWbQS?zPt*9gD#fBfX`h3~4p zu(&pr?$*fliW1n!W5P0%t#a&as;cWfXZ^^D0m}PPU<2mDSm~oGg=cgQdsxJl9qm@ zP#;J5++Y8@Mqi%Zcnhq~tE{@xL$rI$NoDW%hhubmonw#0eV#sIQOD(dW#N24P{Hz$%{ZhyVrY?}`T0Ih z@biT@uXN9I|5XjyTN|c_5AVf!#cPALiN05)*cLcWG#Z#*9VWSzKR{}B(a^^9@ys4A=w%|4KZ!XAJ$PM6Z*8MM!u;O z%O18##mdB zYx+lL>Ji(M;?FT{qB6-vUt$SUU86o_hhBsu%`=v{P>HhL9~6yP{g1l&>xQWVWL^v2Q~;Fbg0aI+~vMl*y{C#H`Rf4gBx;p zNSf9CYSbo>k=?_taME-M0^KyhW2@5xTDXF*(V+$__Ki<>4Q5?tnG+Y-%qKPSN|0_F zK<&Iox6;at*XQsMbLa*U*tMSU#Jo;kowiVWTe<||6yOO0;W~WrteEsSf8&RD|NNJJ zq4Wq#&e5`^c@kRDO3!*^(xQkC;AU2MCb32;Hr{|xZt1Dx@@@Bbol=PL-#8LV|FSO8aH@ko9T_4;1kH7xOF5)tNiY@TIKk<8ZS8waa zn^%Ka5k$=7L4)dWn!NrW^6*MXc1;*OPa+thdy@boITG%t^dQRNrpK=h`d4ON8N`V- zzcdKmlEsd5$iFn};o19M)vJT|lm|XGZTEFR<3KTkWyTW1;P8Lq&foaJJFDJQqUuu7 z?T>vRFy+PNO;PVD_39VvljDOtiD$q4!w8w7@b_QRX2E4+KYQB7LV_=vs_n>6A*70^ zfP;2R^w@LEJG@>>*lMs>6vjDcfRq<4IAYc6XEu!brl59^8=uqlcRjb{PELN{Km6At zazk>ma?gI-heCNWiSgK>5{XYZR~Pfc!e)z^jeKP~ulVinf0rL`boZ#ZY=I#9h~g%7 zV!shU8a5K889-ztGEiyv8-cT0tdZ$We2t3>wVfkO{Gg>n_Qlde`?I_%1^oK%ec$`t zyU+gc-?7-s(;H`j_kQI2vf{(93*sCp2Z@VJd*vccV_h_K`>%YG>4vBG)cxE9t%OI@ zSFXQ1E-m%j*IxY<(NzDXKhqyM+^4ts&;@st&ip*kH;u-Lc3$-t2j#@G(~4^;&MO_vWfgO+o7Yvw7jm(E(~+)2 zJaJxS70`C|70;LONGGm`T6LAz2l?S9tKZsjh;3}2vH_d7cOECi2S4%aR&zNF1-uW@3f4Qfgib%JTRPteD}D9O+7vaw&& z)P2bnR$(EJHq_jh5y2Qte>l`i5a{94vuN5`d~S!V!itRU1LzyF#Nu%xa_GO*GLA0f zq|TNh5uJBA&cqJxUfz7YHet6Mtz=d|*@j^Wf%V#8IkC)7on88s-CZSgo0Z>auva_B zsD=fG^2Ja5IrT;Di}k6$xGm-~cP%zG7CI}bdC2ol%2F1EQlx%NQTrRQ-gLmu{23AvExDa1*`^O z2D>=PW6+B2BtovIrIbloJz?b{$b`^GCdc3Ok#F98>gRuM=om|{8Y5%s*hAd10SmdJ zqe0VCL(1!XxRWhi6)k8_AfYeb$r_v$ghO~mTi(DS+~P9LwkYf5jD=ZlOQO^cSq3|4 zQqxe(awEG;zi>w)O*DftlQG5+V3H(tKr#gs&*WH9q)*0dDUazpC=fD~pw^LkT=pOw zbjY!l#L#`z<&{`2ukIASK}zjZ zR)tWO6G4nV;wDxmYdGNA9P0CpX{xx1d5cmVU2Oo*$`XkK_#U)j%6pEYI>uaesyVa~Pt0Rsi+tE4@0X zeXnpVc;j}OLJoxNeU;Y-5BI*p7V{Xkv0boDo{_%!TYvBF=RWnni}ooEv^%C*0)4_Z z1ZI5oCZ&7j-@1`I_RoG+I0oy!hW_I|q%+TA;>{tj%7dp@Vexf)mqS@WI%dV)3-A5? zySuOa(vW4DmMxINt6%+DE|3 zS6HzKvXwVco`3nGp*(Xblh}PiPAs~{2r0{1lG@OnuZ^;e1EnJ0rzGOULJp$Ki3Q#| zS()_m4}R>=>^}3!KWDL*r}J9?tG_AX8eUsE&{F!E4)_ofcy}T?Vg)5envT?P#cO&2 z7yKmbO8Lg5Q;V+4EaMQuASA5kWG8JVCX?y3*2*+>!ZO~tPa`+`VTq52d<~|&B1kNL zAEfKB#G;X`wOp4rCbgd2I+kor1|R?Ck7`c3vir%O{nX%%jyboyZ7$__lIe<}f2B>! zggS7k_d$LEz>}=%qLlp^i#+3eD*V3eC}<&_gd}psh~bC^pQoxl9Vs9bIlDkXnv01T zl1bxQdd6)|3fRh?+!61*MK=a`%7VMgn-HL@{NztO53wV$Aw3RVNJQZ4oHg0kVxd#A zv6e|jiN4v%1=j$>LAR6kzWUrleG$ko^>b{RmQ_-~b~pt#`x5jsK*;}uq6gNW=THL) zI5=li5EoFIlOOGSQ}_S=*M2HYm*=O-0=)L)4chg}VDH1^UkBT9)fX-`XjWaRVcqaV zGrd;o{+Cbs{PGIT%biz*I@i4Qr%>_x3%8-?EYZT!HFpxkrQPB^99?qe@3 z+78JH|mdv6Oc|(CV=LbyyYvzsh4n_p9E6RQ-F$4<#xaeJN81A8jzd={aFjWCxFV zAiUfcOLn+mcvBo$>*_1?RU7YxL&zkc%fM4KiYE1d7LB8WQ|Kqx0l^y#NDXpci#mj4 zfNam=5aFcFplvw~@1YE3tOACCn?ELXPvV-$@tX5#R0dRT%oKS5G6-`p!1V8lA(IdG z02V>%zU$JGGQ@Hu%abB9MFR|4{eR+Hev_P7KVx;nE&Z0J05-Epg|6Y9t1@TSoSikN zCdtlIAh6_h=6zUXKRD$m3p*zMC zj!=h&Uf}-*n$yrOjA8|iL}%(`U82wmSxFFmLg)gw6axy_nURQ`CKoO}xH*J2j*F(W zRo~^{7oc7lg8(kD?6adaz`983xXUqc7i7WbPAAa{Ic7OBpmrkr$^XWKyqjp{a^DBdDKld-iv>W}6SVuN!SDG*) zaO!ejeKqj)O#dVlZ8DF3rk9-^P~~y)REV)2oGQ5M)pWJDrXBk)mDh~z+Yr< z**AE8gh}Xl;aietmbuU#Bu^K8K>k<(`?h!-!Y@>x3lX2F-S*0VBH8YN_8MEm)tie$ ztLN+@@Yt7q@}r0JTvR1#)A6hZjq-U=S7Pjo#r(0`@mezT$LD_dM@#Z$^m;9jR|j#H z&;UGm^Drr6bDdX|@#t0&ykkmT)5r3__QPoHJw#;D_r~~F`EK+*$`iA65b?l|G{Vhi zzR|E(@BF;u=q#-7VW3KyIF6`-`9Z$%%%8$@PvnR6rhQcB)gP3cE^xOQU2MzkAaga&h?xd7XeylgTKw=wj(_p8m?ugQtP>MQcT9~3puAG|K;*XD`! z8sYr%VAUP_mF+LwCFqm%`N=^96$q`vA4d4*81VP)OA&++}W z?GAlHapw81UL&Lp-44E2Qd)02#Mvln4?bFuad~}rQ;ylYy}Hm!MfSbB`EqVHbcdey zho5#xzGL@|(m(3;q$?Y!KTs7MZ>FbwmqTPDL?>mfTB{f}1e5;aBw!QEkwZ(J=+KE3 zDk!xhmTno<@7UHs-zaoZ6c^1?u0*Iup_k-YHjyZ-mtxud#gG5s?$ba0{g!-rI@fy`v}Fn0+--8m+fjPsxHOmS^y9{0>O4UKDf#XSCaj#Ork%beBfonTjKn7M{P#hb}g|ADX9FN67N;dNM z4+d;cARqshkNVx>PyNErMfXy!+pw~!Md=SA@R?0&3tKrwJG18OtS2b3cOKbDOFpTU zcj7{M)VZ|?gT8}#4G;m7l@OCOmb6_23}3LOBw4({=?vXat}JHTLK-TFOi~kepknl7 z*_x%TnZzx#z@x|(Wwfz6dgI zT2vmQaL`_1#g#4!?7!(>jASR`D_W72{!BlO-RRXbY;z5VVx z;l&Dns6FD=;C{CjFZ+zw2~CK>AmEy!6wi^Q{R!G)^t_=xcV5-LSkJI$nXW2jA($;D(b%a&XeXf3cNU z$DCI~Q{9X41xPs)7sr({8XvR^F@uQb4Pxw=bl`!-cAUk-_<^<#p#GB#996u+mA$X# zR|hQ%OXwcZUmhIJt2m^1KC!K^lj?6965GL$ICg!Y6*O6YgUlB{@?E=6|NM`N?&%7y z&w)y`33fXYTz%@EdtyJYVP&_Itj_bG>YJlX1%BZ(PZa?jJgj`<@CRX3cF@GMqhyW^ z{%PljJ3Fo*Qh4U=|Kje>m;SMwjbEaqG0%EMRa#tBlBIF$TB%I8q2!95Ll9BlVB8>^FhQ5py?2uTU35f^6qB!w7A&LUcEBJmu4RM0uEWrDl8XW3|J7E-}0dk?|$Z&e}SB>DvgQ) zNCJF>P_zxmQ@%~b>TJvHJS9vSq-4tl83CMt+q%f=Y2Jb@9})t6!oh6KqfEgUND&Uy$e)<7 zj4ohO4G0N4vB2qaSWYW`@fg3l>Ec1ZLSI&2UC~Voy7g+TtLp1n5^WJWS*YR!a@^8& zAVrJO2RALZWh?U8zCq7(m(%tEqwADQy4=*gt^MZ3th(Y=y(?p{D{i#ZKpf0>iv6WN zdcocUZukAuwWq}|!agI^KNt zn?>-z`vyVi`x850s4F==YWwDyI~rg-dMo8PxJPkbNyzS%ugq2B`T8Kx1oknaEw1vq zm{#0yWIr-i#BqOyW1!lnV(!@t<67&7c@GYy6ALNc91ZJL_w&`k?0dyWPR7RVs%ty1 zym`Cs2e+%Quxt)&!7(>)j}2dV=QsHkFluK#Nj#OI>vEQI)T{pJCmeFjWX_Tdn_R7+ zwqOMo@dLMyQ0`BXUwr;i*sl@lnSr)bd)kSm7#=ioutz9ui-Y;*?H9EQ>(g>x{f5vK zi1X?@(-g|E3X6V#0|_L3*6l7Bn$Oi$(mU)a#(gKHnofM=L?SvQAd9?W^ObB9qZh`=u<~l-#L|dErPD$?vGgo*Q?HQQbhb^>^(B^)gMON`Twe6_S@c(TZ`)n&i*=fz zgL~o*EOl{rAo@(gOf@8NcYnHD1tmJYK#gp8lXe{VN!X@3E$$(3aT%#W*JE48K6IaU z;FVZ$4euDN(>gWGgIC2_<8_1!HfS|yvImw15uRlop7^sg#N3&b7Kmw3U&QVk{atPL%B$X{v*ap<6qY4ud|jm+VYJ zT4ni{H~-qf*Vyz)TWz+@uOH|P$yL_t>H^Fq;+pz4$Nj6iF!Jg1Q7=R#E0^Glzx^NLQY zG&^`cxVzr9EN%D1HGYHwzoyhZqU=6sKaWXzLNQLKqVbG%jQ+f4Y`N#9f12oCPraX} zdYoIAWo|w<&&3J3SL(pSj_*rsYn^;uo!3d8e|qlsg9?nbFBY#5GS6Jm4>0Vx=T{2< z+K;yA%lHBo@Jb&)S72}*J#IH$f{gFbbf&?Qcf7f8S_e+koTg06+Naqs5Bz!LwH(PE z_YS$IF{X-h-;=moZ~Lga-F-Q-*p`F)Px_}2z7gf_hjd`mc_p~;5AJX8xx3`k;%A=! zHlfQ#ieC9DUsJBU;;g=k^D6sZ>AK46oEj^34$wZdmD@-yA-GQBtUus|>$vZDT@JJ* zYup2JA6Q54eMP+==-Sf&dVfB^OaV^nJiKj7GcNA0I zI*{Dx>)sHM>Xv7;>I>rc^BFAIn7OHA?wKXGB@>gI{6#DJtisB@)D_u3JhUJZu%Dcm zT7`Ar&MOqXBInh2kl5Y%@{c?J{JXwu_m$uHG1GewEcyug=_Nkoq*7`h8ism6>9L~H zYRnK*wx0m}43+ZDf)pXGRYX{n$Pt&cChv!e@P*O~Qg zW)Dl%3$xf+RytBg?meaK2S5J(yU+ae4@$;mIA(z-@4!mC&_oJ3A1dbVP|Y5)Ntw>j zJQLoK#HY*O(w?3>sf3fcqJ7T$RQu4G-{Vqtg*08qV4c>fVFhuXfw(>=w;uMQt4iRr z50<>;dY2h*I(Z(hZDFcST7zhF80#scL{{mPCy3=sRwlP?Jaizj$o=5W=XS5XaBKIu zm+;8%hpyFxSBay4G)1?R@kQjih;{NPRfH4PvV#ELvLc&NN^x@VFg;pc0j4n5EdKKxUCVr@cy2X^_dLC9)g0^p)iAH(sh6W(eH1XF&IHKqg&||5}5xHXyc^m~C$QJ+@1E_E=uhOR$2~7|5 zCbXD6=FaZZHqRMx&lzWsy?N+Itk;{T@L45ygPle9`0(80jNsxq0ZZI7etasqPWnMh zXs$;O?&?czTEPH0PV~!H3Wqh%op{ej{-o{$o|OS}?f0C}-22yf+DkCT=5~G(oSPV$ z?I3h}9v~I~jL_|e@Q(7FJ&(MWgHED{#i10e;aa@Q^O51Y zD^h%sFAY}V6`>Q!(#6my#1|mf4cp_m>UQB_9$qXD{o=#|LRP`&@v(bZS7MLYKHeCeMkjlD29aBaTLDt-l2i!Qs}JKpm< zcVGOqzYpN43E4Xe-K7%uoBFfL0qp7N-eA~pLe<+c2(YEqw>J095@xd?k1~|;KF7L& z4ICS#U9R{wLRPy;5_A|39=DwmT9t>>3VYvv`?p(@m#ZG^)V^c>S>kt6GJUo0$fJUmec)_?lq@7P^e z1GzDe>OLUT>#!|nA3F1UT*|JHrt27Nr+eaP$bgH2 zOBhzLC46xP8@qI>CojK7s9zQz>OtRo1Zfas>l$y0NzVg>C#I5!yi72}Oe}&hDC_W2 z(@Gv_HEb)L}mZd&x{APt>tn&i+=p$I2UxIDcTVarOkxx>26 zh1ch18f8z5(*zwz4pPxlE+*?RNeedtmK<6bx(4CIw6?vex@>T?3Zc(knD{Lwd1I}p z=tF9rc5IFEq(JZxDQy{xY~{P1Q+6&K(SQ~y+OWF}ix8$zO#x(ta6vA;b@(Pmzr3|0 zi+!+wcYm{^3q?8hLhJ-^Euo3hYp^pZC~|K6@i^64&!Kba1_wtaFE@2RZo?-5egF4; z&+e*L?Co#V?)|NpI0v1M&mFEc_JIBsxqzwst!dy$3l`zdec*v#*2oM43+`;t}e3p85Wus_rW~ z5!VIZ*G|gkV#2TLIIM_u@QRMVj;IfeJba+Nx74REUnxAW>ZyRM+G|FZtoeXZ=ew!N z!#t52roc$|+~F%bjYSZA?>;&3^PZ6-83zGabn=|wY+whZM*)({{kU{XpDcZ=xb=aT z*ZFX|yedaloH8m02MtdHwgC=+i#xAa;r8%B?e#cphq9%MWcJFd-uH^tS6*#{Gr`Y% zYYblw&*P>^+X1(os+`;_PT08b)3XXcUiJp=y$33I?(OE@S8+n&yc(;o+)h}NcHoFz zh@B>{zgy3Iz&rvvM5U^81D}$Z*E*kj=f4!Qo~Bce1@)=*`HX(yd*bSv{aluf+!5II zhJfxA{=U~HkL~G?aV{!W`aTZ_Rc6{oa%vS;ue?Iu%lh);EJMzpoA2J;f9(rug>L(j zBX;`Y!zX?8kD?= zw5m%Mb5~<^?coW<3K`l-Df2Xql@z_?SY7booKdj9~5V$OBvdDl#5Igg|;{h zD)JVOvPf~>ES#dCqLvfM@M4+#DLwk+Od~JUq3vvVp$mM;!gx*sASF zU4*>c)V(eD#>embW50X6GT5uGc#w3jQXBdgzthy9F&|4fMbIgz$5QkvvANB%X7{M=vvyT?UeLZ=0K z<&~XSdeBs$?YQ>OKnGaz+z$wpHU_?Q5NG#b=3Qu};JwsdIRhJc=;ShwmNAHrBv4ko zQabf2U8=a%i3Rg5IkH~)@=vJBQ1c+K15O^g@4tLR{>h+?^J#SGf%5 z6;2usH2^ljJ{8+O;wBER=%`}_*o@ctxp)4y-B-Tw4-4~jMARJ%%s_f+tBFtw9QD`1 z;sx6R33=HWTgG`+?ISS)VKRr}E)SKRKb#G?Hv zdj#52tF6+e!qWFW21LQbg#I5qcvWSw_gxgr9(Uj*i6TlKa$&KT2&m|Vj?Tp7jVE2= z9n){ctvakuEcXvPv-X@=2wegq{W>9E#*|HN@S~{%gcP7deqmlyWrQLZ>!ZJ`B${X% z`m5LUO5w-;%iU)_`R6U=@+3#+lQcpaV=b|IO*NA-v}=*<(>HvTKsxY~u=~`<^5_Bo z#bu-hU5_dI5Tbr*^m-OR*-5(@!h^o?bMn~AXs*UnjE8~;J+)#Gs%{#Lt4-l!;>uoF zik0?c#^5Yk=$S+|ho$uNKxw6+qD&l$k;Q=IU1F!nLm3@x?IUuL_TtUwcfa_`OZH5^ zM_)84>7WvpW?GVsJu^m!HLN-6n|#hK-#ML<&$;QGE5g8z(KW0oVl}f3P?tSvv1f;w zB-N90uz@tC_$RS}JwcP%pp=R8DbR^SKFG)%nh6%!QQ9)V7yN11V)*l z>#)c~R5Me@QXt~G_oM~suk>@``#4GUiwOneknJ0o?0ujMgkb#(9Gd&!RN=5s1j3nq z>kuU>vqZp~M#*H;lK^+#ZDzKH5C^a47>i(2_docqkL~`aUuSQL%ka1?z$=4SpW&53 zz0#l?fw4S2P4H;NoXUGD^3lp`t~XHhEaDu6oP(t0J(iexfii1;nDYd%rG#ZH)^rs7 z7|2rwd_Bil!YXGGIg*R)0edqOXX$%x-OeLuf0jJA@I%5k%8uu$#-TQE07QyZfAckx zv^pdALV1l)cwMjP!w)t2KfsZ7M=J|-MRUIv8b{Wp6YJ!r;q}35*WRY_8;8eyQaq7 z?dQHtdkSe)i|%`LQonn=&MWF*2ZO&js8w)UVUj(s^hnuGEVWBJsjzPxTEx@^J0T^1 zYlF#CVZYHO1`4&x8CQk6vhEcyU_RhtOR30{ER4^s9@YMJO}K*{s)#i ziiSVSkuFrjc~k?a;CVsmj&Z)OPOOL2k8xG-u@YcC1Xw8hiHbRGvql~Q&<8x@+p zbLc169{Lx4;~YoyesV){TBdE(opeJ@uE|9IAR-Kr%L&`aIY);>38@X3A!F&^9LVA}% z(V3_6mR5Z*GqGsP0OMk%yy=h$9|Tgq)2{1~9l`M4S6{uVNU-rcT2&&N^8W%C-c?p$T*vgj+T)gg^+2}p+%I9G_x^kT>h5#@-H#oU z{+5T~G{JJdXW+hJ3G~#>y&ULjgF&Wt=^tuy@ivjrEsKWhG+g_6V8=qsS#gsWy*!c( zn(&qScv^CO|Il?KFG`8vA>TjX@!$(gW_jlM@6fAR|44W_C?3noD{Zu`SG}Iq7vmm0 zysJy;vpR06JY8G0?-7nG9_r&zL=SFca%A4_`J37&ZFp4!-c*SCQHjt|A?Vtna?N`z zY5yz5c3vsA9NB?!Gxol!uMXyi%Q$F;lg2lBW`Id$(GEC1pyQRuJag>ZEw_!(?sm_< z@LP8;za;xUH6ePrD(MtcGfI3-y4etd(PJgY0Gl9+W-+}6N;Z_%{()P zhm~76YuJJ`1Zhb}5S=d`W+i24g<bf4DwB5GV^EoC3ZI3i@2 zy5DZ|E3+~4^sBFZb;suDJRCDHEyE+UFP>sndoX#~*`>kQt5k4eG0h+z3uU0IZA3p0 z!G$z_=usS%M}Ir6mv5oUG3K+$c3{1xUpM5$%2IOrc=bi>z~5Ah!t1D%vOp+ugag39 zgb=&}JgHTmTaH?Qy1SkwOnK_+Ym-3ClmGU6KC$~BerLDaPOLFMiRnH6?!U78wZHNAj){Lu!}6+{4W|)KybZv(#*YBWl1(Nw;Rjhn zZWK5hMw+mn5A=NJ$%|etbArw%qV$_}lfI=gPJ!vfl3?v!_3Yb!tG?*=f1Xoz$d11@ zI7Rr?Ejf6uUS;JKuMP4Tsdy(xk`6l=JfEsJD~@fG#-T;uwsQ*_(@}x-psdQ9HuS?O zqE*zr`pWyBa!uF&a9-)Y@V@T|Xhdx|1BF(73OldyO3^_)qhTS}m+E zYhJzfT)5H6xKX-@m9|q&k&s|q7IeEP|_q21-!eJs3u+Fz>Z-SqBE1}vS*U*5x|vRxQ93b-KTXvKo6LfQ=ozY zokU3mvZYH$KGb3h`QVM`{ILFjn5^1=O|{T&L1*M*g_UT^ca6$UQ@-&<6pHQ@s3Sz_)N-bZNdcd*riu*12Pl&? zC3kBrj7t(N#5DL;`ASRZKq;|mG4-I7^tYxMbnqa=K{OVoeD%+OONW333ccBMwQx=u z-+0)0#q~n34{9HVEBA9_gjg=b{(SOSfz>xNqV9{d2(5z7ZoSHzzhG(A*e{w`%P?*L z1>cJxnWL}Et6CWu3LtFi&IORM&=+bzV6qw8;tJ^GZEG9X2U5=bBgDO?@*z0<|U(#(>@?>y@m;N|(Ii zl~~C6+~4@I^OyZ~PuVuholV^#Y-$Z`b9d(JYuj9;b)O4O_^Yunwx3WD*RU47pKtgY zqZ@Sl;Rx@z*WRZ+AzyWvbyHbY+{TFo`_=2a=f2@zKDQI=Eaw#)#m_yj*qB<6kbY&5 zhwOQv2a|Ni%H$8->xjJrH?J!}dr_Hi_$;~Y5V7Nml~=WaJI(;hky-h%o0ax{b@fm+=S6*>SPS6X%*IYE~~I+U+WcI%Z18J zsdMocgy{agFR#EyOk3GKUSLyy`eZ{wtvrqr*`i|;Fh9|Lw!K7|s zK3!z+g;%Sr*b7Tv1@H2(K}H*WhdP21VCl#};WMt3qNFk)kW799 zOJ){&B-JGM)!VOW(6MBaG1|k7l!uE)gK}c#Q=lbvMd-pg(z9?#6SZ2Xi@-DDE|5n8r$MHC8X5;B1&Jz<3`3BX&{ zycE0P=;|EHHQ#N(i#g}oVvG#}F{qf!3`aGYvckzLpBtw?cj};Z+qL`U%4M!SJqj_+8B? z{>xViVy! zbYAJTLDAxT$O^2yK1h3T&l0BY=IR22;djw9QDZg1dJ8?cp_1(W=dQw(utIl8XZmX+C+}LrXc>isuHzN(Xr9 zmz2uC>!vYhR-Q@}I4w>ra0zoyEM#5NYlSy&);NGbdRqsb_4MNB@m>W+x$s19sB++3 z{NTs5FV-i2C`G)5&QHvN6`Q7MVtCG-Z^)tLLow+NDb9kVTb*3OasG6;+3i-Ueds>z zz$>xh8s0Hjr*&#rK{olOS#>y;ZW+PJN6R4AsAF{pICp#!i#a>X1m;%=HAsdNi@~HL zA?co+93u*}gdis&DazVRijp4>e;i}*!RlBim7!IU!OzBCwQtce?wM{C#+KoXR6bPrJe#!vNbtq&rWn}hjJ~-br-lPx+v2W;iL(_ zYgvRXtNB`4;1d?A4XhYxKSIu>MKMl(lcb4~G<}IMg!Xk1uw`|sQPeW&<;>MWA^nfO zoC^e+aIsX!_J!rjRVMZHA2aA=2bP>y^kbY@?$2CwaHN(+5z)D%m%K3BKz?dFa3_HG zjQ}S#qjf8X!bYlB*Lu3u?~^GY|n zo@}@WI!EZSbq><64H7!l*J5A-@xiztO*Sy10DP|6Wv=%i5`+`&Bw7;#7ux0i6%iF>y`tbjL?DKLR?n4l;q~c`h)RL(wY2Am$^ZxxVoL zC4lI!KGavb^g#YlN5W*jbYhiBFlr`&2Amkrt8bH3gLKwy1Z+QGcgop^lczP0A{Jus z(CvrtW_~IeC+e`zTY2g(o>@2Lt2p{m-wXQ|THWf}_2LB}GEk9cN z2Y7y@^y`E9@v^_JhmGuHQp__+>W4#uu>}rz(hv1Kk+e__WmBcMKXRaF!ZX?v%zKU5 z_ziW}fnKgto4dD{U652) zdl!0qnChOoR<~Wd@q*~YCH=!oA0Ueq`dg!ww^J(WSnha@FHR&FO@salp1VcUI+53- z$Hs|e!sV0|SU9od0KR!sil9UEAbk%zt3UJnLjUFUMatzV0S6iZ5-TqHx8lB7Pt<{h zv1#BnxB*r`r2{_+dxYTtl&(}we1JU(5dHSG4D~VSNI-bjnt{MLdrTCF8c-Kw~gqm^|~l}(fnT4!~l^rM0(I^5L#iB8i) z+popM`~LXv*9azr-jZb_Wx}|vob8kxVhlL(OW}T-+C+y7B zX#&q=ew+jsS7gJXOs;{l0{22v4SS4RCGa}@?5ruuP6sl)@(fP%*u&b?eKJ$Ohj00n zSnvJF_vux(+7s0SJv|L8uj3eN9Sxg?C<@w0@bGtQHBPZ7g_JX8?$J%el5$~7zLVkI z=)BaWsZNcV`^1CJ>0zQaEcF-9sjenC@tLo__Y5c6VR? zg;DeszBYKRH*DWud8G$%oKt?N=E0gYb(U63>@;~Dv2kdin(#+gboy%{*K4fd;p zig9T5yuUiH##g87!Jqp+>=N5hg7Sjh`O-genz5ASGJ)T?`Cfr^Ux`W%N?5U}--1*! zglug8@=O1Cu#m|8V?Y`7l~$BGR^B2;ffGny7-6<=R>^@7&xTF(u&l8C{E!E&tm1>D z(z;LP8PL~Ap|P(I*?R7UB{-}lhuF`MLl#K%f;WAlS7Jr3>VbY{B*BqYSPvi6Ytw=p z3pf|i$GzSB>T4=D^+nDSj3=L#$KLnvy`;Q$X}iIK2hp3C{voWCib}+CQYn+Uz#@`1 zL6?bB2`4eMp1g}MKSVGF=vRL2Qd^nKfDi>o`e9zrI5=Xj<54oE3rtUtAffy zOWP6MO(RO(G0b_}gC>UmVlvz@(mf6e3T_Kt z=?7kkk*0$!EJ;roV~5a?rX}xm144H6yd{~rncD4s?$wue-zvw>;VLYsrj0U@sccQ| zy#mXdxr?j^(wNXZG1+rq9Lq>IH80VIE)&}*Lp2Yo1PpZaBGX)@Ay;P7v=B;Hq$Mu=*2^weDOj~axR+O?70K9 zR;+*(Sy0kTa!3SbOD%T#VbE~s9GB^@p|b&n0tI=}CGzk7G}S#2zPeQ)K}a9+jY zXURD?{xOR?ce!sBLSGM5*}g8g9d+|o@w};K%y4Lgdd$`suQ@=2Rsk4fv(b|Dh%?;R zAByt`g+~FV#~cnM9stfN@WKpd2|OQuDRxPo!FztnGX$NEKBJh^(WhfiFZgtgk-XPZ!xOwi!Z{==B-`2wq8bc=#Ht7lcPm$2zSy7y`fb0uP87gF~Th zbzp*RlPRo2lY5A{Lrp) zV>?RA;W-z53ODe;il$@ZpyL_Cj+6QN;Mn&H2d~G8+EexBhsv?{Rjt6XO5oq(CGJjrFzjs?HOZIziU~8zc?=2$nJP zx1RZ?feP8gHTT^+zc^`1)-%ul3xXl6lo}M|R~dVF+W}iFM{&@0a|IUdB01xJRdW2_ z?QrsIg!C7-o;h5FMIV(A^~(plSCzi9d-cvQ49(XBuMzGvCo(@~{-Z_PlBEmkHIqQ} zIfCgju=Q2LFM^OG>zy@?=2Ms=lwa@@i8pnN%G(8QZ~QZm7HSdsxv zvOU?&aeCbSs;O4_)I0f{yC~;$O1^23P#1?HgsyoywS`%di{8Kuo^nYQ$h4f^dE=S% zmBO{8m5%f{jEEe=y05`602uc@c)*!AIIbtbC&vB70ZnX_dM6JJwI-L)@Xi_LVV3&VM(8v@^K+1VlEDSpd`H_Zf( zRxn*(jhO2L?Ws1*uetnU+YdH@)3%ID#=2J$bE8R`K}4F7&BU+n!X zSd!U*R(U5PR-(t1-YcxKd(g*K*dj+IoKeUb=tb5UNejA|w2M)z{0+keB#W<9vCn;Y%!>uhkpb22TDHQ!Qto zApAq;qh;*#?B(5oQfJt)0CQ(nYpDOV6HE8U1e{o`cwvL=SWEO{evAa@o_YJXYtH4> zLB2Ss>nr?rUg3D)A)bSJ=};UP`MUmsGaOvf$Q?Yubf7sghyUP_Y*t_Oo>zQvkbSQ_ zKMS9GIE|KN?<+pu(?7u5iDjzX@ln4!a$W^qu);Iu`n5HjSkJuuw@8=c!Ou}fO*>)3 zXP^Hqc7ScUsPk^OaY~thx^}2^MX%oZIU$bVi`;hHu!c7Mrx9t@mNIcGL{^tg+K#r1 z<|*bI_XW3sO5?h#^l)TJx757rAA0z5v<}^dxmG`TK%c?x3%}&1uC(jT-78YD4;kq%CNo~{#@D`J;n#0`gX$BjdOxgYgN~||NX$Z? z#5Qx~RTFf~y@u-PEOizANlq-6KKI1p-o~#Jx{11-upxw6LB1CB-(^Py4>)&m&UKxLORiJYQ-ua9gvtj`=(y9K zM2u-%C{TH&?MeWv>J%5zr0jVZ=mFg%TR5ORU+BKq)Kh+HntaY(lyf>I-xnV8132_- zo~DL(!Ys)}H~f9K-=6-aw(%+{VJB5-Spp1iVa}vBqd%!l{pUW69Q}3wb51bvad`=D zo8Fd7L27?R@Kmm}gEDq#b*yQ?PM&sJWhEVI%QB#$Y7-?dH+4VFniJRiUh(>kCKKOu zImU=SChe0+(Tcf1M$%t+x8 zN`?_kzo4s7M&$e9h;qnR**%At>DVdaj$!s-=O*0aOLlON#8I+6;Dv}MZ?T<^`+QUu zeuVT0XzEdIwEzy2rwcei!Xq&ql)aU*%{4I&EcWH$)h7C~u5|9By5v=WkB+FbV0N5{ z?ja6$<}vm6E7x_!awiTc7bRp7Satf@AO1T|zjaS#Lt5MSqOk%hK^a=Wg>jzx?F0N{!dCd>t`huAQ z&*E}mc@>fYlt)l3InO-b&Z{`Cyy{A?dui>8Xy~vxboZV#g}GmWZOIQVK#UVpIY`5A{wGsan!@x_)Bj59l8q(61&Bkj!qW8e`3p}x>0kIb9Td?S&a z8>~D_sPZazGHkbdhc?0g;%EQog%bI@-K#JE`~vGF_3bptC37rYXj*s#s?d4r>pB55 zqT<0gNreGwDr~WT2m>CSXbbN*#(dq~~QJB&z22hQq4 zdW*&ywd%?p3*OKjo>IbdCOnAI3HeTP9`KP6Olf!vdGEE~FyHl??-qyng>tNZVKOL- zrDGh!DOY_*_My*tY`K_nVwsQrrn)ijlhjJ85kw1l2lm9mPP{6+yAj*84Wm(QspTI- z1W}oNOGDXNXhOZHK;jR6{QGyG`RO0*BEIh8C+5I%yDsJ%9RXuMa%ia+lQ9O+k#g4# z1v>c&b*i$Bo7hR;FzOjOO}&?I!hlaaPqJgFmf7sHhLq3O`}#FPem&?x*;X48mB>WI zV5@^0dI_oFBwqMAxEL~MBQFmJlwZ=2r2@7S8#iw(GnIbGL=pJKHRZPaz!{AB>pDT5 zGQRPaUMJLM?wwa+6QEoJTq<_=6L)^yu`YGHFW!4)_v2svwUuo5eeeIyTy{~y451t1P3HXFq1dp_{nzg44HA6~w_hVszhsp= z&&>>}(LQA8a~0b$drj9Stz7U*tb6Xr5S>whw*Z>)>_Ch?_T$Db_i6$8%stDd=X$KS zOvEWI7&N)RS>$tL=O$=a3*OH+d_Qh|_u=6mWjcFanI)_0k@_`EvBX$q?%NH<38niA z?;}PT97L3^y?J=OiaoFPR$lob-S_@Hlp`ziFVc;3ObKWJOzApOK4*AC(=oKrZW?#`SMSwdfao?IHIm29mm4m zJHI5|5<30N^WP?bwI5L^3Rd(DWA)aZFa3WT=<$qnE3<>g?wf$!Ds-Kbo$zhG`8#gz z{kZV%<7w~toL2S$M^I6;N&nE(&wNvH(eW=DWaRk{8Qqq-zS5R?W>k#bBVA`w(9$Qa z?QXyAV_Jpv$$>qV!0GB$SXBlxN{Yx)M*ryD%tj^$rEOUpO#;)syPvln*KfWjRARA- zYy^br&<|qN6FU&R+RAt7`%WzAupMlnh2BoA#fJsV0UgBnZ17h&GPc5tEosW^uds5iP zBtM}}Rd&TS#f%GEfG+bS{7L#Wy!m3SKqqWJ-=%ph7zuMC@_?3rh%5SvE*;WoGj$RMs?X&rU~7!W=NK z=OSRvfwx1+fd<+k-{mUrBC5>hX&pxX(GsIfi{CHf44I{B>Q}U_z8y}bIw(pbs)iVN>~*2aQ%dtljyp>O7U%wliNK~7>=zXzX(Y{!JF|euqP`d|+S z|K!^5Kgmz1Qt1&K1+?mfv*-{w>-E>t|m1!tN8# ze1oP4RzPV`mI+r&LQCEk2TjC&jZg#Rp(dRSOkw%=!Nim!Bb4-7zt%`)Nhu$8UNPV+ zFI{?=IWBoE7hPOli)#IVJ6zd)$g8mUGGPUnMDGYaix-`L;$^M80nsNP$Wf42`DV&Qu;~u#+eFhBkG_ExG6yvLzYfNjmgtDQCys%O+*Z|L_N1R6mjf zR5#r(T$STW4%D$fLASq%kpNHgA!$u_J~Y9&|3Ihg+Ls1=kr_T@>Q5Qbch5z- zV7t*WQMF;WOkV>zy35ygc*Giv=F`~V#@VZ{9AIPtwBCctSaLw&a3NL;dPyU6>7_ULr!z3FXC(PFObKUTtp|i z03M}~BkatX7Y;{QE+pp&e|o|>G3DpS8C5II}@}GM>gQr1S($m|9|%0G-$Uh%j2=H2gm&fa_NHSE3i+QV7T z8RD=?90_n*4&(Iv>Kas;^*E$PwkZXptZ5{JIeC12u$@<$t34mnG{%qbxT?nooz5Lt zc4E2L(HYLGzoRodKdwD8=4>A|4()8H+PwZtKQ-$0)O$Za(j?vxAHw5L|A1q+xtcT9 z2}D{KJc9Yleb-kZ-{%M=EG#o0q`ihyFS1p?1#?cpLzJ?W?4zZhy1^iGROoX(roKo2 z?jyR2G~}s-C7WIIN*uh7aqFzV!dq+ENfdzmL5l#O>6)UCLknC!~q<84pq~xQ4c{=pWuyhC6b=c z4Fk8RCcIzSb3uPbpN*R->QNLi3)oK83YZo`06KV`UJ=etTwe@mB>0(MXq3*x!~kL7k}qB=U)7yKxjmR9b!)s8>xwkLC5EB-R!uX- zA$!dpX`j&huy{JeGVxyF+NT&?TN0jUK7q1hQyxurZg6;ViQgijcE(i|>X0E@@d{ zjtIAzpA=Y|yavif?O0X*&D+?Z|Gh>zt-LReWShRwtFa(s%w1T<*XdmzvhvbjrNCh{ zr5n0rB==TnYmfP0wfciU%@;z_%3p zc`7V?{iltMQ5UtviW)fRN5)H+pVdWky|7(*?!JoiO7_cFU5YY2Fh^9rX`|EQq3**`Lvx0*D@(en7<>h3EVEgTyE#%nm8XWlO zJ)CZKtScmF4PpNah2V==` zk8&jr!AH5xSiA=w$(bT~Ll&ZSWv1L3Wyvk6t`9^lNvT3;NaK^IjJ(r2f8pA<^&;~R z>MWOW@EGaxKmy<%~0_w9lgwFy%9pE)jkTY*H zIpOpPeVOnp`to3jTw^1k3Zj!}WR3LJ=I)nstLJ8F8_lI*ZX%}&Niw-7EJY1z%sF1L zoSIOlWIDLWp8CN1cJF)a3EdUMge|J-t_#1d3V6HxwQj=NeE{HaR;TRTtm+g z-y8KA1igDv52$#V`_+H4aV}o`ekyI^+L6VtlBC_|otZdYob?Q>ma&J%PT#Nd*dA#&8!lXjbPH5mrB?-2n23ApSXf$S0^;g@gdj> zL7C>ag&&|j0NPTm^Hf+w&^eIT!Ce|e#ZO5+F7-hr6?;1zRUBaJAniE$ zc50!Yp1;ARbkI0g^qVyNd#?9<&z$e~UX?@3ywXqkF_gXK^5|eYhJE){?eXk5BJGCC zR+<4=E9yf5gkqPKRRW*erhsJQxeN^IH( z0;ODoRT|xF4)YS8ug%ma{iR;afFq_>k6?v=0qPog|J@G^IBW1A&g_KwL5nA1p;Cqr;ywO#3CQ&i_TqsVB^bm~1koLL_gOU8uCJQq=SrQwGj%o>{mr;3hsVj-yl2_5WnczhNIpnk#=5awu0=kA2^ zze=SFILVA+di2@vBQq_8s@QX%`*FS8{l}F#-{Ea`U|FZqSa%afn=aW`W0#=u(}i$t z-fNme&>4S}D>2eaE$N0YFdK@*G^BCKJKcbFRaUt*%92}BEf=zuq*Ng^r133V8S+k7 z>}Q(=>ASFO`x*wJpLA?Sf=YmCxLZmi!;_A&h0DJWCY&4v2*<9B@CmMm_<3@Kp9VXQ z46erQi5Ctv2rm}0;t8=zkX5V-vSc?!<}<__JE!0+C9mANK9#<+$*NGqnpYSBxa}b|U0R#57k+^(TJ8KChr#W~d6BXn<<@4xzFy(@-3Dgh$-AkHiO zywIBp9z~7wieH}d>oYaOhNmizbi<4TUYE2D0fDv;$|);c!@*hQ!$+3q-zoMlOVQ$x zBuexQO+uG8EOF<811^ve_5h<~mDuv?ynD!*K}VnK>H4cE;yD!BOT%p!ehxdWaf4xOA5}; z6A{e+hr+Vr9V*?2ZTX}&M=;-#3#~D^c)N5PhwCjnVtGvUB2KKfL0{=`;iVcP1VR>b zrJ2k6E-d>HxC^U)x;HYoTjB4LJQt00l62K7V=hQAM9GdD>dWN9S)yLw2gLp0(*VIL zPa!mXikAv3_@_=apDjV^PV2_Vc6-4;Z4j>XW7{e|u`$+5E^hTo0zEo7?!3}_N-yX= zME$-iF5vyuK^zM@;w(Tu^!xExdsXH-7Y_K#(Y-?5!pC>N+Ch>Z4gC0^=lxu~gGcan ziLOWbE3^rfR?@0QpxRk`yi2S2qSzQ5tyFaEd3GJOB@|Mc$q>t8$;=k5y6 z+7|Q`Ewt`@jVAqBKh`5%RDHCG-Jf(z;aU4uFZ9b1JE33oSihZ;n)}$V`3|Ic&J#0? z6FZXw^)%tlJ2(9Je@2LFfCa?%oJ#v*J@BWH6CqI7-}V*Crtr9gM&A#DlxCKDt-t#~Ov>=3sZ_#EN|EQM_!AI`4HGl9#~!yM7EECstM2QH_a!ICISf zov0ZPfT+G=-q}TP)9FX)KkvnQL+{1nrsWJ@hXxo33u{_%*3O(8G|Hylu^Y#MWqc1}AgdWpI@`fjy^rS^fJ<%x+rm<~D zq+WgVrXM@Ktc8ac8()?Uyzk26dD$@=4lg#L{P5)`Y$wBcrAf5!;ym~`b-8fidC z0Uu!xP$eNAXw3@_O|mS?pcRIq?MvOCc(=aPT^Doqn6F^Zeu9M8C(Sj!sHr)p=L@Rfs#p$$%1tuEOJfc`I{DCA z2d|5)d#=lA8uAF|WDQ8k?u_FxPl0kggYI8C^@wnE$wbh68gv$Z6D_vfHax$tulG86 zoJIt!*;LHQS_$AoUUrDBG7ebHj7ZtLIOxG)sRo_tkS?sc(w_mXOqVapVuhtowgFgH z-zotGej|kWLf^tV260Xw$`59n#GbG!-^vtt)1D2Z16lEwZRJt6wA?Rx)?9f>Um@g$ z?LKf`)ty&)WRM?+de842x$@MLJ%8fl#glv;NU#*+fY(Q=7VU%2zy5@WbD$qH>qiIu zHCVy>V;Y{*`7xAvSq@--3~2hH)aB9*f3!7uoCk~-;*>e<9pRgDLhxwV+Z(?9!he0F z!0mgtjzm0EQ0?=Wn9FTKZI>OlrIODv^yf8Hx*GiGD7h}{NPp310*e%C2k-@ej&-yj zBXob81x_q%rw?}J3j=Inv5nK+g@s(4ZP2 z<*Z6W;fWnFCMX0I8+yrYg6_)Ik4-eejkw@DU37@FEecKl2Lm zK9j}?kzb`V2$qRVdGX0#bE`U0A@?=)s6&fTelgJkN##vzspjL`Re=qj;Zcm7=~_cI zhmZ3pq27m8V&|eoPZT}`;jh2;H8~pPp9s|L}p>>6?=^Po*30(F=O6p8q zg|^0qt`~=iKHv#Z$u)8DqE}Mak|dT|)~ibHGiBO|1^C_iQg<#2J$GQ_eYBQQ-lOo~ zYoP3URCMaMbg3-fiC}mXn3ro9a>(q##n~2guyE*#~Ewl z=S@4b;&Df7qh1JaxuRhnd`rXB=C@SNS($J9=B)H@0e)u26BysudLJS2j53U_e)f7P z8ypT<-hp*gu&s*H_D?KCX!RIjzDPI`R((GDTYmNKr9b&srh>sy6SOs4{e9y0}i-@KXRrE<8;;_QsQLU!I@+=d9`7Y ziCE#;J_gbW3FPZwF7pLH)zuuQyX7z1fi*d=ysH!*<(~U`E@U30)y1pRw$H#_=6qLY z!L|IRX&%CDhX(U3`^?p&gZ-lgy`nY`=an5-xywJ-66g&(kn`%bpHaHziXyQrEEgUIVFTckviLIc6B^b7{QI&y07Y(&U&%Mv?@^rEKit9#xP;$Q& zp+yv<(rwcdhey*u&t247zj;Tc>H}qhQV8;uDK>XH_eAc(x~gtP2Apv6*Pc0mupj4^ zXyo9w61Fj@OenG+tFD|^cA7CZuq(x#zj^&@mVf2(k7dtpTA|?xbGu3%gN{mxNB+qi z5mX1MBMv~_6X_=pf|`8_Y1)%EX`rZTVygP)2NLEhX{F{$)Y3DUHaL2u@W=E>;ZKC& zIXa)tLhfUOc-5-jz+~mYHmOPRr<4WVQDIw>`~r2VvR$^6#=uguNMH;ST4v*pd;@}Z z@VrJ@a!V>?$v0;Ut(FGX_~d$SXl$8m!MJEweT4w*wqi~7yX$vu?cUJwf4usY-LHP{ zr!<^!e?ZMdB7B{cf>3D~@Pz{w4drn}FnKB81BOA+6PaNFglAV&-2E>)^VPuhg#|J= zBnDB;JTtFO2d?75&lQ8>RKe}B6y}J?d*wdAy!})ac`J}gd4ES=SJZTG$I}me0SCgz9(&3yTNbRk zfW%(<=)QI=bWFiiId!^Ypy{dL0U!Kbg6&jU*%0`FIpuWvJ8;H>ZFwabN&A8J!==M) zajUd4$Xw$?2i7UW%y1%Ua5$+x8KACNI#J_L+|xj`wFiEu08~eP%R0nZ^w`zffrIM8#akW^7xs?!3AnUX26~eo(o$aIf?ahIH%j-l}q!)c0BP3G7ZN zt~Q(ws~e1C)cYO`u+0Ix!Y9Du)PXWskGK-T1>O5HCVfAC(?o^uEcoxHkSmJJ1l{h^ znX{8V80hI#S@R&M`4l>(@wS;ZH;oBJEN!BP1{UQ@I!Kyt2 z?U+Q`C4{>0zI6F%T@Z8kRXeYAImpY}`GHS3i}MO2)gf8F2)9h7i46lj=cxqk*dD+M zMOQMA!-aVq8`)=)##xd&h(t~Gj|@G(_YW1d$L#&#wb%8YDe1)yEA)e-zyr)zL}tX* zkQwq0B?s#H;D_U!ThzW^EPv{j*nWhKzI=g&b!Dyf%?iNvmtIu=YToJVvN?v0^;u`~ z-5(t{KF+W7wcZP;{zZ1~6oMElf)g?J#$8yyPVd6{p9MWWv;)cYr2=XX)f)2xb;9h- zMrRjtB7j9b213AfZr-h@3)qxwmbQ5)Se}qOmc8M~N^5ji*5l7D@=~c1-+U0HbpA`~ z992CPjdx;6Fpm`y(k8`>#cG0n&a>6y$U+!RBaU9xTGE0k8udJn@5OrC9aw+ph5vi^ zZ+`rTWS(tcZDKK7I^?(zs>2v`#vGDgpiWhGE^CY>uF?Xu%%jR@nTOI%7jp^50-N(K zNiZP#Kxa8X7oA@&bGaQY$?u^HX2{lExN#-0hyw#^iGA7R}|K$dlKRF?gbTMqdzI z99Q{etesaZLeJ@u%?|W!Uj#1pvMh!!6OL`5ENrg3Cs;89h8-s+qZD-gmu#>MFMQzs zE0Ric#}#A2@+-Ci#NoIQYZGw=bk(_4*(ewZWA4pn5QB0kOr%N9=ZVbVMy8L&wD`W; zUh3{U7-XX>ouV2JpL$(6r0};M=4!n4V(z1aw(k4L@~&7dn(G?%+_E^YbUEcsr21HM zz-^^-_Z4?w-O`?oU*8Bk?tbCPi*c& zG9>Sm^zO~OtoD5(J9910Oh85ViFj8}9(iiJOoLcUSZi6kjtq1e#l$*{zwO3j$dBav z5ES@JWKxQ+J$-iss?zFf01a%#l)_q%F4p`LlYh&qk={X)W5bzM z5~_Ygwp+1^6!H})>qAN0R(>azc*co^Ao{29k*W+^8SiFn|6ySa@uDBnREUC)jORc1 z2h^{;7wg|^W)F1ywmY!U28Qkaklv!ilXVn|!+7wTL-GsMsmjh}jj_ZFQsWVC%VTV_ z#KY;PTR6 zc2j*rb-NonriKk4Fw+!VK$}YQA!8cs3LO5jq>y3}$UH+jM~@5WP7~UC8h6g4CFB^U zQ#OV(MoNKdaMtFXFOlfV}1mWd2P_Ru<3WA_##Iz@F;g*z;Dwj zWP82f0*P`qeZVajo|^0K<_@fr^>75n5nxs6QFtpgu(6H9qE3Zm(k&(*t+A7Jb0-$p zP!7a4yvlDQ&2a=Gf058L|Af?h1p4T2{WZIn{^P$q6+XO4KSubOA5{aTfGom}Q6*U` z%*}U3DaGT$m0az2jkBCsPcnNH4E?&Z;8TH(NA-*Ojy_q-X!OL;Po1?coN^X8pQM$C+W76;$_vA21oU&mco zTF3oL6YycDlcRPLo#`$t9Ab=Hcp#9L#Txsp>=Y;^ATii2J+P)$%s=%g>m%QDs(OewA3Re zxa8+lcjOwjKu1c6q~~+Ppe`vBUND2xb%=q@(hjGab}eCzJL5R5Q^SzAmv(vJoHd^t z3%s*9uI5^}AJhx@kQr$3YNKyFZ{)cc&_vUY2^<@K-dNZfEbBM}JsjXkef^6Vb!Nb; z^5d5i@{}XF2cG!Pd|j&=bX~8LV%~-I<$9rew@K`Guj=cAf9v&coR zgUA0%Z99d%bift}wv&#rk+PFmOWJJQ;t7?@oUgPXT*P{Oh@OWHp1$}P>`xicoTUtW zs+tWm7U-7G9nALg`FF=%=MZ`gP`_?!0?PruC`zZ$pyi za~uq%klC;f;ynW1Zw<65-VI--6bHrd@u6|+3l`nMvb&H+2mKg(d2GS)uk(dB<{st? z9Xf?4=OJJLxwKCFP~$HSsTf*##fje)Tj(C>)WPZ=1de7LUpmmvbz-uFsT~t^N=^v( zC24z-ceF3Oy`Hr^*=4UKCkmsZuw%YLa=RI)vC3P2Qb%7@vNoWdT$s^l z6lcg30|&adN*lh{CuY%njAEAjI2)Ze|D|lIKmmz1cvHz)lB3@1h91F!dZQm6&5QGD z$3CGPSL3lkuFAMDXAkZ)bhIupY3H%QvfGp8@ zyvqKPN5=DrTO2LdUjIV&huU+hKt$g4HF0>*%WvLN8m;LOFF6vp2Dx~q{U8;3zyr46 zFcJ+o5HC1rCCeoWCp{f1bN{=GyZ<`283-S{nE08^#6$di%>bv-@9J~zz@opfCn_*P z+Y@$j%Db@Kzu0Zj!q3$rU%aUoSxMs1APxY!tD#Qd?j`R*&)qdE*+ z6`LJ`kyVk>*kEdMVfWbM&!-F=>7)M1>;>u=E?Ne7)KX-T;Bi9pV`z{N zd3P7MdAU2{X+LK{wZD4qvwAPq7k;96J($rua;5V}Ui=%o-}C%GtZqj6QXu`29!H&4 zU^?lr(%i?JqQK^}S-Gp>Nocx`fjF&G!&)x+zy#?+b2<97pdCtuZZ2_Jr-rR%&vi?h z^C8C-7wCF$VuwSDFAaY5O5RI!bWKz8zrVt}us*GRK<8CIk2Y%59i#e*f8WAOoZEDv z$gwO)c%^F~(4XBfv!?d-Cw}H>M^@pP4nz$fF&WQWcb?dPsVkCK>{ozQ#?l}56S|lW zPvb+U-Fh_0Z)>f~b^NsMx=LT-h1W(xhn-hAuylOl>N9?MAUN)5`qQ={`{36w*@ol# zWemF<3t{y=HR*afO@$N!Zrye~l>R93 zplg6}?-%n=6=e+bt0(gc`!KqdkBW5Pd;EJ|V2x64I$#6-$pJ31CFU>UT9?>V8zRu% zg}Wv^Qr!LLc%-OD1@}<{UMG2P505{24`s_;By2l&GF-0!V}A?@14RUeEi9ax=r$z} zZI+if@8pO;B^v&Yxv2DzR;w4)p=1;^BStNm0VrRGW%Vt1vlwXNr#a&SoHi-YK@-+Pt!U9l_lff9wupY_z~h7&toyGZ8? zwJ4!mcIg5-K*zuSw6Dw_pST=Yn@0z|#}r-mm%XpV0j%*ez5nXk>wi}`?CUssOeyVk zz&pUe^@theJ}?r3>T~DLb=Bb=xeQJn?(_NP^%r-KJ@IL`E$ux?K=h;r~XFR6j#y1TM73=G+tovY%%JTgdKRmeE6*pV|~J6__R zf(_RTyBq2^T8j>lYljxsQG#p(EJnB_{qlZduGKV#^o_z)TRfs`F!MMeDYjA3hsH+f z+L7q#FKALlC2(SW?)U5E?tcK%2Qs{4Blm_3fxEC!9tY>dQLsS=POHvSdGhl)zy*)) zT0&zw)~U-Hi+o@OX@rHvUTQ%(lnVM>;H4dCb=?A>%}v8Kc?hBVp>e&4XjAX65jxJKgthk|>B-Cs zk4Hgs=~jxH>PG`SLH0$0VElFO?@y3bAK00tPVkum;2F~SkLw*xUzT17z*>d@55jOqNF@@PID`1{W@mn)#J}3&zKbvJn056a&}nBJ{;F( zLxr}}>R)iF8Apds2Oir7&sa?-3fqg|#xtBW@Pdu>GA??2#Bmkp74P(duJzLOI5~y_ z$|bn{Bb%JfUPzTwscWp*%emo$gdz~PVa<5(YWX2ytcrmb_+rF@th{ebsau!-nGgQJ z?!WpLU*vhnf*R?0rkXYSb5ExL9))wG0S3?%821cv<`H&YY`ECac#O#YHEsPj?^w^ly=hvMw+KWW|jJ(9<&JmXs%m&75;cXM3wZ zG^@!a5+WD=EwN%p4Lc;^I9T)B;w=1z+#}>%fBol7yg09Pk?T7*a9(Bi&qcp4%-I2Q zF>A*GEysaC9im&um2Nu(3wR$?V+jk~<>CdM_m+~=FK+Kg2lIFuG|{5xbB~$Hd1VK% z-gzbG6+b!{hyL)9d{&BbWZ~d+pk^FiMFIkX)8k#Ta0 z{$u}DdocP^|5p9rt2%fDZul9=`1F6-fUYs}GI#m}f^ZR6^N8(Xlc#f4*qLc3Q)YlX zo=1JCKkY98D_yK%%Xku;#uatP=*C|@)z}Cwb#JE`=+F_BvrC|`XgL514fYiBuXJZ` zTzf$>(Cx+D)hC{}#U8X$(OEMLWyX`7SeLCUa}saj^bwLszljrA_4S^|*Aba7=PwkR z4pLQ$_hP;9v%lZe58CN;R`C|p`j6@^tbapyVKG5^Lc{2-^O)IIr21(Ul3$=sRkq8P zQrm~>U z7a)9FxcXKDdH+nDS-H&{ zoY;3)eAHQWA#Gk=NDlh|4qYEW8g87>dD&A@#z@P;UISyyFjj8Cr~=$+!`VW&dZB^? zTocz>5u&;**p0Q&I&}niLp+F@C$S}X@mZknmY2FSCyYRH${K!@UROjT^CS_QQi#5S9l3DcVau+34=S{;}fRWc_1Ej$_<_D#l#7o_p=xkN#CC=7F&fdb-U07Zq(3^{f z0;9J9N7YB~3-(rj<{$c}1L^9c>@Avc#~{w>L-}U5jj1@a+pzE(bvI~%LxH_ry!5!7 z4%3}ieigL#bGrV@V}x;9#d#%pv=4<{nzQlzVNijg|{ffxA`gVAD>qJuRgzn z4aEWA9&}v`);9X=M}DooNcg`j%{U^(9ho?5-7qiQRVT?`Y%Ahux`9UFM95rAdxhov zIaftx12sU2iiH_malHNN?)n=zv*PUKy;qkm zUEV$U^e2sF6)S}!hmR7{Ka8cz<=KH{AMm`OTVDKPcly@#R8POG?xAJO_n*_0( zjnDtUPwZa!nIBWZgB9K}2Nr7h_w_}>-~0RzNpWaTic;i@FhK~VSEu`gZjD#~=L3Y? zWtFYm8s^TKuo{=a=5zy0efYFSS#nEiUBiYSl3h}5G+hVBBErO=-8!zgYwE=tuTPc3 zFu}}FU@4;f)4B`m7f*2))-5dpm!$tYzA%X1(*pWLbx_2_lM4x)PO2GqP+{TjD#;Z+ zgREcf=NeH?wH%TjlOudN;M0l`9_+%ac_t1Ia}ACFgLMkmXBY|LdG5mUyRe{r7{jcE zw2&#ZI%aAx(-S(-pNheFN)ur)Q%1S(z7j$^uRbZ~6={Ak>3%U&iM3s-|9CX7zY5cj z5xQ^H0sRLJHSOAV+;GR(L*uo5GzS%=40|EnxJgmQzTeZb5QzQKcBQG``!7FU6i6aK zLF>;Fz=v)>q@>P@s3W>gEx{)RIwsRk75k6+&IYn<+D;MKyS+iUOE3_9O* z6T#r+8ZeF-wW-g9cJIjpvhSVgBMuL&1~6CKF!oAjQ&Se@0nA>@7&90E`96&B=!6Eo z`K3R-`_QNU73oH6pmi%H_5M>aAj3C$w$8o5jUK@~{VKib9QsxK7ao=>#)^lGhDBcb z96l$7jjVyb<);FJ0_WxJ{@NffZr{GFU3XvUO5giBC0zH!TtTpxK;{B>9+-~)BTdM~ zFLqlrhNdJqf#(`Q*zLID(ZNpl9#h=VVechY|(D+G{^oV@SI^k0rIWVmq`* z!{N>~!{Lebga#_NVo_bx12#T5WH zNqeXVgI{N}?(+hmL}BPUAODmbUmpuMRkg=RW%W-4joLLQX97Bs%F7Csw#|&fz>K@z7Ap z$!$@ZpfqFD<4Sm-3Zxh()*qA;>-T~6z=U_yfrS>hi^%qAFD4hEQk9I&iniKNsBY-@ zz(AD=PE|J1e2&sN7mCLplnoj1hBTn~ABxr}OKwTE+*y)fg)qw=X_b)%#jz4xe3Rc^ zjPs7Guit##{AR=Q%ADE5x@3~kT(kR%?#?<*_}^an^6uB@U067zQV*ywS?ZKPNgNg| zB1-E$B)oSgiyD2Dej*>30In1C%_Ye(Iof%ZMF^NBLNByl=*-V~_^w$gcXFU!Cu5xp zjP2c5UFk`?2BAUgkht3Ke5HBGH!u4J%Xhhns{75G2~A+ci}OnPvC*M7V+;I3)0>L% za$3Pg|EglxKJ}aH=(Oq*cuGtIo(RSvi~%PNh5wxjMEIVE9j9LXzAN8(rBcQXf%b+n zvdNv*I%Xc>8D878L{-5^7*JLYdctv4m`Iac0G)RmoLL|+W(!Ik=;tRNx|h1sRzkCY*O?@|$1!Q@am+`Zs(0 zd!3e=*hkuF!9|(W!Zq?TKR6?RV)m+~cX|f;ZP>2;nRCE{PX+o{^UpqySqAts|82gR zZR3rsQ5^b}%wjnn8}yy_T+!BJgXo1lop%aT+H)r@MOF@{C%8+y*jUO``KZ!F51;HW z<1AxuX{UuAW6Yz2y66VxI-yPdF;;qiIhcnvJVOXVs zk?b<33=pVdii70TIz5%m=!MzQi!Xg!$#07bNR6miGCuP?zszW(#B#~;=4=IfveHbG zR&%+VjJ}}1dPaQEoLKL;1B(~B|L*5MZzVARaJ7|->a$+Ys=;zcbwIJ6aC1wA{gJ;=>TH-(Ch`?}SA4Q`acU&#y$^$bzF;9APci~4K zdr~|inf8W5XvuB070jd0GY_z^t75No_#1|fRlq1(Fpv(`6i!)%aA^rGx1sQ3DOKAA zdN;k)-I}yQb?T0o)cYuTjx|tY!;wy7O;Sg@t6fn2ZW}m5TfsMzqiCvd2s#4qSRQAs z)Dbe?0`RfbehcKiyIC&U?)B%8K9<@2Q0W%-;M&%eP$B9KE!<{ub z3o!cYOvkC|fmPZCd;Mb;%_p=av@?n+CrHL{l&$40^$6zC=Z&m^7=ClTuYdO!bgjgr zgS=9r9anYn-OejJjl3g7QQ}4m6?fVj#lU(`?y@c)xM~4aAU_V0Yg#+6d<~aLW^pdI5-2XgA2w(Rn z<(64eWix&}Pquv8psQ!sH?>aPsL?Y1BV!Cnl;x@iH!a3#^>Nj!$Isj8xT2-16S_Dd z&@T&O(**VlKmXGzEhbj{ZuiVHA2LL5mdhiBbTC&>ZoiU>q3{<91$4KRga^rqMQ^<0 z;Sas=Q@+sT&Iw-tt420`DY1rtCn%Kb3ujGik6*sBd+O@d-7`-W7ZW?L*i_UpejO7Xy=42{+hEk#)!1yih%NOPN&j+Y!5C%L z?cRVGsxrYijAa`ueBglwM>$Db4&i1T;kb(9_lD|2p#2@tQ3s1L*M^)fUN{7Q&NqL< zbV}Kbk#?*kr6MbkGi$QJtHH#U*MQ2?Uf_%)?!vmP4FE57e-s{R;E@I%Y2c9t9%;&)J{%-Yq zfnA{mXQ|(|t|@uyy`NuD&Sfe#5S^U10Zjuw`q$axM;cY8_w!b>)P@=O`mFjjKW>-V zOg3PyO+Uq{75Y!VV-tOi;a)+&9GnM+{cso7kIbb;+47YAwD+ezHc2ZAI_+9a!B` z-iO6Y-I-K*hYOvD@6~q_43{Z`DR8@f^c*L~#__@TJiEJm0gk? zz66NUDF)|BVU4onmQ=)sPjeQm5E?=@AnHvzC4mED$_<=}0tZ&V+sc^Onvm1tED_;n z^)&NTLs)gYhw(}0T{wa_QDGhx^<7x#++JzX`8`+guExcAp@qV~j_5H$ElSkT|3viL zJP2UquvCJSCquDYnpW=r-Aj5|kOv$hxXG#`Xx>HqO^O>9An1UBJv_i1_| z^_$MFU&cYYDQPe;u|cmwr-3`27>5;j#6FZ6vC?q@^U`)XfmNSRTzy)Y3Frn|0(yvK z_>#}rg{L~r96~_ThOFY>u*97^LgGd5PI0ro3Rv_aC^FQa+Bc&!1r6`G`DZ#c9=#4hmjQt;j?DToYtJ+55{k zbmtZCSj|htb;YFe;h31xIIq$Pb4Sn*{A+J`^E;_8Rh&_jeW-%kcH`Pt@)+T0C=E`p zZQJS++r3J>LCzjKdvSD4eopo7u^ZK8G3Y}zXSL8*l1eSQqq& zD0S_vY`P0MeHWJO`V@h$Z??l0dx@==9;j5SgZe2g3A-zge@uAXvB`huNkGmn>ca-g4{HfHl&!iPK`ImAO#0rII$86hOQ}> zp41biwobDK|BX0pxKG#MdcD$!c%`O6BB8JT>s|bwnQm{t3|3;PinL;oSGIJ(8LB` zF-RE+<=}Bd0Jk+2jqG+Gd+aGcMtB6t`32L^YOto;($n0W3%ketD_k5_H4Ueqgw~EL zHWr_L{Jmp?LEbNKm;SxED3#M+?X0>}j}GP~?o&UyZ@PbCwR6sCohTta`X77fKbO-E zK~}iS*s(3Dyq&*sUghpZ-mQ1t3*k@?%vPqe*a=d=%h`yhI-&2hq0wiaUE1b4mmMkb zs47gvQ_}stB5`43s0K>TFLnRtKlFpUKlja_?&_Y?m=+UOEf2^VIjXEs9z_qR2GpOu zYps67l6y_Tm^~ML$kkF9QvU~L%x4!kVmO~4cxi?x?X_%v=@B)iO@3%)W^k`BVf?BPMs+n;3T`R zlAq7?;cE5w{L#EZjev!RrJAb+rxMDS+`+u@2H=bd)tMJw*WFjSi06eOIPlodt({oQ z#X0rR;mMD=HEG)EzmcapjkjpeFyJm;Xfl7d^J)|K{t_BB+k3%rcRw#||M@ttYTwz{ z3+Wm3r2U`z8mAT;#q@_6ReCTcQK9PCb#1WXt51F3lsk{f=)}6zsatxgmY_co&ZoKgo*N>X4Ug+Lc6m=dGTpU*(CpBKu-ds7kO|-T2V8PVQd2(IW zAj$|GZ__SJ`Ha%wO&qxZOQva>aF)Hm!lLt#COx0GIJN;0IJ_r=OinG?LL7<8>#L^k zba% zX7`a#J-NGi>*nsJ-lcW()~($Ox}ENpoLe}!{6cDV6rK3=^vrD}ggdgT3MFsUzMAQ>MSp zEB=h`yyAKGyi~KCSNy`l0ECf**p|oUqTfxRaZ-0)*|BN6iQQ7Kt{b}M*GR+Q<-hQB z-xnn5xqL}W(x7AfFs_LEt}7gA{rK$-%?aNthg`xkLdaKhoND_4PTgkP0(NphlWAAu zF8qNcClz}vt+6Aq=}wF%#>G`6bKAl_B7;G{qbKH2;5<(F0I@($zt75v^%u^w6AQKV zK{H2Crt?-Ur$eEU4wXHEz2h2Cx21=$XZ_Kw%vD}joN*o`uRJxc*d}tSbG4++AWUOB zM>G;y1mrp{fR{i+?$fF3a-Y~eV)KA&KsJ~?j#e?*)SazCd85^BkW~u@{Iwyg99!(k=GTP&L z=SM8v-oC|$s?aWQ`i*!F=#gU!pK%_d^kgXV9&b6E9`Q5z}{EDI;jc3gu(Gm=-NkAr7J`T`-rs zxNsUb1U|1%!fVuZ!73NIfIx1VChU4qw7|elC|H#x*LYxT(sS0u1wd`gn~o{qQNnlj z%Y+Y{0}HkMAvv*rmtO47>!ux~Kc}R)&RgkdQh|%uOlpdhW;*jfk;?>EOZ`X`ov2Su zwjDr8c>c*}bsODH9XPP!#QK`DFI~=n4PPVp1g>Z+>cq&Z)Hc~|TnI~IYJl#p$dPfg?ns%?_L6_CzDP*D zJXr_>c@AnkqB!Se?!!gM3oH9fivarmhgcqPgpl|o9Xl1VP(xlXF{M~&~)E@^_eH$D_WdZarE}f+xyN% z@a;UJuXXgT-FEZ>Q$OO&On;`A+fLAb(iJN+tlaiQ4qFi1WyHa#lA;Gka>TD4Wr-ty z>%6kl7U2%Dl$-@X2PG@oh&)gQrud8j4J|xo;#o4bOB>hN&;gmkag_)|BhS16bUyRV z1X02KQuoUqL}qa=PuKx%bwF2y0obHnxIHz|7XB#zPHJE~4&0?WF*o^_Ue*~O297?b zH$7+6pw94NzSy*8o1co?e7x4kn8+s$QkPBy_mI55-0q$N<%VFrDL2z6g)W@+Av*&8Ev$Zew2#$D$HKh7&&8NuUE?4k3%p6vRN zCm1_G?*ph2F!(4%WC-aT0Qm#%P`Ko$mAJ}R(O-aH5ni&*f3od0+J^e-3xJTqD zf9|utZ};LC{$Sy~z0m{dz(NH-sTaHd>(6~&`qG89@|dhIxJh}^2O^C|lQMe1TxNp{ zjuMq4<+NIxi*&kNhFh&)^%%H5^kfVNS#;%kDu<1OP~)_s zLK;G%$8kmQCPry;fg9{oBv_(bfeogqkp&WzZtIppGFm2dq_vK)lu5P~V~V3r>$|X! z3zG%g(}5Qn78~iDgGJFes+AJUAyUEj#0qd|QUlhs?O^FV@hXr#qB(2bKn3LC374dA zoHiY5+I5z8UQs!)hIjvC1E|qDRoZu63E;;CwRqThg(C}k_i1bl2cyS=`nn$-wDYol zF>_zKGrB_(cdnJ+_8NRuPzcyY(>EME;Ixo5n|}Qpk5P^t_nlX~u$|+E>e!DBep+AO zT!$D-&MH9%#nTyc4+ZkhZY_henasI4Z9UMCC86{IiQqtZLJ050+B;9-A6-vd-~b~SRap052yypUb}WZ?|U84+~kWN{rHr-Le;mL zx9IzxJD3xkb}TMJA%9Y^m53KHGLe@HE?}fj;&4>?T@xO~&~-er@KM17s{!ngODNVc z8KoAs3tGQyqD=q%2Tr^SvZ0L)w~epRI%7tic@%%^%m3N#L!bPY%8A8T&YZzda{j>- z^-4`%D?MB}84|rugMX|u8?`b=P&R^DG@pXfheLLkRXq0BC3i5|=10LM(fo8b`psXG znB0Ap7cOfD$UcsppyFHwfS)7qO1rJm5qk2(rR1lWYg@>JKy+RZFv8fcyV0i zN7DQt5m{y<{dkY5{pGdSf3cibxxQ#8R^NSPXO#AOncLC4^n`vl<0O>>zU-6JZrAi7 zK4CWrH{STt?lIkkwHH8fhb|?dDt%h3^vQI%vx`u)efL$?%h(5Iv)>Kkp#iqy1z-Jn zjaNn5iPh__onB_P)Gr%$&+g{67j}<7^;y-Mcf-9Mjw|ZE zQ8M{0Xlmk|ER6<)E(T#3xZKHRxQR{ER9yzUIbHb`Xq5q>!UQ*O<`ec~R~zANj~`eE z7Ml3OFaFf-cRcs2c2_hR@*XFTAL$S^u3qlWhykcr^B;-e2IK+7V7bEB!--X!;7fR3 z71Av*PO2d;G`~B$kLn#t9)JSE>!lldJWzket&775g%9%KmGX8}jlAJd2G@%xIg^Ad zvcmzaA*m%&+}ul^L3IjW>HrU%W2lHb+HQE;G~@24xeM!u-}`AT@tI)Kikx9Y3SGpx zhhu}s2p1=oCnXjy9wX%TGPg}s`FCBY*qG4l1fhcvM=qb7udm=b*So$1`@?VNadWMsResxH`zdERxy9;^m6}Cv9 z9}cmbVLeKCs0taqAfp3mU}p(i9z%|pbeUm8?u>CjBGmz98)h<qjDoAC3Ujn^V-OFVZD3(YYRpNp1V}Q1rY1@MZXT9 zoLDNyvinD-3pm#~^haPm%GhxBm{Uf2Z=T1+7IHP0GFP6faIm@ufumW+mkzXZoxoQ1 z!vvj@`*`;T!4~ga(4GCT!2z!yTQg%9->qyqq+ni`NVAA?5S&i((fN1{9Db=gGfU~x zqA+Ez;L!&NZMj4?&C$jtibm zhVFy8PlL|UZU1oHd8=C8*8OYe6@7_q4Eul|eVf>4^MYmiI{dnxZ7;DEKhdK;i+u>c zSdn+_EeCr({asiWFF(C|;+cOy)jG;?g^-L9+7a2*54aA?>^u-)OTIDyD!8ySiW`3d z9g&Req8~a1Ik2N;x6H_D>_tqMHzatSYyM0AK#KaPL#czWkjP7?&wcL4^g{QaI6&~$ zf*xoG7ES-7FaC|)zxv#-^ibf@7w!h~T?8uIb7HxJ*~C^y=}syeeCv>rRKZRx@gq*h zr8A4N(Gr&v%Mk-xaz6UllZ^f{x_j}NMhVZ(*F z@hovwYs>S{ZMN_ie$FOv6Z)>Jf(XN4pOJxhT>`A`+-clx%ohp0c{i6xYk7{<`0Zna z$Uq_vyPn_@i@w?(IaVA%{r6D|mD?o=QWAQC-jk&H21dhCFZEagIa+*lSmPg!cb-a~ z)QdX%>w~n9_m^33N@GtP$t&)(064E&x9;alEFOFvCX0ysO+2tf`Y3l>@%Z5Kh^=9i zbsri(G)lTR=#f4KtnrD9e{ecqa-mhha9Szjv|=1rw?wP$j+|F_eD_s8RB%!+$4+vV z03|$Bova0tiaJoMllT>CMd+ATbQp33=rY5G+!-Uf0y4r;Ralb^^ah8A&eJ^P8VJNY z>->e0L3Iuj=;bKgxX)<&Ic(QCukw9Sx%w48J~UUwd4&V(vOdZZCl(h%1WW)2mLF@@ z`zx92G$nYxB1yR?kvS$!5OQRYM2_|YIIJXtd9WX;z8eP?e2))DU>pHHp4X$&d)9yr zerLNb#rChrZEbz-c#@kfuzV0+!y%*V0RlPVB{7QPn1BXLj7>qrg{UjbjJyLd@I zV4`=cPU|#IL|$8=en4m5Kf6yr=+03ReUHvMS~tDE4-8nW_Pyi*`O+_$A;$$Jb|c@m zG1X@XSSRlAnB4sCD?6=dAGFvTbEeWG-#k-3;NTh`T>poTOdY=ZpqdT~z4wji*hlRp zy{D`@uau@9l*0-Z>@Tm&dBuLRU*8f37T0pR&z|}H=BtXi=N>(AHf80xcHruaGX5ze zkYVr2*9i}W)M20>LOCZe9v{4P<-Kk@y)lOy-~E!`g*C(W)95UEvvY|YdLGB-`b_2S z<8og48qIwkxcgx42KsvNTKL9JLNk6iVt(v*VYQKTg|QorDgL;cJ?SnioK_co$5r2z zn5&{ahpzd>ZX*i*I*{j(IC3dk!wZRmCFzv%ThIohv*F#y&`oI}x!j;Nd?lc0Mm@U@ zEu{0!V!-zRqhv1uD)Sn$Jb&U8d}m(h{=hr1P~Ly|;{UVzZ6EuG^fk3htM_72mdE25 zDYMb0djzPbCl?#zk| zIvCk-ss*d^<~`EFF>2vCO1h+&cPbZL0BI!{Nh70kEx+&lq6AyxYh*Y$j@kQ|AE&+x z3%423m)litsA6% zSaWR7@17VPT2DIX<@?EHnkHS2EG2;P_q{l;^fwj^Ij?fTmJLTQMre`#!BLqDL^c%D z(F>CMHr5y0%h~-442ap@9Pu1KzJelbMS8uDL+~ zB;b@`1I%G;Wq;#`fBEjue&hcGCM#1Z8S{uMSe74@-;X0ga@$OfR77c3V zBb@6vuP$G{tdFwjUSiEz40z@z=7EcHUV*1qSSCO(IP(XAd1f2ZM~p~hXztU&o`8d| z7dy?xmoNN8eV;n@KC#(0=al((O8S8gIAx9RC)$~7ab^N)yHCVp_ZZ=ue7RS5VqtTj z^|~`i=$?IY)0!SbX71Y}O*Ak4iNAb5RrXNudikq=e($C3x%iUe(49J>BF`&JC)9Xx zVN?O8`|p5efUo*yuRoC|9LWohi81^(*+s;W1q;e_cS%-w_RXyHnJYbkX^te0>`DXE z;XmTaH*BVJT9~dUv5$@xe#rHU+a1@w(sQZ!ezo?M+SO?u_o01ee;nxc z%~vEZ<0pdAA^O-J0*%mR>~^}I#0kg;<6#QcsrAyWnp>!JHc4H$iowiYN-3$SujjDk zk%l*i%F}~7)4$k^`ji;QJdEI_{|d<*evHuUY;()CUe?@PcZN${Mq4f;2ipwgrQI_h z{x!Q-zpnNZ*iugBa=(LGK(o+PqQ6K@)i}G3kMc$ z|Knf#n^wX6UMv6_H>~Hjt8`ul3)7Y_fBk_%?$yXZfX3sn@@#||gmGq3dQ5<+w#1G+ zQP6S7qeIh`AMu@8k@ofJ&a6zA!N-xr4U!q?&{F^KaZYKocZtdrQ~N_PC@E9vde#^e z<|%4ohAfYm?8u7`)68(buC;MmXtg%brqV>0T;2nF{iOvQ!z zx_z8<@ayC_Tu_yQMsTb+1-gU$fqq;5tF?JjG#25{yO^rHB>r!kI9n58^6OdDei8#sS42R50Eu_j6arY{B zo1E9c=GMQM&wWm3xx+Z6ADwt~I)w(XjRz}qje4%)5oa7rII(z?5I8-}ulBcCCu*d6 zUSUp=ZD1p-k!bs+z?n@7i6?$50m{XQs zT#E+2nsb;xR((w81t&a`uf?mPhRwVW!Itvv%c$8qyY99(r9F>jo=jTO4Wky7hQ|1T zg&rB`BRZiLop!RyY}d@gI#$jrU^uV5x1?Nr%u3EHeH|{>5AC>8tofX=a!c>?x}&{a z_J?Rxl9|_uIiu4le<=OF5B^}*3(4aP^Iw1A|1!1fO&!vhG~>LV-IvkLx?#G*P4B|G z`ow2W*}{nj`kjyFAl;?EWhXc!u^D1L{j^pqynrYunJ!y+y3e5X5gWly?wA+0Sal)l zKs#C6eKiZsa`(^0OHb~ec+W2r-S&M+}Wj}2-^Ssn0pNSrc?8&2U->8Wf+0em1V zaw!ynX*h*=Uv}VKZkAQAfomYA#;x?6HO?Nu&{Llv_k2i#^&HbD-vyowMi8>PqIo# zBi$uoxSTD)-Yau08N&9JFDBHZ3=X_==}AdvuNoeFv`?>g&ziRKZNYTAakg~ijf)-N*affexbo2T7_yxS-gjW(fW4vm zymtF$?7?+L2z7^J+MvWSftGd+h1R4_E+yf1TAnR#!+Xts??7%3Nd+(3T>&qw;vuPQ;0ISS2(f!$fD%F zrQty}(bm&j+W50EpJwZ$3GZvYkC1pq8T=4*QBEw|Y18(CH@9Sz6|jv~F}BT^(BeB& zBt1YQv5)fzvb(33x@)e~beI=?ONYH+U0AEh)f~(lU5zK*1-I zBPoS)V)2V>dc8Wuh06&)n6I*RvMOdwJ9coJ;}mCKTO>mI*imqw#|fp5h>RK>U6xrLV}?c=7c~A$MW1$VPIwKqWb_ z)oFHW;7DX51AAG_pyH3ZrCx9d5BTWdE-Ydn)U)I%F`QURH}B8~r}bmbGkYPCZIPru zbmfWNcWzw+dSu`(t)s$&S9cZn!+8AJFlQ9ow87H=anxCxdTgi>sPet z*Yxe)z8gwC7F7v`aB_!_?prA8Pl&;#jCB6kp960hiFss@uMO5?gWB|5~Ml;?pk!g+q4A0uSWU_P_y*?=YmP01tF^a%VQXkcaF>k6ey>0j(NWmvU8&CZ=s zny&~K^e!8M=LyXVx8=ObBZ15g84ZJhjU^+q$Ax)n<8K<~L(hqMj1UJ_)*Op?bh;-E zEd6*Ko9gQ0P)=7yUmp>UE;)h#@XT*xx5x4mozaCf_Bgswm%+mHXn`${xKWjU^HzT5 zIYjui>)22S%*chtRfF{y&k7cAHn4y@8HSytadG>hib#NoB@XsI28H>Zp4 z0PM{>H&iWMyVzkBC)T)d*A6gsmV8&8n1pIctYiE+-)O-}E@^6$KwiO-P7}Y}fwgg9 zA*1fpxq1C-YF6fTe)P`u!)-a1zHP@+e$1v^kA`2~z79e)s7mC=^i%_2s?vqj4OsH8 zzNRtbP45r{DjN3U+(<_g?-#o5{J2`))h>aTRDYAjLP{_{Gf^3A=Eiu!^ip zrMi!%7?|}ap0*ic_3fc+wBDPUJ9Gr|BC7sxINZdGwLR~@()zA-J?{!sFAlu0F|zlv z>;YK29~0<`4z6Li3x!5=1>_1UO+M-9sPh7MnNz??RGf^@InD3}3w8+H#T7>`8IYT% z2`R1gklkfX16g46nbLIyR63LHl^bx6Cj>CQ7-3!a3woLRkLzXbKYowAPfva>9auE) zKhS%zeze|;MQ7oqmF+vME@&})!Pze`A$st63}yg9pj6<}2Afze=-0Pm6=QSX$q}X? zR-7Ew;9?N7iMGJHScI_+Q+e*L`mo=Z6$ijKZoVPhDp#ss>K^MrEDV}v;C zdNSi(OLk;waf!~L7oS@Amc`bsq!lC_ZR@`ehEb-pE$&(JM>)4Xb@iFu|M2ZEo`uyt z<9<-iIP0hSG-=wHc1Hgf^4K8F_o4pRgl!wt1Y(UBbK1x?cd~|wCHGux_HmDCX8g(Y^bwPd2 z1;73=Vc7S4#m_z5g~i=dx62Xbxv%8g6tIOJ)TFC^;gxj;em=wpUY;if^SV>K&U-}D z4~8QPX6#cT%>Qx z-QAIa}!xpk_$_|W`^Ge)Y zM|i+w%3IO|?=tXoxE@WbTq;3Z6nx=8g0S%Iy(aZw-psyVam}N|w{Lty4vA3G-gaKy zy#8%{eek9oSvPL#y+u3TU8H~RB^bfdm7^fQs?os6KuPv2&Mn@dl);XSH+LWVzTZRb zl)Sxr>5G4O;{V_!x%(;sy`UJ5)>Rcv(T(qJomf-hoTT~u^}M5fci#~Q++ARiu`BwQ zACh{lq$-eZL)e9H&ws=VU9G#~$F(Huy6a(v;#SQ|okt6Gw-X5el}KHX!9QN`p7?;B9^)FijJ7De#8Dpwkss zS#n)A9eia6N!FiE%i<=Iu2LAdEz3l1>mI8}{ZuM<%3s`yLSUmcGo@~9qqX}cn(pZ~}E=-`4s{UNQ`UWk*{8D04goK6dm zw95J-jTa;P=yzYCaN$r}eg&gHxxIV!=pg;;#f(G--FWUEJKa#w1!6xFQ|r)%4CQ$>xRazjXV$Zkohl%y{K)#W=CxQ^QWB1VfxU{)C=V5AaY$rj0fH zwA|LRs0ow_bFR4?rjzULnm0am0&5CKRbpm5p*MK&Fd$HD;=G2h{qeuF``B;)wdyM= zgbPAVYRvoI5LriQbT0xb?)?nzzf`O-@2uv>{qdE*b5@~!u3BV=#qD2oPv8S9NfUR z&`0qpHSqFR|D0ax{u{H2vSB zwu~eMT#MWa+~k=HK{Ovp-R8{%MwB6knvu={u=kCH5PrfLVQlom$22Z)J~$tej`M$U zX5qYoKOytF?xfcpS1*5?AMpuXCF$;{NnGG{-s;TYMn)F|7EUWjPEwhy|{PEheJC)Ne6_x;3Dc$g80B8ERU$F!%}>^Qv94$mlo=ZwCy;b1rE-zRfV5MKi*s!R`f)8hf+ zkPgqD*DI$|q}Y<2CO6l`3vywgBlN{roQ@0Jh4mxv{fz1-&Z0qGW?K>Z%d_mcJLe*e zfw}Rasl#%6X@QA?XnO+Vmv-OXqu3Xt2oWuP;a_wd!?(o4SM)%No6FWG;Z&(>6!JC7 zea?OH+Ao>o_wzM8$xGed-2H|Re81tHCq|dQc>R?#zgYf5m$~~Y_CyPnv>vc4AIcN^ z(Lu_5sEl7Vbc{y_?Y!c-ad=2m^auUW&MR%c*?=c6%T>AiXlT4!lXkjI-C}rE8%G}t z;xusmF&4lf9tSeWTb4HeikC+RZ^_~Nh8?Q3$>T{3aAK7-#b_k;AwElJI%t+O{K6eL zsYoDqy0GRj^ClhcmZy`4zF=uV_@g5rBkTdHWFkv)K7$i!VINx=JkMi7c8YO(zF^+* z1Xr5E{xFhLhJ8$w{Gf;ZT0iJDUe)h_iapZkJ0F>Q?7XrQ;FezQo5%Vf^(I&2(GCID z4bkW{9YP{6oWNT;dTSVQ@GKKPvMcUB4eeIPAm2qe1KAl+Pf?K#w@vmCgHxbv$co|w z!V`*%r?5^yJiho|=mgTF*1T74r8z9Z_ zKrB#WFCrSRAp@xa6VC~BPsy#!W(IsNqb_s4@!q<4>z;Y*B)!F)=l5b^yo?p!X`%$~ znbxbXai|LyJ_N=9hocNEKP9cN9rcC2ZtqelCb!=?TPK!Z!tT)qZUz{mk4K!iD5oM6 zQ^sI7NIBmvCY(64-rRlb)*F6z7Pt(>)K>L_bcDz-8(6!3jia3FF_4{T9B8O3?-0Aa zX1XS*91aBQQ~4~j5NRs+q=Y(kp`k|wxohX5;!GdZfhH?AJx1ux*+?0a<9rb!;xQpA zjr`81wW&k&48!PSVZEOR@^*#4d+j^B&pq*ei-%I~$hFtogomE`G^`nFItIJeQm(vFK znFKgZ{?K83Y_k5y8&9zkOL{41oGdSEZ|Bu*-+A@w?Hl#(wz^eS@^NCpi|6Ix$JsA_ zthzN+MJEKb=*P~I|xQq~NpEjm6+03W&zGsiA7OsB|gjBXWen?k@7pps3dY?2G0 z^U;aQ0i5IDyRd%rNBs30CNyla@0@Y}9HpZJZC-ljK;b(8I$hU1Xtkadb-EVJ6eG<% zp|#+S9!v0=BIngDUkLH8msm0L6l*kdC}|u6+^v;I4yBjk;mH9t8MKUV*Ul3MvNY=; zk2&wcZH*h1s*E3P-qJvw_5|!h_E7E?FtJW@0^AMH1LX4@cWRdR%I5$*nmq{KR_Y+q zDQNfT0@#AN`|8GZty?#4`cAAnw$a#I8Kvi~RT|sHHtVoW<4`34-nMKjG?cPaA2K5d`x!Wv`1%}zPA!+*G-H>htx_MvfL zp&Q}(4;M?A1W7t zk358yU#w6TYG5+W>)vxPLRg(vU3^r1SAWqLXLIRUNnT^LIF`pGcjvZKn z>f_^j=an8C)GgaMuV~Y=m#^+#xpiHbixW#3J7g_eY5Kcu6j+uzYaY~N0ywc1^HP>E z86M_3XsLVaegIEMg!_Ut=iwEeEiZAfQ-g)_gw(`1$>xqOs)e3!F6{n=5B;*;fBDV7 za{|K$x0uJtWW+KYwb8#INLM{Upf-ofCizk~8;(RZhcyKI5hmMnD|4YN~8 zjsWH{0_)7>E4MwT(bt|cG+*87$Fx+AGip5U%;chZ1rgK%9{Ed&xKaV9Z?RIjKt{VBs+YHuD2ZnP#Zr zyz2Q%d_7MRL&_;f=1*od=yHG?v<0v6BiN;b$KV|<#iyKCs>_{Q`ih{O4llp_Qph>G zj%p80au^Bantv6oq8V>h;7rwgzd#Vr^l8|^^5F?tm3Cc9uJss%*&Yn(z7vUM4L;dF zj*TOY7EaUOPv=(J&zX8(*gg5oKe+qutN*Wah+Q0_spG6KJF$+r6D!SA%aoXpwarQ@ zZ7W-vuhXaxQs+21v;|L<&b$RPAJGx>!+Kw{@Kxd49)J*`Xm}Hm2Y>*`aV)qAjh zR(E6RJy;I3y72*2dc-r#1a%T#qt4(1>F{r4=%%#r7x`=WN z+EmPD99zcWMf`zsX1y#&R&KXU-Qbi zr)5M>;^OAPs~;oGU0CQVwd={PCnwXGGeULn&qSt;nlD&L*8!UAiFcm~O_~048Rd1H zq6v_M9-<0cI_D=~k`tf_PZ|G=oLE2j#0QKWvE4&qKzlkMrSICp`E@9K^s%R+6VYKa z=!dOKZ(lx#^GYzdwywM&2)H~tn6D4AKu|_N=f0Ccn`=9-?8MS$yWn%5xYI&rYop$y zRo#Dv(3i$ep8|89k-~V8-8k;NWGm121^w8d>SE{BE$+NXn>fGk^5goNBu*@knl6C5 zV5t~qWI-3`Ql_F~f+@`f3KAa5PA=ms3{J~4*f862h_t1g+PcnSy~d_>v$crkl;XQZ zEt|X>CI*bD^Bi9K6MuR4vETZ`G6mTJa|rVev9k1=RMw}nH51xL!5XkC-ZnE)|CR1> zKxqn)L!D}<0yVefy+U$0@#B*0myuy7mJsuZ7I$K4W~nu$={zBbQ!T^6QQkkyXx3pJ z^vi`Wig!;Oh`vu@ZBpIH`^5YRHt2@``yR~G-0z+Bs9zEzdsfbaa3suOojZZ)3VD?| zy&8)Q?cA0F>sCGfn!5yLmu1H*yKERX%fhAD^BIm(815K_K;e8@7(&O-K($h`V0}!jNY3WQtcS5 z&9uZ+dVoek<%6JbJDPDCwvOGdHE85KYnk;C{QH7xP>^Wcl1=+7ptV(?9|=OStmJeq z?b3%v(MdQ>e`e=a^#L57c=lIBAhO`SR1ZjnF6elI+xDm}WzX@BkcqzOEb>(%wIsgDb07*naRKN}=!oQK5j(lK?{0YabIuB~&QZT`{>A7gj z!}`=p0zx_Md#=r~ZHT-u&6J<+}4aw{DG3Z_txu zzd_b$xh=~wrY+IG5MYdKL-SWOcFTyVCmVi(CJ2Hc0@~fUfk4wqSOR7g%3 z>$E`efG2)cFN4BC8@hx0jmxnL%f4;_JP^4Kbf&yc<;21f5_?xk0^xcv%GU_Vr|M13 zsU~!eYXav|H@>=z8i9r3RG12!Qzx%p*Afipebg z;wwK`{n8uX(F9wjC|QE|$IjpD#1{ug*5g-Sv=b4X=w6|7d`NTP{3=-7Sbz&xw(Fx_ z9ke~m9SM;_@4I*aWTM?yV5JjForc`*BjU*sEt-@GWV;Y%g9GztTBh4#uDXi&nh5t~ z>#_3ciu*hy?OpTAjXlZF3%}W;La<%c^(~(00+?-1m~p!+P6BKU;6Tq=L5Y4j=1n=v z4$l)t1WOfs1JLRjYqiYCu1~iNQb2})fD^!s>3~Wy>gXg(q%)E`KFh0PtLqwdWzQa0 ztFst;8Qxi$^{R%uL8~)`y{alqi>L2o@fv{?$Ig}IsM_UUNHwnU@Puf648sAB<11e$ zY?JQb#o6Ye6MLljJ{=6gd8LW7e<)EIJhz4eiaYJ05j)9{YSb+;6mCo&3AuAgvcpjy z4$I6JP!~X54)^MiT}p0euz|y|h}LSKa+&4|nfag{sv4h|6L~*L>=C1rZP~txlo)%< z14ky=-_%{>x`%cYKaBD82{-Hvj9#<5v`eP z9yMBO^6EUQu8cGCJQxN%=;s4FGPqvL0$=4a?Gn;?1ZL&~Q|AiM&O`EZ4e*Vz_vIHK zPsEZFo`A?lpsoml8Qk+R%Tj5hsWK>%M`Klq5hpnJbK!KzJk{lymr$eV&3>T{vj#q| zqi_>$od@rj9}e}@^(zwB)rwLRHFrrlihl0gy8kU73VOYru)_xC2;J(P%hxJVg0hhJ ziuc2M=UCX&W+4eBZyba33K(2eAL7QdhSKDjgRvUkvP7HAs&R&2+*>a_;-8#%p{)RO#)33KbP?=poJ#v@g+z$GuoP&fwbTSD*VcCj?FuDXuf8}DR@RX z*~k|>I3_I1ilWz6G(eOdvqoA2bkbv|4$E_E`Y{{G)Iwnu|dZa|K>t+V@Yer0b$pxxVM*20OFMKONx2;-NWBc&V>= z^-35^PAqOUthj<_rRxkh-O*bN>&VzG=UhoW5o%C)bEx!UGfxuly{7`rb8(x zgQReJF52|il@~+rE(nTGJ^QWI`|gRASR;N*4~&Q8>Ve%0D;!`Ikht1n^{4<_ibHKw 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 literal 0 HcmV?d00001 diff --git a/src/main/kotlin/apps/shared.kt b/src/main/kotlin/apps/ch01_02_shared.kt similarity index 100% rename from src/main/kotlin/apps/shared.kt rename to src/main/kotlin/apps/ch01_02_shared.kt diff --git a/src/main/kotlin/apps/ch13_groups.kt b/src/main/kotlin/apps/ch13_groups.kt deleted file mode 100644 index 4fdb6ab..0000000 --- a/src/main/kotlin/apps/ch13_groups.kt +++ /dev/null @@ -1,118 +0,0 @@ -package apps - -// By Sebastian Raaphorst, 2023. -// From https://forum.raytracerchallenge.com/thread/7/cylinders-scene-description - -import light.PointLight -import material.Material -import math.Color -import math.Matrix -import math.Tuple -import pattern.CheckerPattern -import scene.Camera -import scene.World -import shapes.Cylinder -import shapes.Group -import shapes.Plane -import java.io.File -import kotlin.math.PI -import kotlin.system.measureTimeMillis - -fun main() { - val flooring = run { - val t = Matrix.rotateY(0.3) * Matrix.scale(0.25, 0.25, 0.25) - val p = CheckerPattern(Color(0.5, 0.5, 0.5), Color(0.75, 0.75, 0.75), t) - val m = Material(p, ambient = 0.2, diffuse = 0.9, specular = 0.0) - Plane(Matrix.I, m) - } - - val cylinder = run { - val t = Matrix.translate(-1, 0, 1) * Matrix.scale(0.5, 1 ,0.5) - val m = Material(Color(0, 0, 0.6), diffuse = 0.1, - specular = 0.9, shininess = 300.0, reflectivity = 0.9) - Cylinder(0, 0.75, true, t, m) - } - - val concentricCylinders = run { - val gt = Matrix.translate(1, 0, 0) - val m = Material(Color(1, 1, 0.3), ambient = 0.1, - diffuse = 0.8, specular = 0.9, shininess = 300.0) - - val concentricCylinder1 = run { - val t = Matrix.scale(0.8, 1, 0.8) - Cylinder(0, 0.2, false, t) - } - - val concentricCylinder2 = run { - val t = Matrix.scale(0.6, 1, 0.6) - Cylinder(0, 0.3, false, t) - } - - val concentricCylinder3 = run { - val t = Matrix.scale(0.4, 1, 0.4) - Cylinder(0, 0.4, false, t) - } - - val concentricCylinder4 = run { - val t = Matrix.scale(0.2, 1, 0.2) - Cylinder(0, 0.5, true, t) - } - - Group(gt, m, children=listOf(concentricCylinder1, concentricCylinder2, concentricCylinder3, concentricCylinder4)) - } - - val decorativeCylinder1 = run { - val t = Matrix.translate(0, 0, -0.75) * Matrix.scale(0.05, 1, 0.05) - val m = Material(Color.RED, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) - Cylinder(0, 0.3, true, t, m) - } - - val decorativeCylinder2 = run { - val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.15) * - Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) - val m = Material(Color.YELLOW, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) - Cylinder(0, 0.3, true, t, m) - } - - val decorativeCylinder3 = run { - val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.3) * - Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) - val m = Material(Color.GREEN, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) - Cylinder(0, 0.3, true, t, m) - } - - val decorativeCylinder4 = run { - val t = Matrix.translate(0, 0, -2.25) * Matrix.rotateY(-0.45) * - Matrix.translate(0, 0, 1.5) * Matrix.scale(0.05, 1, 0.05) - val m = Material(Color.CYAN, ambient = 0.1, diffuse = 0.9, specular = 0.9, shininess = 300.0) - Cylinder(0, 0.3, true, t, m) - } - - val glassCylinder = run { - val t = Matrix.translate(0, 0, -1.5) * Matrix.scale(0.33, 1, 0.33) - val m = Material(Color(0.25, 0, 0), diffuse = 0.1, specular = 0.9, - shininess = 300.0, reflectivity = 0.9, transparency = 0.9, refractiveIndex = 1.5) - Cylinder(0.0001, 0.5, true, t, m) - } - - val world = run { - val light = PointLight(Tuple.point(1, 6.9, -4.9)) - World(listOf(flooring, cylinder, - concentricCylinders, - decorativeCylinder1, decorativeCylinder2, decorativeCylinder3, decorativeCylinder4, - glassCylinder), light) - } - - val camera = run { - val from = Tuple.point(8, 3.5, -9) - val to = Tuple.point(0, 0.3, 0) - val t = from.viewTransformationFrom(to, Tuple.VY) - Camera(2400, 1200, PI / 10, t) - } - - val elapsed = measureTimeMillis { - val canvas = camera.render(world) - canvas.toPPMFile(File("output/ch13_groups.ppm")) - } - println("Time elapsed: ${elapsed / 1000.0} s") -} diff --git a/src/main/kotlin/apps/ch14_hexagon.kt b/src/main/kotlin/apps/ch14_hexagon.kt new file mode 100644 index 0000000..2e2ab5a --- /dev/null +++ b/src/main/kotlin/apps/ch14_hexagon.kt @@ -0,0 +1,40 @@ +package apps + +// By Sebastian Raaphorst, 2023. + +import light.PointLight +import math.Matrix +import math.Tuple +import scene.Camera +import scene.World +import shapes.Group +import java.io.File +import kotlin.math.PI +import kotlin.system.measureTimeMillis + +fun main() { + val hexagon = run { + val sides = (0 until 6).map { Matrix.rotateY(it * PI / 3) }.map { + side.withTransformation(it) + } + Group(Matrix.rotateX(-0.4363) * Matrix.rotateY(-PI / 18), children = sides) + } + + val world = run { + val light = PointLight(Tuple.point(0, 10, -5)) + World(listOf(hexagon), light) + } + + val camera = run { + val from = Tuple.point(0, 0, -5) + val to = Tuple.PZERO + val t = from.viewTransformationFrom(to, Tuple.VY) + Camera(1200, 600, 1, t) + } + + val elapsed = measureTimeMillis { + val canvas = camera.render(world) + canvas.toPPMFile(File("output/ch14_hexagon.ppm")) + } + println("Time elapsed: ${elapsed / 1000.0} s") +} \ No newline at end of file diff --git a/src/main/kotlin/apps/ch14_shared.kt b/src/main/kotlin/apps/ch14_shared.kt new file mode 100644 index 0000000..38fba0c --- /dev/null +++ b/src/main/kotlin/apps/ch14_shared.kt @@ -0,0 +1,20 @@ +package apps + +// By Sebastian Raaphorst, 2023. + +import math.Matrix +import shapes.Cylinder +import shapes.Group +import shapes.Sphere +import kotlin.math.PI + +// The shared side of a hexagon. +val side = run { + val corner = Sphere(Matrix.translate(0, 0, -1) * Matrix.scale(0.25, 0.25, 0.25)) + val edge = Cylinder( + 0, 1, false, + Matrix.translate(0, 0, -1) * Matrix.rotateY(-PI / 6) + * Matrix.rotateZ(-PI / 2) * Matrix.scale(0.25, 1, 0.25) + ) + Group(children = listOf(corner, edge)) +} \ No newline at end of file diff --git a/src/main/kotlin/apps/ch14_world.kt b/src/main/kotlin/apps/ch14_world.kt index 706d25a..3becd2e 100644 --- a/src/main/kotlin/apps/ch14_world.kt +++ b/src/main/kotlin/apps/ch14_world.kt @@ -19,14 +19,6 @@ fun main() { val transforms = listOf(0, PI / 3, 2 * PI / 3, PI, 4 * PI / 3, 5 * PI / 3) .map(Matrix::rotateY) - val leg = run { - val sphere = Sphere(Matrix.translate(0, 0, -1) * Matrix.scale(0.25, 0.25, 0.25)) - val cylinder = Cylinder(0, 1, false, - Matrix.translate(0, 0, -1) * Matrix.rotateY(-PI / 6) * - Matrix.rotateZ(-PI / 2) * Matrix.scale(0.25, 1, 0.25)) - Group(children = listOf(sphere, cylinder)) - } - val cap = run { val trans = Matrix.rotateX(-PI / 4 ) * Matrix.scale(0.24606, 1.37002, 0.24606) val cones = transforms.map { @@ -36,7 +28,7 @@ fun main() { } val wacky = run { - val legs = transforms.map(leg::withTransformation) + val legs = transforms.map(side::withTransformation) val cap1 = cap.withTransformation(Matrix.translate(0, 1, 0)) val cap2 = cap.withTransformation(Matrix.rotateX(PI) * Matrix.translate(0, 1, 0)) Group(children = legs + listOf(cap1, cap2)) diff --git a/src/main/kotlin/apps/ch14_world.yaml b/src/main/kotlin/apps/ch14_world.yaml deleted file mode 100644 index ad8053e..0000000 --- a/src/main/kotlin/apps/ch14_world.yaml +++ /dev/null @@ -1,205 +0,0 @@ -# ====================================================== -# group.yml -# -# This file describes the scene illustrated at the start -# of chapter 14, "Groups", in "The Ray Tracer -# Challenge" -# -# This scene description assumes: -# -# 1. Your ray tracer supports multiple light sources. -# If it does not, you can omit the extra light -# sources and bump up the existing light's intensity -# to [1, 1, 1]. -# 2. Child objects in a group inherit their default -# material from the parent group. If you haven't -# implemented this optional feature, you'll need -# arrange other means of texturing the child -# elements (or accept that all elements of the -# scene will be white). -# -# by Jamis Buck -# ====================================================== - -# ====================================================== -# the camera -# ====================================================== - -- add: camera - width: 300 - height: 100 - field-of-view: 0.9 - from: [0, 0, -9] - to: [0, 0, 0] - up: [0, 1, 0] - -# ====================================================== -# light sources -# ====================================================== - -- add: light - at: [ 10000, 10000, -10000 ] - intensity: [ 0.25, 0.25, 0.25 ] -- add: light - at: [ -10000, 10000, -10000 ] - intensity: [ 0.25, 0.25, 0.25 ] -- add: light - at: [ 10000, -10000, -10000 ] - intensity: [ 0.25, 0.25, 0.25 ] -- add: light - at: [ -10000, -10000, -10000 ] - intensity: [ 0.25, 0.25, 0.25 ] - -# ====================================================== -# These describe groups that will be reused within -# the scene. (You can think of these as functions that -# return a new instance of the given shape each time they -# are referenced.) -# ====================================================== - -- define: leg - value: - add: group - children: - - add: sphere - transform: - - [ scale, 0.25, 0.25, 0.25 ] - - [ translate, 0, 0, -1 ] - - add: cylinder - min: 0 - max: 1 - closed: false - transform: - - [ scale, 0.25, 1, 0.25 ] - - [ rotate-z, -1.5708 ] - - [ rotate-y, -0.5236 ] - - [ translate, 0, 0, -1 ] - -- define: cap - value: - add: group - children: - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - [ rotate-y, 1.0472 ] - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - [ rotate-y, 2.0944 ] - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - [ rotate-y, 3.1416 ] - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - [ rotate-y, 4.1888 ] - - add: cone - min: -1 - max: 0 - closed: false - transform: - - [ scale, 0.24606, 1.37002, 0.24606 ] - - [ rotate-x, -0.7854 ] - - [ rotate-y, 5.236 ] - -- define: wacky - value: - add: group - children: - - add: leg - - add: leg - transform: - - [ rotate-y, 1.0472 ] - - add: leg - transform: - - [ rotate-y, 2.0944 ] - - add: leg - transform: - - [ rotate-y, 3.1416 ] - - add: leg - transform: - - [ rotate-y, 4.1888 ] - - add: leg - transform: - - [ rotate-y, 5.236 ] - - add: cap - transform: - - [ translate, 0, 1, 0 ] - - add: cap - transform: - - [ translate, 0, 1, 0 ] - - [ rotate-x, 3.1416 ] - -# ====================================================== -# Construct the scene itself -# ====================================================== - -# a white backdrop -- add: plane - transform: - - [ rotate-x, 1.5708 ] - - [ translate, 0, 0, 100 ] - material: - color: [ 1, 1, 1 ] - ambient: 1 - diffuse: 0 - specular: 0 - -- add: wacky - transform: - - [ rotate-y, 0.1745 ] - - [ rotate-x, 0.4363 ] - - [ translate, -2.8, 0, 0 ] - material: - color: [ 0.9, 0.2, 0.4 ] - ambient: 0.2 - diffuse: 0.8 - specular: 0.7 - shininess: 20 - -- add: wacky - transform: - - [ rotate-y, 0.1745 ] - material: - color: [ 0.2, 0.9, 0.6 ] - ambient: 0.2 - diffuse: 0.8 - specular: 0.7 - shininess: 20 - -- add: wacky - transform: - - [ rotate-y, -0.1745 ] - - [ rotate-x, -0.4363 ] - - [ translate, 2.8, 0, 0 ] - material: - color: [ 0.2, 0.3, 1.0 ] - ambient: 0.2 - diffuse: 0.8 - specular: 0.7 - shininess: 20 diff --git a/src/main/kotlin/scene/Camera.kt b/src/main/kotlin/scene/Camera.kt index 449e6f8..cad7979 100644 --- a/src/main/kotlin/scene/Camera.kt +++ b/src/main/kotlin/scene/Camera.kt @@ -9,13 +9,16 @@ import output.Canvas import kotlin.math.tan // Map a 3D scene onto a 2D canvas. -data class Camera(val hSize: Int, val vSize: Int, val fov: Double, val transformation: Matrix = Matrix.I) { +class Camera(private val hSize: Int, + private val vSize: Int, + fov: Number, + internal val transformation: Matrix = Matrix.I) { init { if (!transformation.isTransformation()) throw IllegalArgumentException("Illegal camera transformation:\n${transformation.show()}") } - private val halfView = tan(fov / 2.0) + private val halfView = tan(fov.toDouble() / 2.0) private val aspect = hSize.toDouble() / vSize.toDouble() private val halfWidth = if (aspect >= 1) halfView else halfView * aspect private val halfHeight = if (aspect >= 1) halfView / aspect else halfView diff --git a/src/main/kotlin/shapes/Cappable.kt b/src/main/kotlin/shapes/Cappable.kt new file mode 100644 index 0000000..05af572 --- /dev/null +++ b/src/main/kotlin/shapes/Cappable.kt @@ -0,0 +1,69 @@ +package shapes + +// By Sebastian Raaphorst, 2023. + +import material.Material +import math.almostEquals +import math.Intersection +import math.Matrix +import math.Ray +import kotlin.math.sqrt + +// An object like a Cylinder or Cone that can be limited in length and cappable. +abstract class Cappable( + internal val minimum: Double = Double.NEGATIVE_INFINITY, + internal val maximum: Double = Double.POSITIVE_INFINITY, + internal val closed: Boolean = false, + transformation: Matrix = Matrix.I, + material: Material? = null, + castsShadow: Boolean = true, + parent: Shape? = null + ): Shape (transformation, material, castsShadow, parent) { + + private fun checkCap(rayLocal: Ray, radius: Double, t: Double): Boolean { + val x = rayLocal.origin.x + t * rayLocal.direction.x + val z = rayLocal.origin.z + t * rayLocal.direction.z + return x * x + z * z <= radius * radius + } + + internal fun intersectCaps(rayLocal: Ray, minRadius: Double, maxRadius: Double): List { + if (!closed or almostEquals(0, rayLocal.direction.y)) + return emptyList() + + val tMin = (minimum - rayLocal.origin.y) / rayLocal.direction.y + val tMax = (maximum - rayLocal.origin.y) / rayLocal.direction.y + + return when(Pair(checkCap(rayLocal, minRadius, tMin), checkCap(rayLocal, maxRadius, tMax))) { + Pair(true, true) -> listOf(Intersection(tMin, this), Intersection(tMax, this)) + Pair(true, false) -> listOf(Intersection(tMin, this)) + Pair(false, true) -> listOf(Intersection(tMax, this)) + else -> emptyList() + } + } + + internal fun intersectBody(rayLocal: Ray, a: Double, b: Double, c: Double): List { + val disc = b * b - 4 * a * c + + if (disc < 0) + emptyList() + + val sqrtDisc = sqrt(disc) + val (t0, t1) = run { + val t0Tmp = (-b - sqrtDisc) / (2 * a) + val t1Tmp = (-b + sqrtDisc) / (2 * a) + if (t0Tmp <= t1Tmp) Pair(t0Tmp, t1Tmp) else Pair(t1Tmp, t0Tmp) + } + + // Check for intersections with the body of the cone. + val y0 = rayLocal.origin.y + t0 * rayLocal.direction.y + val y1 = rayLocal.origin.y + t1 * rayLocal.direction.y + val t0Int = minimum < y0 && y0 < maximum + val t1Int = minimum < y1 && y1 < maximum + return when (Pair(t0Int, t1Int)) { + Pair(true, true) -> listOf(Intersection(t0, this), Intersection(t1, this)) + Pair(true, false) -> listOf(Intersection(t0, this)) + Pair(false, true) -> listOf(Intersection(t1, this)) + else -> emptyList() + } + } +} diff --git a/src/main/kotlin/shapes/Cone.kt b/src/main/kotlin/shapes/Cone.kt index a2481d2..efa209f 100644 --- a/src/main/kotlin/shapes/Cone.kt +++ b/src/main/kotlin/shapes/Cone.kt @@ -11,15 +11,12 @@ import kotlin.math.sqrt class Cone(minimum: Number = Double.NEGATIVE_INFINITY, maximum: Number = Double.POSITIVE_INFINITY, - val closed: Boolean = false, + closed: Boolean = false, transformation: Matrix = Matrix.I, material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): - Shape(transformation, material, castsShadow, parent) { - - val minimum = minimum.toDouble() - val maximum = maximum.toDouble() + Cappable(minimum.toDouble(), maximum.toDouble(), closed, transformation, material, castsShadow, parent) { // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = @@ -43,62 +40,14 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY, val xs1 = if (almostEquals(0.0, a)) { if (almostEquals(0.0, b)) emptyList() else listOf(Intersection(-c / (2 * b), this)) - } - else { - val disc = b * b - 4 * a * c - - if (disc < 0) - emptyList() - - val sqrtDisc = sqrt(disc) - val (t0, t1) = run { - val t0Tmp = (-b - sqrtDisc) / (2 * a) - val t1Tmp = (-b + sqrtDisc) / (2 * a) - if (t0Tmp <= t1Tmp) Pair(t0Tmp, t1Tmp) else Pair(t1Tmp, t0Tmp) - } - - // Check for intersections with the body of the cylinder. - val y0 = rayLocal.origin.y + t0 * rayLocal.direction.y - val y1 = rayLocal.origin.y + t1 * rayLocal.direction.y - val t0Int = minimum < y0 && y0 < maximum - val t1Int = minimum < y1 && y1 < maximum - when (Pair(t0Int, t1Int)) { - Pair(true, true) -> listOf(Intersection(t0, this), Intersection(t1, this)) - Pair(true, false) -> listOf(Intersection(t0, this)) - Pair(false, true) -> listOf(Intersection(t1, this)) - else -> emptyList() - } - } + } else intersectBody(rayLocal, a, b, c) // Check for intersections with the caps of the cylinder, if appropriate. - val xs2 = intersectCaps(rayLocal) + val xs2 = intersectCaps(rayLocal, minimum, maximum) return xs1 + xs2 } - // Helper function to check to see if intersection at t is within a radius of 1 - // (i.e. radius of cylinder) from the y-axis. - private fun checkCap(ray: Ray, t: Double, y: Double): Boolean { - val x = ray.origin.x + t * ray.direction.x - val z = ray.origin.z + t * ray.direction.z - return x * x + z * z <= y * y - } - - // Return any intersections with the caps of a closed cylinder. - private fun intersectCaps(ray: Ray): List { - if (!closed or almostEquals(0, ray.direction.y)) - return emptyList() - - val tMin = (minimum - ray.origin.y) / ray.direction.y - val tMax = (maximum - ray.origin.y) / ray.direction.y - return when(Pair(checkCap(ray, tMin, minimum), checkCap(ray, tMax, maximum))) { - Pair(true, true) -> listOf(Intersection(tMin, this), Intersection(tMax, this)) - Pair(true, false) -> listOf(Intersection(tMin, this)) - Pair(false, true) -> listOf(Intersection(tMax, this)) - else -> emptyList() - } - } - override fun localNormalAt(localPoint: Tuple): Tuple { val dist = localPoint.x * localPoint.x + localPoint.z * localPoint.z diff --git a/src/main/kotlin/shapes/Cylinder.kt b/src/main/kotlin/shapes/Cylinder.kt index 4632cd1..09a3d1c 100644 --- a/src/main/kotlin/shapes/Cylinder.kt +++ b/src/main/kotlin/shapes/Cylinder.kt @@ -5,19 +5,15 @@ package shapes import material.Material import math.* import math.Intersection -import kotlin.math.sqrt class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, maximum: Number = Double.POSITIVE_INFINITY, - val closed: Boolean = false, + closed: Boolean = false, transformation: Matrix = Matrix.I, material: Material? = null, castsShadow: Boolean = true, parent: Shape? = null): - Shape(transformation, material, castsShadow, parent) { - - val minimum = minimum.toDouble() - val maximum = maximum.toDouble() + Cappable(minimum.toDouble(), maximum.toDouble(), closed, transformation, material, castsShadow, parent) { // Note due to Kotlin semantics, we have to use objMaterial here. override fun withParent(parent: Shape?): Shape = @@ -31,64 +27,18 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY, // Only check for intersections with the body of the cylinder if a is not near 0. val xs1 = if (almostEquals(0.0, a)) emptyList() - else { - + else { val b = 2 * rayLocal.origin.x * rayLocal.direction.x + 2 * rayLocal.origin.z * rayLocal.direction.z val c = rayLocal.origin.x * rayLocal.origin.x + rayLocal.origin.z * rayLocal.origin.z - 1 - val disc = b * b - 4 * a * c - - if (disc < 0) - emptyList() - - val sqrtDisc = sqrt(disc) - val (t0, t1) = run { - val t0Tmp = (-b - sqrtDisc) / (2 * a) - val t1Tmp = (-b + sqrtDisc) / (2 * a) - if (t0Tmp <= t1Tmp) Pair(t0Tmp, t1Tmp) else Pair(t1Tmp, t0Tmp) - } - - // Check for intersections with the body of the cylinder. - val y0 = rayLocal.origin.y + t0 * rayLocal.direction.y - val y1 = rayLocal.origin.y + t1 * rayLocal.direction.y - val t0Int = minimum < y0 && y0 < maximum - val t1Int = minimum < y1 && y1 < maximum - when (Pair(t0Int, t1Int)) { - Pair(true, true) -> listOf(Intersection(t0, this), Intersection(t1, this)) - Pair(true, false) -> listOf(Intersection(t0, this)) - Pair(false, true) -> listOf(Intersection(t1, this)) - else -> emptyList() - } + intersectBody(rayLocal, a, b, c) } // Check for intersections with the caps of the cylinder, if appropriate. - val xs2 = intersectCaps(rayLocal) + val xs2 = intersectCaps(rayLocal, 1.0, 1.0) return xs1 + xs2 } - // Helper function to check to see if intersection at t is within a radius of 1 - // (i.e. radius of cylinder) from the y-axis. - private fun checkCap(ray: Ray, t: Double): Boolean { - val x = ray.origin.x + t * ray.direction.x - val z = ray.origin.z + t * ray.direction.z - return x * x + z * z <= 1 - } - - // Return any intersections with the caps of a closed cylinder. - private fun intersectCaps(ray: Ray): List { - if (!closed or almostEquals(0, ray.direction.y)) - return emptyList() - - val tMin = (minimum - ray.origin.y) / ray.direction.y - val tMax = (maximum - ray.origin.y) / ray.direction.y - return when(Pair(checkCap(ray, tMin), checkCap(ray, tMax))) { - Pair(true, true) -> listOf(Intersection(tMin, this), Intersection(tMax, this)) - Pair(true, false) -> listOf(Intersection(tMin, this)) - Pair(false, true) -> listOf(Intersection(tMax, this)) - else -> emptyList() - } - } - override fun localNormalAt(localPoint: Tuple): Tuple { val dist = localPoint.x * localPoint.x + localPoint.z * localPoint.z