diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12a5be9..d8254d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,5 +26,5 @@ jobs: arch: x64 - uses: julia-actions/cache@v2 - name: Install pkgs dependencies - run: julia --project=@. -e 'using Pkg; Pkg.test("RayCaster", coverage=true)' + run: julia --project=@. -e 'using Pkg; Pkg.test("Raycore", coverage=true)' - uses: julia-actions/julia-runtest@v1 diff --git a/Project.toml b/Project.toml index be372db..4d03889 100644 --- a/Project.toml +++ b/Project.toml @@ -1,4 +1,4 @@ -name = "RayCaster" +name = "Raycore" uuid = "afc56b53-c9a9-482a-a956-d1d800e05559" authors = ["Anton Smirnov ", "Simon Danisch Float32(sum(view(viewf_matrix, :, i))), 1:length(bvh.primitives)) world_mesh = GeometryBasics.Mesh(bvh) N = length(world_mesh.faces) @@ -67,11 +67,11 @@ end using KernelAbstractions, Atomix function random_scatter_kernel!(bvh, triangle, u, v, normal) - point = RayCaster.random_triangle_point(triangle) + point = Raycore.random_triangle_point(triangle) o = point .+ (normal .* 0.01f0) # Offset so it doesn't self intersect - dir = RayCaster.random_hemisphere_uniform(normal, u, v) - ray = RayCaster.Ray(; o=o, d=dir) - hit, prim, _ = RayCaster.intersect!(bvh, ray) + dir = Raycore.random_hemisphere_uniform(normal, u, v) + ray = Raycore.Ray(; o=o, d=dir) + hit, prim, _ = Raycore.closest_hit(bvh, ray) return hit, prim end @@ -109,10 +109,10 @@ using AMDGPU prim_info = map(bvh.primitives) do triangle n = GB.orthogonal_vector(Vec3f, GB.Triangle(triangle.vertices...)) normal = normalize(Vec3f(n)) - u, v = RayCaster.get_orthogonal_basis(normal) + u, v = Raycore.get_orthogonal_basis(normal) return triangle, u, v, normal end -bvh_gpu = RayCaster.to_gpu(ROCArray, bvh) +bvh_gpu = Raycore.to_gpu(ROCArray, bvh) result_gpu = ROCArray(result) prim_info_gpu = ROCArray(prim_info) @time begin @@ -158,11 +158,11 @@ final_rays / 10^6 prim_info = map(bvh.primitives) do triangle n = GB.orthogonal_vector(Vec3f, GB.Triangle(triangle.vertices...)) normal = normalize(Vec3f(n)) - u, v = RayCaster.get_orthogonal_basis(normal) + u, v = Raycore.get_orthogonal_basis(normal) return triangle, u, v, normal end -bvh_gpu = RayCaster.to_gpu(ROCArray, bvh) +bvh_gpu = Raycore.to_gpu(ROCArray, bvh) result_gpu = ROCArray(result) prim_info_gpu = ROCArray(prim_info) @time begin diff --git a/docs/make.jl b/docs/make.jl index 92f06a2..97e2ff0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,19 +1,28 @@ using Documenter -using RayCaster +using Raycore using Bonito +using BonitoBook makedocs(; - modules = [RayCaster], - sitename = "RayCaster", + modules = [Raycore], + sitename = "Raycore", clean = false, - format=Documenter.HTML(prettyurls=false, size_threshold=300000), + format=Documenter.HTML(; + prettyurls=false, + size_threshold=3000000, + example_size_threshold=3000000 + ), authors = "Anton Smirnov, Simon Danisch and contributors", pages = [ "Home" => "index.md", + "Tutorials" => [ + "Ray Tracing Tutorial" => "raytracing_tutorial.md", + "BVH Hit Tests" => "bvh_hit_tests.md", + ], ], ) deploydocs(; - repo = "github.com/JuliaGeometry/RayCaster.jl", + repo = "github.com/JuliaGeometry/Raycore.jl", push_preview = true, ) diff --git a/docs/src/.bvh_hit_tests-bbook/meta.toml b/docs/src/.bvh_hit_tests-bbook/meta.toml new file mode 100644 index 0000000..9f7a875 --- /dev/null +++ b/docs/src/.bvh_hit_tests-bbook/meta.toml @@ -0,0 +1 @@ +version = "0.1.0" diff --git a/docs/src/.raytracing_tutorial-bbook/meta.toml b/docs/src/.raytracing_tutorial-bbook/meta.toml new file mode 100644 index 0000000..9f7a875 --- /dev/null +++ b/docs/src/.raytracing_tutorial-bbook/meta.toml @@ -0,0 +1 @@ +version = "0.1.0" diff --git a/docs/src/.raytracing_tutorial_content-bbook/.versions/raytracing_tutorial_content-2025-10-28_201849.md b/docs/src/.raytracing_tutorial_content-bbook/.versions/raytracing_tutorial_content-2025-10-28_201849.md new file mode 100644 index 0000000..a3637ce --- /dev/null +++ b/docs/src/.raytracing_tutorial_content-bbook/.versions/raytracing_tutorial_content-2025-10-28_201849.md @@ -0,0 +1,603 @@ +# Ray Tracing with Raycore: Building a Real Ray Tracer + +In this tutorial, we'll build a simple but complete ray tracer from scratch using Raycore. We'll start with the absolute basics and progressively add features until we have a ray tracer that produces beautiful images with shadows and materials. + +By the end, you'll have a working ray tracer that can render complex scenes! + +## Setup + +```julia (editor=true, logging=false, output=true) +using Raycore, GeometryBasics, LinearAlgebra +using Colors, ImageShow +using Makie # For loading assets +using BenchmarkTools +``` +**Ready to go!** We have: + + * `Raycore` for fast ray-triangle intersections + * `GeometryBasics` for geometry primitives + * `Colors` and `ImageShow` for displaying rendered images + +## Part 1: Our Scene - A Playful Cat + +Let's create a fun scene that we'll use throughout this tutorial. We'll load a cat model and place it in a simple room. + +```julia (editor=true, logging=false, output=true) +# Load the cat model and rotate it to face the camera +cat_mesh = Makie.loadasset("cat.obj") +# Rotate 150 degrees around Y axis so cat faces camera at an angle +angle = deg2rad(150f0) +rotation = Makie.Quaternionf(0, sin(angle/2), 0, cos(angle/2)) +rotated_coords = [rotation * Point3f(v) for v in coordinates(cat_mesh)] + +# Get bounding box and translate cat to sit on the floor +cat_bbox = Rect3f(rotated_coords) +floor_y = -1.5f0 +cat_offset = Vec3f(0, floor_y - cat_bbox.origin[2], 0) # Translate so bottom sits on floor + +cat_mesh = GeometryBasics.normal_mesh( + [v + cat_offset for v in rotated_coords], + faces(cat_mesh) +) + +# Create a simple room: floor, back wall, and side wall +floor = normal_mesh(Rect3f(Vec3f(-5, -1.5, -2), Vec3f(10, 0.01, 10))) +back_wall = normal_mesh(Rect3f(Vec3f(-5, -1.5, 8), Vec3f(10, 5, 0.01))) +left_wall = normal_mesh(Rect3f(Vec3f(-5, -1.5, -2), Vec3f(0.01, 5, 10))) + +# Add a couple of spheres for visual interest (also on the floor) +sphere1 = Tesselation(Sphere(Point3f(-2, -1.5 + 0.8, 2), 0.8f0), 64) +sphere2 = Tesselation(Sphere(Point3f(2, -1.5 + 0.6, 1), 0.6f0), 64) + +# Build our BVH acceleration structure +scene_geometry = [cat_mesh, floor, back_wall, left_wall, sphere1, sphere2] +bvh = Raycore.BVHAccel(scene_geometry) +``` +**Scene created!** + + * Cat model with triangulated geometry + * Room geometry: 3 walls + * 2 decorative spheres + * BVH built for fast ray traversal + +## Part 2: The Simplest Ray Tracer - Binary Hit Detection + +Let's start super simple: for each pixel, we shoot a ray and color it based on whether we hit something or not. + +```julia (editor=true, logging=false, output=true) +# Trace helper - runs a callback for each pixel +function trace(f, bvh; width=700, height=300, camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + # Precompute camera parameters + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + for y in 1:height, x in 1:width + # Generate camera ray + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + # Ray-scene intersection + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(bvh, ray) + + # Let the callback decide the color (pass sky_color for misses) + img[y, x] = hit_found ? f(triangle, distance, bary_coords, ray) : sky_color + end + + return img +end + +# Binary kernel - white if hit +binary_kernel(triangle, distance, bary_coords, ray) = RGB(1.0f0, 1.0f0, 1.0f0) + +trace(binary_kernel, bvh, sky_color=RGB(0.0f0, 0.0f0, 0.0f0)) +``` +**Our first render!** Pure silhouette - you can see the cat and spheres. + +## Part 3: Adding Depth - Distance-Based Shading + +Let's make it more interesting by coloring based on distance (depth map). + +```julia (editor=true, logging=false, output=true) +function depth_kernel(triangle, distance, bary_coords, ray) + # Map distance to grayscale (closer = brighter) + normalized_depth = clamp(1.0f0 - (distance - 2.0f0) / 8.0f0, 0.0f0, 1.0f0) + RGB(normalized_depth, normalized_depth, normalized_depth) +end + +trace(depth_kernel, bvh) +``` +**Depth perception!** Now we can see the 3D structure - closer objects are brighter. + +## Part 4: Surface Normals - The Foundation of Lighting + +To do proper lighting, we need surface normals. Let's compute and visualize them. + +```julia (editor=true, logging=false, output=true) +# Helper to interpolate normals using barycentric coordinates +function compute_normal(triangle, bary_coords) + n1, n2, n3 = triangle.normals + u, v, w = bary_coords + normalize(Vec3f(u * n1 + v * n2 + w * n3)) +end + +function normal_kernel(triangle, distance, bary_coords, ray) + normal = compute_normal(triangle, bary_coords) + # Map normal components [-1,1] to color [0,1] + RGB((normal .+ 1.0f0) ./ 2.0f0...) +end + +trace(normal_kernel, bvh) +``` +**Surface normals visualized!** Each color channel represents a normal component: + + * Red = X direction + * Green = Y direction + * Blue = Z direction + +## Part 5: Basic Lighting - Diffuse Shading + +Now we can add a light source and compute simple diffuse (Lambertian) shading! + +```julia (editor=true, logging=false, output=true) +light_pos = Point3f(3, 4, -2) +light_intensity = 50.0f0 + +function diffuse_kernel(triangle, distance, bary_coords, ray) + # Compute hit point and normal + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Light direction and distance + light_dir = light_pos - hit_point + light_distance = norm(light_dir) + light_dir = normalize(light_dir) + + # Diffuse shading (Lambertian) + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Light attenuation (inverse square law) + attenuation = light_intensity / (light_distance * light_distance) + color = diffuse * attenuation + + RGB(color, color, color) +end + +trace(diffuse_kernel, bvh) +``` +**Let there be light!** Our scene now has proper shading based on surface orientation. + +## Part 6: Adding Shadows - Shadow Rays + +Time to add realism with shadows using Raycore's `any_hit` for fast occlusion testing. + +```julia (editor=true, logging=false, output=true) +ambient = 0.1f0 # Ambient lighting to prevent pure black shadows + +function shadow_kernel(triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Light direction + light_dir = light_pos - hit_point + light_distance = norm(light_dir) + light_dir = normalize(light_dir) + + # Diffuse shading + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow ray - offset slightly to avoid self-intersection + shadow_ray_origin = hit_point + normal * 0.001f0 + shadow_ray = Raycore.Ray(o=shadow_ray_origin, d=light_dir) + + # Check if path to light is blocked + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + # Final color + color = if in_shadow + ambient # Only ambient in shadow + else + attenuation = light_intensity / (light_distance * light_distance) + ambient + diffuse * attenuation + end + + RGB(color, color, color) +end + +trace(shadow_kernel, bvh) +``` +**Shadows!** Notice how objects cast shadows on each other, adding depth and realism. + +## Part 7: Multiple Lights + +Let's add multiple lights to make the scene more interesting! We'll define a RenderContext to hold lights and materials: + +```julia (editor=true, logging=false, output=true) +# Define a simple point light structure +struct PointLight + position::Point3f + intensity::Float32 + color::RGB{Float32} +end + +# Material structure (for later use) +struct Material + base_color::RGB{Float32} + metallic::Float32 + roughness::Float32 +end + +# Render context holds all scene data +struct RenderContext + bvh::Raycore.BVHAccel + lights::Vector{PointLight} + materials::Vector{Material} + ambient::Float32 +end + +# Create multiple lights +lights = [ + PointLight(Point3f(3, 4, -2), 50.0f0, RGB(1.0f0, 0.9f0, 0.8f0)), # Warm main light + PointLight(Point3f(-3, 2, 0), 20.0f0, RGB(0.7f0, 0.8f0, 1.0f0)), # Cool fill light + PointLight(Point3f(0, 5, 5), 15.0f0, RGB(1.0f0, 1.0f0, 1.0f0)) # White back light +] + +# Materials (will use these in Part 8) +materials = [ + Material(RGB(0.8f0, 0.6f0, 0.4f0), 0.0f0, 0.8f0), # 1: Cat + Material(RGB(0.3f0, 0.5f0, 0.3f0), 0.0f0, 0.9f0), # 2: Floor + Material(RGB(0.7f0, 0.7f0, 0.8f0), 0.0f0, 0.8f0), # 3: Back wall + Material(RGB(0.8f0, 0.7f0, 0.7f0), 0.0f0, 0.8f0), # 4: Left wall + Material(RGB(0.95f0, 0.64f0, 0.54f0), 1.0f0, 0.1f0), # 5: Sphere 1 - metallic + Material(RGB(0.8f0, 0.8f0, 0.9f0), 1.0f0, 0.0f0) # 6: Sphere 2 - mirror +] + +# Create render context +ctx = RenderContext(bvh, lights, materials, 0.1f0) +``` +Now we need a new trace function that works with RenderContext: + +```julia (editor=true, logging=false, output=true) +# Trace with RenderContext +function trace_ctx(f, ctx::RenderContext; width=700, height=300,camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + for y in 1:height, x in 1:width + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + img[y, x] = hit_found ? f(ctx, triangle, distance, bary_coords, ray) : sky_color + end + + return img +end + +function multi_light_kernel(ctx, triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Start with ambient (grayscale) + total_color = Vec3f(ctx.ambient, ctx.ambient, ctx.ambient) + + # Accumulate contribution from each light + for light in ctx.lights + light_vec = light.position - hit_point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + shadow_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += light_col * (diffuse * attenuation) + end + end + + RGB{Float32}(total_color...) +end + +trace_ctx(multi_light_kernel, ctx) +``` +**Multiple lights!** The scene now has three different colored lights creating a more dynamic lighting environment. + +## Part 8: Colored Materials with Multiple Lights + +Now let's combine materials with our multiple lights! + +```julia (editor=true, logging=false, output=true) +function material_multi_light_kernel(ctx, triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Get material from context + mat = ctx.materials[triangle.material_idx] + base_color = Vec3f(mat.base_color.r, mat.base_color.g, mat.base_color.b) + + # Start with ambient + total_color = base_color * ctx.ambient + + # Accumulate contribution from each light + for light in ctx.lights + light_vec = light.position - hit_point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow test + shadow_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += base_color .* light_col * (diffuse * attenuation) + end + end + + RGB{Float32}(total_color...) +end + +trace_ctx(material_multi_light_kernel, ctx) +``` +**Colored materials!** + + * Orange/tan cat + * Green floor + * Light blue back wall + * Pink side wall + * Red and blue spheres + +## Part 9: Reflective Materials - Mirrors and Metals + +The materials we defined in Part 7 already have metallic and roughness properties. Let's use them for reflections! + +```julia (editor=true, logging=false, output=true) +# Helper: compute direct lighting with multiple lights +function compute_multi_light(ctx, point, normal, mat) + base_color = Vec3f(mat.base_color.r, mat.base_color.g, mat.base_color.b) + + # Start with ambient + total_color = base_color * ctx.ambient + + for light in ctx.lights + light_vec = light.position - point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow test + shadow_ray = Raycore.Ray(o=point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += base_color .* light_col * (diffuse * attenuation) + end + end + + return RGB{Float32}(total_color...) +end + +function reflective_kernel(ctx, triangle, distance, bary_coords, ray, sky_color) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + mat = ctx.materials[triangle.material_idx] + + # Compute direct lighting (diffuse component) + direct_color = compute_multi_light(ctx, hit_point, normal, mat) + + # Add reflection for metallic materials + if mat.metallic > 0.0f0 + # Compute reflection direction: reflect outgoing direction about normal + # Note: ray.d points toward surface, but reflect() expects outgoing direction + wo = -ray.d # outgoing direction (away from surface) + reflect_dir = Raycore.reflect(wo, normal) + + # Add roughness by perturbing reflection direction + if mat.roughness > 0.0f0 + # Simple roughness: add random offset in tangent space + random_offset = (rand(Vec3f) .* 2.0f0 .- 1.0f0) * mat.roughness + reflect_dir = normalize(reflect_dir + random_offset) + end + + # Cast reflection ray (offset to avoid self-intersection) + reflect_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=reflect_dir) + refl_hit, refl_tri, refl_dist, refl_bary = Raycore.closest_hit(ctx.bvh, reflect_ray) + + # Get reflection color + reflection_color = if refl_hit + refl_point = reflect_ray.o + reflect_ray.d * refl_dist + refl_normal = compute_normal(refl_tri, refl_bary) + refl_mat = ctx.materials[refl_tri.material_idx] + + # Compute lighting for reflected surface + compute_multi_light(ctx, refl_point, refl_normal, refl_mat) + else + sky_color + end + + # Blend between diffuse and reflection based on metallic parameter + return direct_color * (1.0f0 - mat.metallic) + reflection_color * mat.metallic + else + # Pure diffuse material + return direct_color + end +end + +trace_ctx(ctx) do ctx, triangle, distance, bary_coords, ray + reflective_kernel(ctx, triangle, distance, bary_coords, ray, RGB(0.5f0, 0.7f0, 1.0f0)) +end +``` +**Reflective materials!** The spheres now have metallic properties: + + * One smooth copper-colored metal with slight roughness + * One perfect mirror reflecting the scene + +Notice how reflections capture both the scene geometry and lighting! + +## Part 10: Multi-threading for Performance + +Let's add multi-threading to make our ray tracer much faster! + +```julia (editor=true, logging=false, output=true) + +``` +```julia (editor=true, logging=false, output=true) +using BenchmarkTools +function trace_ctx_threaded(f, ctx::RenderContext; width=400, height=300, camera_pos=Point3f(0, 1, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + Threads.@threads for y in 1:height + for x in 1:width + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + img[y, x] = hit_found ? f(ctx, triangle, distance, bary_coords, ray) : sky_color + end + end + + return img +end + +# Benchmark single-threaded vs multi-threaded +b1 = @belapsed trace_ctx(material_multi_light_kernel, ctx, width=800, height=600); + +b2 = @belapsed trace_ctx_threaded(material_multi_light_kernel, ctx, width=800, height=600); +md""" +Threads: $(Threads.nthreads()) + +Single: $(b1) + +Multi: $(b2) +""" +``` +**Performance boost with threading!** The speedup should be close to the number of CPU cores. + +Notice how we can reuse the same kernel function with both `trace_ctx()` and `trace_ctx_threaded()` - this is great for composability! + +## Part 11: Multi-Sampling for Anti-Aliasing + +Let's add multiple samples per pixel with jittered camera rays for smooth anti-aliasing: + +```julia (editor=true, logging=false, output=true) +function trace_ctx_sampled(f, ctx::RenderContext; + width=700, height=300, + camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0), + samples=4) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + pixel_size = 1.0f0 / width + + Threads.@threads for y in 1:height + for x in 1:width + # Accumulate multiple samples per pixel using Vec3f for math + color_sum = Vec3f(0.0f0, 0.0f0, 0.0f0) + + for _ in 1:samples + jitter_x = (rand(Float32) - 0.5f0) * pixel_size + jitter_y = (rand(Float32) - 0.5f0) * pixel_size + + ndc_x = (2.0f0 * (x + jitter_x) / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * (y + jitter_y) / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + + color = if hit_found + result = f(ctx, triangle, distance, bary_coords, ray) + Vec3f(result.r, result.g, result.b) + else + Vec3f(sky_color.r, sky_color.g, sky_color.b) + end + + color_sum += color + end + + # Average samples and convert back to RGB + avg = color_sum / samples + img[y, x] = RGB{Float32}(avg...) + end + end + + return img +end + +# Render with 16 samples per pixel for smooth anti-aliasing +@time trace_ctx_sampled(ctx, samples=64) do ctx, triangle, distance, bary_coords, ray + reflective_kernel(ctx, triangle, distance, bary_coords, ray, RGB(0.5f0, 0.7f0, 1.0f0)) +end +``` +**Anti-aliased render!** 32 samples per pixel with jittered camera rays eliminate jagged edges. + +## Summary - What We Built + +We created a complete ray tracer that includes: + +### Features Implemented + +1. **Camera system** - Perspective projection with configurable FOV +2. **Ray-scene intersection** - Using Raycore's BVH for fast traversal +3. **Surface normals** - Smooth shading from vertex normals +4. **Diffuse lighting** - Lambertian shading with distance attenuation +5. **Hard shadows** - Using `any_hit` for efficient occlusion testing +6. **Simple materials** - Per-object color assignment +7. **Multi-threading** - Parallel rendering across CPU cores +8. **Callback-based API** - Flexible `trace()` function for experimentation + +### Next Steps + +To make this into a full path tracer (like `Trace`), you would add: + + * **Recursive ray tracing** - Reflections and refractions + * **Multiple light sources** - Area lights, environment lighting + * **Advanced materials** - Specular, glossy, transparent + * **Sampling** - Multiple samples per pixel for anti-aliasing + * **Better normal interpolation** - Proper barycentric interpolation + * **GPU support** - Using KernelAbstractions.jl + +The `Trace` package implements all of these features and more! + +### Key Raycore Functions Used + + * `Raycore.BVHAccel(meshes)` - Build acceleration structure + * `Raycore.Ray(o=origin, d=direction)` - Create ray + * `Raycore.closest_hit(bvh, ray)` - Find nearest intersection + * `Raycore.any_hit(bvh, ray)` - Test for any intersection (fast!) + * `Raycore.vertices(triangle)` - Get triangle vertex positions + * `Raycore.normals(triangle)` - Get triangle vertex normals + +Happy ray tracing! + diff --git a/docs/src/.raytracing_tutorial_content-bbook/meta.toml b/docs/src/.raytracing_tutorial_content-bbook/meta.toml new file mode 100644 index 0000000..9f7a875 --- /dev/null +++ b/docs/src/.raytracing_tutorial_content-bbook/meta.toml @@ -0,0 +1 @@ +version = "0.1.0" diff --git a/docs/src/bvh_hit_tests.md b/docs/src/bvh_hit_tests.md new file mode 100644 index 0000000..b40b287 --- /dev/null +++ b/docs/src/bvh_hit_tests.md @@ -0,0 +1,224 @@ +# BVH Hit Testing: `closest_hit` vs `any_hit` + +This document tests and visualizes the difference between `closest_hit` and `any_hit` functions in the BVH implementation using the new `RayIntersectionSession` API. + +## Test Setup + +```julia (editor=true, logging=false, output=true) +using Raycore, GeometryBasics, LinearAlgebra +using WGLMakie +using Test +using Bonito + +# Create a simple test scene with multiple overlapping primitives +function create_test_scene() + # Three spheres at different distances along the Z-axis + sphere1 = Tesselation(Sphere(Point3f(0, 0, 5), 1.0f0), 20) # Furthest + sphere2 = Tesselation(Sphere(Point3f(0, 0, 3), 1.0f0), 20) # Middle + sphere3 = Tesselation(Sphere(Point3f(0, 0, 1), 1.0f0), 20) # Closest + + bvh = Raycore.BVHAccel([sphere1, sphere2, sphere3]) + return bvh +end + +bvh = create_test_scene() + +DOM.div("✓ Created BVH with $(length(bvh.primitives)) triangles from 3 spheres") +``` +## Test 1: Single Ray Through Center + +Test a ray through the center that passes through all three spheres. + +```julia (editor=true, logging=false, output=true) +# Create a ray with slight offset to avoid hitting triangle vertices exactly +test_ray = Raycore.Ray(o=Point3f(0.1, 0.1, -5), d=Vec3f(0, 0, 1)) + +# Create session with closest_hit +session_closest = RayIntersectionSession(Raycore.closest_hit, [test_ray], bvh) + +# Create session with any_hit for comparison +session_any = RayIntersectionSession(Raycore.any_hit, [test_ray], bvh) + +fig = Figure() + +# Left: closest_hit visualization +plot(fig[1, 1], session_closest) +plot(fig[1, 2], session_any) +Label(fig[0, 1], "closest_hit", fontsize=20, font=:bold, tellwidth=false) +Label(fig[0, 2], "any_hit", fontsize=20, font=:bold, tellwidth=false) + +fig +``` +## Visualization: Single Ray with Makie Recipe + +```julia (editor=true, logging=false, output=true) +# Create a ray with slight offset to avoid hitting triangle vertices exactly +test_ray = Raycore.Ray(o=Point3f(0.1, 0.1, 10), d=Vec3f(0, 0, -1)) + +# Create session with closest_hit +session_closest = RayIntersectionSession(Raycore.closest_hit, [test_ray], bvh) + +# Create session with any_hit for comparison +session_any = RayIntersectionSession(Raycore.any_hit, [test_ray], bvh) + +fig = Figure() +# Left: closest_hit visualization +plot(fig[1, 1], session_closest) +plot(fig[1, 2], session_any) +Label(fig[0, 1], "closest_hit", tellwidth=false) +Label(fig[0, 2], "any_hit", tellwidth=false) + +fig +``` +## Test 2: Multiple Rays from Different Positions + +Test multiple rays to ensure both functions work correctly. + +```julia (editor=true, logging=false, output=true) +# Test rays from different angles (with slight offset to avoid vertex hits) +test_positions = [ + Point3f(0.1, 0.1, -5), # Center + Point3f(0.5, 0.1, -5), # Right offset + Point3f(0.1, 0.5, -5), # Top offset + Point3f(-0.5, 0.1, -5), # Left offset +] + +# Create rays +rays = [Raycore.Ray(o=pos, d=Vec3f(0, 0, 1)) for pos in test_positions] + +# Create session +session_multi = RayIntersectionSession(Raycore.closest_hit, rays, bvh) +fig2 = Figure() +ax = LScene(fig2[1, 1]) + +# Use different colors for each ray +ray_colors = [:purple, :orange, :cyan, :magenta] + +plot!(ax, session_multi; + show_bvh=true, + bvh_alpha=0.3, + ray_colors=ray_colors, + hit_color=:green, + show_hit_points=true, + hit_markersize=0.15, + show_labels=false) + +fig2 +``` +## Visualization: Multiple Rays + +## Test 4: Difference Between any*hit and closest*hit + +Demonstrate that `any_hit` can return different results than `closest_hit`. + +```julia (editor=true, logging=false, output=true) +# Create a complex scene with overlapping geometry +# This creates a BVH where traversal order can differ from distance order +using Random +Random.seed!(123) + +complex_spheres = [] + +# Add some large overlapping spheres +push!(complex_spheres, Tesselation(Sphere(Point3f(0, 0, 10), 3.0f0), 20)) +push!(complex_spheres, Tesselation(Sphere(Point3f(0.5, 0, 5), 0.5f0), 15)) +push!(complex_spheres, Tesselation(Sphere(Point3f(-0.5, 0, 15), 1.5f0), 18)) + +# Add many small spheres to create complex BVH structure +for i in 1:30 + x = randn() * 5 + y = randn() * 5 + z = rand(8.0:0.5:12.0) + r = 0.3 + rand() * 0.5 + push!(complex_spheres, Tesselation(Sphere(Point3f(x, y, z), r), 8)) +end + +complex_bvh = Raycore.BVHAccel(complex_spheres) + +# Test rays to find cases where any_hit differs from closest_hit +test_rays = map(1:100) do i + x = (i % 10) * 0.4 - 2.0 + y = div(i-1, 10) * 0.4 - 2.0 + Raycore.Ray(o=Point3f(x, y, -5), d=Vec3f(0, 0, 1)) +end + +session_closest = RayIntersectionSession(Raycore.closest_hit, test_rays, complex_bvh) +session_any = RayIntersectionSession(Raycore.any_hit, test_rays, complex_bvh) +fig = Figure() +# Left: closest_hit visualization +plot(fig[1, 1], session_closest) +plot(fig[1, 2], session_any) +Label(fig[0, 1], "closest_hit", tellwidth=false) +Label(fig[0, 2], "any_hit", tellwidth=false) + +fig + +``` +**Key Findings:** + + * `any_hit` exits on the **first** intersection during BVH traversal (uses `intersect`, doesn't update ray) + * `closest_hit` continues searching and updates ray's `t_max` (uses `intersect_p!`) + * In complex scenes with overlapping geometry, `any_hit` can return hits that are significantly farther + * Both always agree on **whether** a hit occurred (hit vs miss) + * The difference appears when BVH traversal order differs from spatial distance order + +## Performance Comparison + +Compare the performance of `closest_hit` vs `any_hit`. + +```julia (editor=true, logging=false, output=true) +function render_io(obj) + io = IOBuffer() + show(io, MIME"text/plain"(), obj) + printer = BonitoBook.HTMLPrinter(io; root_tag = "span") + str = sprint(io -> show(io, MIME"text/html"(), printer)) + DOM.pre(HTML(str); style="font-size: 10px") +end +``` +```julia (editor=true, logging=false, output=true) +using BenchmarkTools + +test_ray = Raycore.Ray(o=Point3f(0.1, 0.1, -5), d=Vec3f(0, 0, 1)) + +# Benchmark closest_hit +closest_time = @benchmark Raycore.closest_hit($bvh, $test_ray) + +# Benchmark any_hit +any_time = @benchmark Raycore.any_hit($bvh, $test_ray) + + +perf_table = map([ + ("closest_hit", any_time), + ("any_hit", closest_time), +]) do (method, time_us) + (Method = method, Time_μs = render_io(time_us)) +end +Bonito.Table(perf_table) +``` +## Summary + +This document demonstrated: + +1. **`RayIntersectionSession`** - A convenient struct for managing ray tracing sessions + + * Bundles rays, BVH, hit function, and results together + * Provides helper functions: `hit_count()`, `miss_count()`, `hit_points()`, `hit_distances()` +2. **Makie visualization recipe** - Automatic visualization via `plot(session)` + + * Automatically renders BVH geometry, rays, and hit points + * Customizable colors, transparency, markers, and labels + * Works with any Makie backend (GLMakie, WGLMakie, CairoMakie) +3. **`closest_hit`** correctly identifies the nearest intersection among multiple overlapping primitives + + * Returns: `(hit_found::Bool, hit_primitive::Triangle, distance::Float32, barycentric_coords::Point3f)` + * `distance` is the distance from ray origin to the hit point + * Use `Raycore.sum_mul(bary_coords, primitive.vertices)` to convert to world-space hit point +4. **`any_hit`** efficiently determines if any intersection exists, exiting early + + * Returns: Same format as `closest_hit`: `(hit_found::Bool, hit_primitive::Triangle, distance::Float32, barycentric_coords::Point3f)` + * Can exit early on first hit found, making it faster for occlusion testing +5. Both functions handle miss cases correctly (returning `hit_found=false`) +6. `any_hit` is typically faster than `closest_hit` due to early termination + +All tests passed! ✓ + diff --git a/docs/src/index.md b/docs/src/index.md index f95670b..e492081 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,4 +1,4 @@ -# RayCaster.jl +# Raycore.jl ```@setup raycaster using Bonito @@ -6,7 +6,7 @@ Bonito.Page() ``` ```@example raycaster -using RayCaster, GeometryBasics, LinearAlgebra +using Raycore, GeometryBasics, LinearAlgebra using WGLMakie, FileIO function LowSphere(radius, contact=Point3f(0); ntriangles=10) @@ -21,15 +21,15 @@ s4 = LowSphere(0.4f0, Point3f(0, 1.0, 0); ntriangles) l = 0.5 floor = Rect3f(-l, -l, -0.01, 2l, 2l, 0.01) cat = load(Makie.assetpath("cat.obj")) -bvh = RayCaster.BVHAccel([s1, s2, s3, s4, cat]); +bvh = Raycore.BVHAccel([s1, s2, s3, s4, cat]); world_mesh = GeometryBasics.Mesh(bvh) f, ax, pl = Makie.mesh(world_mesh; color=:teal) center!(ax.scene) viewdir = normalize(ax.scene.camera.view_direction[]) -@time "hitpoints" hitpoints, centroid = RayCaster.get_centroid(bvh, viewdir) -@time "illum" illum = RayCaster.get_illumination(bvh, viewdir) -@time "viewf_matrix" viewf_matrix = RayCaster.view_factors(bvh, rays_per_triangle=1000) +@time "hitpoints" hitpoints, centroid = Raycore.get_centroid(bvh, viewdir) +@time "illum" illum = Raycore.get_illumination(bvh, viewdir) +@time "viewf_matrix" viewf_matrix = Raycore.view_factors(bvh, rays_per_triangle=1000) viewfacts = map(i-> Float32(sum(view(viewf_matrix, :, i))), 1:length(bvh.primitives)) world_mesh = GeometryBasics.Mesh(bvh) N = length(world_mesh.faces) @@ -64,11 +64,20 @@ Label(f[3, 2], "Illumination", tellwidth=false, fontsize=20) f ``` +```@example raycaster +using Bonito, BonitoBook +App() do + path = normpath(joinpath(dirname(pathof(Raycore)), "..", "docs", "src", "bvh_hit_tests.md")) + BonitoBook.InlineBook(path) +end +``` + + ## Overview ```@autodocs -Modules = [RayCaster] +Modules = [Raycore] Order = [:module, :constant, :type, :function, :macro] Public = true Private = false @@ -77,7 +86,7 @@ Private = false ## Private Functions ```@autodocs -Modules = [RayCaster] +Modules = [Raycore] Order = [:module, :constant, :type, :function, :macro] Public = false Private = true diff --git a/docs/src/raytracing_tutorial.md b/docs/src/raytracing_tutorial.md new file mode 100644 index 0000000..885caea --- /dev/null +++ b/docs/src/raytracing_tutorial.md @@ -0,0 +1,14 @@ +# Ray Tracing with Raycore + +```@setup raytracing +using Bonito +Bonito.Page() +``` + +```@example raytracing +using Bonito, BonitoBook, Raycore +App() do + path = normpath(joinpath(dirname(pathof(Raycore)), "..", "docs", "src", "raytracing_tutorial_content.md")) + BonitoBook.InlineBook(path) +end +``` diff --git a/docs/src/raytracing_tutorial_content.md b/docs/src/raytracing_tutorial_content.md new file mode 100644 index 0000000..deb5094 --- /dev/null +++ b/docs/src/raytracing_tutorial_content.md @@ -0,0 +1,600 @@ +# Ray Tracing with Raycore: Building a Real Ray Tracer + +In this tutorial, we'll build a simple but complete ray tracer from scratch using Raycore. We'll start with the absolute basics and progressively add features until we have a ray tracer that produces beautiful images with shadows and materials. + +By the end, you'll have a working ray tracer that can render complex scenes! + +## Setup + +```julia (editor=true, logging=false, output=true) +using Raycore, GeometryBasics, LinearAlgebra +using Colors, ImageShow +using Makie # For loading assets +using BenchmarkTools +``` +**Ready to go!** We have: + + * `Raycore` for fast ray-triangle intersections + * `GeometryBasics` for geometry primitives + * `Colors` and `ImageShow` for displaying rendered images + +## Part 1: Our Scene - A Playful Cat + +Let's create a fun scene that we'll use throughout this tutorial. We'll load a cat model and place it in a simple room. + +```julia (editor=true, logging=false, output=true) +# Load the cat model and rotate it to face the camera +cat_mesh = Makie.loadasset("cat.obj") +# Rotate 150 degrees around Y axis so cat faces camera at an angle +angle = deg2rad(150f0) +rotation = Makie.Quaternionf(0, sin(angle/2), 0, cos(angle/2)) +rotated_coords = [rotation * Point3f(v) for v in coordinates(cat_mesh)] + +# Get bounding box and translate cat to sit on the floor +cat_bbox = Rect3f(rotated_coords) +floor_y = -1.5f0 +cat_offset = Vec3f(0, floor_y - cat_bbox.origin[2], 0) # Translate so bottom sits on floor + +cat_mesh = GeometryBasics.normal_mesh( + [v + cat_offset for v in rotated_coords], + faces(cat_mesh) +) + +# Create a simple room: floor, back wall, and side wall +floor = normal_mesh(Rect3f(Vec3f(-5, -1.5, -2), Vec3f(10, 0.01, 10))) +back_wall = normal_mesh(Rect3f(Vec3f(-5, -1.5, 8), Vec3f(10, 5, 0.01))) +left_wall = normal_mesh(Rect3f(Vec3f(-5, -1.5, -2), Vec3f(0.01, 5, 10))) + +# Add a couple of spheres for visual interest (also on the floor) +sphere1 = Tesselation(Sphere(Point3f(-2, -1.5 + 0.8, 2), 0.8f0), 64) +sphere2 = Tesselation(Sphere(Point3f(2, -1.5 + 0.6, 1), 0.6f0), 64) + +# Build our BVH acceleration structure +scene_geometry = [cat_mesh, floor, back_wall, left_wall, sphere1, sphere2] +bvh = Raycore.BVHAccel(scene_geometry) +``` +**Scene created!** + + * Cat model with triangulated geometry + * Room geometry: 3 walls + * 2 decorative spheres + * BVH built for fast ray traversal + +## Part 2: The Simplest Ray Tracer - Binary Hit Detection + +Let's start super simple: for each pixel, we shoot a ray and color it based on whether we hit something or not. + +```julia (editor=true, logging=false, output=true) +# Trace helper - runs a callback for each pixel +function trace(f, bvh; width=700, height=300, camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + # Precompute camera parameters + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + for y in 1:height, x in 1:width + # Generate camera ray + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + # Ray-scene intersection + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(bvh, ray) + + # Let the callback decide the color (pass sky_color for misses) + img[y, x] = hit_found ? f(triangle, distance, bary_coords, ray) : sky_color + end + + return img +end + +# Binary kernel - white if hit +binary_kernel(triangle, distance, bary_coords, ray) = RGB(1.0f0, 1.0f0, 1.0f0) + +trace(binary_kernel, bvh, sky_color=RGB(0.0f0, 0.0f0, 0.0f0)) +``` +**Our first render!** Pure silhouette - you can see the cat and spheres. + +## Part 3: Adding Depth - Distance-Based Shading + +Let's make it more interesting by coloring based on distance (depth map). + +```julia (editor=true, logging=false, output=true) +function depth_kernel(triangle, distance, bary_coords, ray) + # Map distance to grayscale (closer = brighter) + normalized_depth = clamp(1.0f0 - (distance - 2.0f0) / 8.0f0, 0.0f0, 1.0f0) + RGB(normalized_depth, normalized_depth, normalized_depth) +end + +trace(depth_kernel, bvh) +``` +**Depth perception!** Now we can see the 3D structure - closer objects are brighter. + +## Part 4: Surface Normals - The Foundation of Lighting + +To do proper lighting, we need surface normals. Let's compute and visualize them. + +```julia (editor=true, logging=false, output=true) +# Helper to interpolate normals using barycentric coordinates +function compute_normal(triangle, bary_coords) + n1, n2, n3 = triangle.normals + u, v, w = bary_coords + normalize(Vec3f(u * n1 + v * n2 + w * n3)) +end + +function normal_kernel(triangle, distance, bary_coords, ray) + normal = compute_normal(triangle, bary_coords) + # Map normal components [-1,1] to color [0,1] + RGB((normal .+ 1.0f0) ./ 2.0f0...) +end + +trace(normal_kernel, bvh) +``` +**Surface normals visualized!** Each color channel represents a normal component: + + * Red = X direction + * Green = Y direction + * Blue = Z direction + +## Part 5: Basic Lighting - Diffuse Shading + +Now we can add a light source and compute simple diffuse (Lambertian) shading! + +```julia (editor=true, logging=false, output=true) +light_pos = Point3f(3, 4, -2) +light_intensity = 50.0f0 + +function diffuse_kernel(triangle, distance, bary_coords, ray) + # Compute hit point and normal + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Light direction and distance + light_dir = light_pos - hit_point + light_distance = norm(light_dir) + light_dir = normalize(light_dir) + + # Diffuse shading (Lambertian) + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Light attenuation (inverse square law) + attenuation = light_intensity / (light_distance * light_distance) + color = diffuse * attenuation + + RGB(color, color, color) +end + +trace(diffuse_kernel, bvh) +``` +**Let there be light!** Our scene now has proper shading based on surface orientation. + +## Part 6: Adding Shadows - Shadow Rays + +Time to add realism with shadows using Raycore's `any_hit` for fast occlusion testing. + +```julia (editor=true, logging=false, output=true) +ambient = 0.1f0 # Ambient lighting to prevent pure black shadows + +function shadow_kernel(triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Light direction + light_dir = light_pos - hit_point + light_distance = norm(light_dir) + light_dir = normalize(light_dir) + + # Diffuse shading + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow ray - offset slightly to avoid self-intersection + shadow_ray_origin = hit_point + normal * 0.001f0 + shadow_ray = Raycore.Ray(o=shadow_ray_origin, d=light_dir) + + # Check if path to light is blocked + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + # Final color + color = if in_shadow + ambient # Only ambient in shadow + else + attenuation = light_intensity / (light_distance * light_distance) + ambient + diffuse * attenuation + end + + RGB(color, color, color) +end + +trace(shadow_kernel, bvh) +``` +**Shadows!** Notice how objects cast shadows on each other, adding depth and realism. + +## Part 7: Multiple Lights + +Let's add multiple lights to make the scene more interesting! We'll define a RenderContext to hold lights and materials: + +```julia (editor=true, logging=false, output=true) +# Define a simple point light structure +struct PointLight + position::Point3f + intensity::Float32 + color::RGB{Float32} +end + +# Material structure (for later use) +struct Material + base_color::RGB{Float32} + metallic::Float32 + roughness::Float32 +end + +# Render context holds all scene data +struct RenderContext + bvh::Raycore.BVHAccel + lights::Vector{PointLight} + materials::Vector{Material} + ambient::Float32 +end + +# Create multiple lights +lights = [ + PointLight(Point3f(3, 4, -2), 50.0f0, RGB(1.0f0, 0.9f0, 0.8f0)), # Warm main light + PointLight(Point3f(-3, 2, 0), 20.0f0, RGB(0.7f0, 0.8f0, 1.0f0)), # Cool fill light + PointLight(Point3f(0, 5, 5), 15.0f0, RGB(1.0f0, 1.0f0, 1.0f0)) # White back light +] + +# Materials (will use these in Part 8) +materials = [ + Material(RGB(0.8f0, 0.6f0, 0.4f0), 0.0f0, 0.8f0), # 1: Cat + Material(RGB(0.3f0, 0.5f0, 0.3f0), 0.0f0, 0.9f0), # 2: Floor + Material(RGB(0.7f0, 0.7f0, 0.8f0), 0.0f0, 0.8f0), # 3: Back wall + Material(RGB(0.8f0, 0.7f0, 0.7f0), 0.0f0, 0.8f0), # 4: Left wall + Material(RGB(0.95f0, 0.64f0, 0.54f0), 1.0f0, 0.1f0), # 5: Sphere 1 - metallic + Material(RGB(0.8f0, 0.8f0, 0.9f0), 1.0f0, 0.0f0) # 6: Sphere 2 - mirror +] + +# Create render context +ctx = RenderContext(bvh, lights, materials, 0.1f0) +``` +Now we need a new trace function that works with RenderContext: + +```julia (editor=true, logging=false, output=true) +# Trace with RenderContext +function trace_ctx(f, ctx::RenderContext; width=700, height=300,camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + for y in 1:height, x in 1:width + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + img[y, x] = hit_found ? f(ctx, triangle, distance, bary_coords, ray) : sky_color + end + + return img +end + +function multi_light_kernel(ctx, triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Start with ambient (grayscale) + total_color = Vec3f(ctx.ambient, ctx.ambient, ctx.ambient) + + # Accumulate contribution from each light + for light in ctx.lights + light_vec = light.position - hit_point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + shadow_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += light_col * (diffuse * attenuation) + end + end + + RGB{Float32}(total_color...) +end + +trace_ctx(multi_light_kernel, ctx) +``` +**Multiple lights!** The scene now has three different colored lights creating a more dynamic lighting environment. + +## Part 8: Colored Materials with Multiple Lights + +Now let's combine materials with our multiple lights! + +```julia (editor=true, logging=false, output=true) +function material_multi_light_kernel(ctx, triangle, distance, bary_coords, ray) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + + # Get material from context + mat = ctx.materials[triangle.material_idx] + base_color = Vec3f(mat.base_color.r, mat.base_color.g, mat.base_color.b) + + # Start with ambient + total_color = base_color * ctx.ambient + + # Accumulate contribution from each light + for light in ctx.lights + light_vec = light.position - hit_point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow test + shadow_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += base_color .* light_col * (diffuse * attenuation) + end + end + + RGB{Float32}(total_color...) +end + +trace_ctx(material_multi_light_kernel, ctx) +``` +**Colored materials!** + + * Orange/tan cat + * Green floor + * Light blue back wall + * Pink side wall + * Red and blue spheres + +## Part 9: Reflective Materials - Mirrors and Metals + +The materials we defined in Part 7 already have metallic and roughness properties. Let's use them for reflections! + +```julia (editor=true, logging=false, output=true) +# Helper: compute direct lighting with multiple lights +function compute_multi_light(ctx, point, normal, mat) + base_color = Vec3f(mat.base_color.r, mat.base_color.g, mat.base_color.b) + + # Start with ambient + total_color = base_color * ctx.ambient + + for light in ctx.lights + light_vec = light.position - point + light_distance = norm(light_vec) + light_dir = light_vec / light_distance + + diffuse = max(0.0f0, dot(normal, light_dir)) + + # Shadow test + shadow_ray = Raycore.Ray(o=point + normal * 0.001f0, d=light_dir) + shadow_hit, _, shadow_dist, _ = Raycore.any_hit(ctx.bvh, shadow_ray) + in_shadow = shadow_hit && shadow_dist < light_distance + + if !in_shadow + attenuation = light.intensity / (light_distance * light_distance) + light_col = Vec3f(light.color.r, light.color.g, light.color.b) + total_color += base_color .* light_col * (diffuse * attenuation) + end + end + + return RGB{Float32}(total_color...) +end + +function reflective_kernel(ctx, triangle, distance, bary_coords, ray, sky_color) + hit_point = ray.o + ray.d * distance + normal = compute_normal(triangle, bary_coords) + mat = ctx.materials[triangle.material_idx] + + # Compute direct lighting (diffuse component) + direct_color = compute_multi_light(ctx, hit_point, normal, mat) + + # Add reflection for metallic materials + if mat.metallic > 0.0f0 + # Compute reflection direction: reflect outgoing direction about normal + # Note: ray.d points toward surface, but reflect() expects outgoing direction + wo = -ray.d # outgoing direction (away from surface) + reflect_dir = Raycore.reflect(wo, normal) + + # Add roughness by perturbing reflection direction + if mat.roughness > 0.0f0 + # Simple roughness: add random offset in tangent space + random_offset = (rand(Vec3f) .* 2.0f0 .- 1.0f0) * mat.roughness + reflect_dir = normalize(reflect_dir + random_offset) + end + + # Cast reflection ray (offset to avoid self-intersection) + reflect_ray = Raycore.Ray(o=hit_point + normal * 0.001f0, d=reflect_dir) + refl_hit, refl_tri, refl_dist, refl_bary = Raycore.closest_hit(ctx.bvh, reflect_ray) + + # Get reflection color + reflection_color = if refl_hit + refl_point = reflect_ray.o + reflect_ray.d * refl_dist + refl_normal = compute_normal(refl_tri, refl_bary) + refl_mat = ctx.materials[refl_tri.material_idx] + + # Compute lighting for reflected surface + compute_multi_light(ctx, refl_point, refl_normal, refl_mat) + else + sky_color + end + + # Blend between diffuse and reflection based on metallic parameter + return direct_color * (1.0f0 - mat.metallic) + reflection_color * mat.metallic + else + # Pure diffuse material + return direct_color + end +end + +trace_ctx(ctx) do ctx, triangle, distance, bary_coords, ray + reflective_kernel(ctx, triangle, distance, bary_coords, ray, RGB(0.5f0, 0.7f0, 1.0f0)) +end +``` +**Reflective materials!** The spheres now have metallic properties: + + * One smooth copper-colored metal with slight roughness + * One perfect mirror reflecting the scene + +Notice how reflections capture both the scene geometry and lighting! + +## Part 10: Multi-threading for Performance + +Let's add multi-threading to make our ray tracer much faster! + +```julia (editor=true, logging=false, output=true) +using BenchmarkTools +function trace_ctx_threaded(f, ctx::RenderContext; width=400, height=300, camera_pos=Point3f(0, 1, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0)) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + + Threads.@threads for y in 1:height + for x in 1:width + ndc_x = (2.0f0 * x / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * y / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + img[y, x] = hit_found ? f(ctx, triangle, distance, bary_coords, ray) : sky_color + end + end + + return img +end + +# Benchmark single-threaded vs multi-threaded +b1 = @belapsed trace_ctx(material_multi_light_kernel, ctx, width=800, height=600); + +b2 = @belapsed trace_ctx_threaded(material_multi_light_kernel, ctx, width=800, height=600); +md""" +Threads: $(Threads.nthreads()) + +Single: $(b1) + +Multi: $(b2) +""" +``` +**Performance boost with threading!** The speedup should be close to the number of CPU cores. + +Notice how we can reuse the same kernel function with both `trace_ctx()` and `trace_ctx_threaded()` - this is great for composability! + +## Part 11: Multi-Sampling for Anti-Aliasing + +Let's add multiple samples per pixel with jittered camera rays for smooth anti-aliasing: + +```julia (editor=true, logging=false, output=true) +function trace_ctx_sampled(f, ctx::RenderContext; + width=700, height=300, + camera_pos=Point3f(0, -0.9, -2.5), fov=45.0f0, + sky_color=RGB{Float32}(0.5f0, 0.7f0, 1.0f0), + samples=4) + img = Matrix{RGB{Float32}}(undef, height, width) + + aspect = Float32(width / height) + focal_length = 1.0f0 / tan(deg2rad(fov / 2)) + pixel_size = 1.0f0 / width + + Threads.@threads for y in 1:height + for x in 1:width + # Accumulate multiple samples per pixel using Vec3f for math + color_sum = Vec3f(0.0f0, 0.0f0, 0.0f0) + + for _ in 1:samples + jitter_x = (rand(Float32) - 0.5f0) * pixel_size + jitter_y = (rand(Float32) - 0.5f0) * pixel_size + + ndc_x = (2.0f0 * (x + jitter_x) / width - 1.0f0) * aspect + ndc_y = 1.0f0 - 2.0f0 * (y + jitter_y) / height + direction = normalize(Vec3f(ndc_x, ndc_y, focal_length)) + ray = Raycore.Ray(o=camera_pos, d=direction) + + hit_found, triangle, distance, bary_coords = Raycore.closest_hit(ctx.bvh, ray) + + color = if hit_found + result = f(ctx, triangle, distance, bary_coords, ray) + Vec3f(result.r, result.g, result.b) + else + Vec3f(sky_color.r, sky_color.g, sky_color.b) + end + + color_sum += color + end + + # Average samples and convert back to RGB + avg = color_sum / samples + img[y, x] = RGB{Float32}(avg...) + end + end + + return img +end + +# Render with 16 samples per pixel for smooth anti-aliasing +@time trace_ctx_sampled(ctx, samples=64) do ctx, triangle, distance, bary_coords, ray + reflective_kernel(ctx, triangle, distance, bary_coords, ray, RGB(0.5f0, 0.7f0, 1.0f0)) +end +``` +**Anti-aliased render!** 32 samples per pixel with jittered camera rays eliminate jagged edges. + +## Summary - What We Built + +We created a complete ray tracer that includes: + +### Features Implemented + +1. **Camera system** - Perspective projection with configurable FOV +2. **Ray-scene intersection** - Using Raycore's BVH for fast traversal +3. **Surface normals** - Smooth shading from vertex normals +4. **Diffuse lighting** - Lambertian shading with distance attenuation +5. **Hard shadows** - Using `any_hit` for efficient occlusion testing +6. **Simple materials** - Per-object color assignment +7. **Multi-threading** - Parallel rendering across CPU cores +8. **Callback-based API** - Flexible `trace()` function for experimentation + +### Next Steps + +To make this into a full path tracer (like `Trace`), you would add: + + * **Recursive ray tracing** - Reflections and refractions + * **Multiple light sources** - Area lights, environment lighting + * **Advanced materials** - Specular, glossy, transparent + * **Sampling** - Multiple samples per pixel for anti-aliasing + * **Better normal interpolation** - Proper barycentric interpolation + * **GPU support** - Using KernelAbstractions.jl + +The `Trace` package implements all of these features and more! + +### Key Raycore Functions Used + + * `Raycore.BVHAccel(meshes)` - Build acceleration structure + * `Raycore.Ray(o=origin, d=direction)` - Create ray + * `Raycore.closest_hit(bvh, ray)` - Find nearest intersection + * `Raycore.any_hit(bvh, ray)` - Test for any intersection (fast!) + * `Raycore.vertices(triangle)` - Get triangle vertex positions + * `Raycore.normals(triangle)` - Get triangle vertex normals + +Happy ray tracing! + diff --git a/ext/RaycoreMakieExt.jl b/ext/RaycoreMakieExt.jl new file mode 100644 index 0000000..9e70b90 --- /dev/null +++ b/ext/RaycoreMakieExt.jl @@ -0,0 +1,231 @@ +module RaycoreMakieExt + +using Raycore +using Makie +using GeometryBasics +import Makie: plot, plot! + +""" + plot(session::RayIntersectionSession; kwargs...) + +Makie recipe for visualizing a RayIntersectionSession. + +# Keyword Arguments +- `show_bvh::Bool = true`: Whether to show the BVH geometry +- `bvh_alpha::Float64 = 0.4`: Transparency for BVH meshes +- `bvh_colors = [:red, :yellow, :blue]`: Colors to cycle through for different meshes +- `ray_colors = nothing`: Colors for rays. If `nothing`, uses a gradient based on hit distance +- `ray_color::Symbol = :black`: Default color for all rays if `ray_colors` is `nothing` +- `hit_color::Symbol = :green`: Color for hit point markers +- `miss_color::Symbol = :gray`: Color for rays that missed +- `ray_length::Float32 = 15.0f0`: Length to draw rays that miss +- `show_hit_points::Bool = true`: Whether to show markers at hit points +- `hit_markersize::Float64 = 0.2`: Size of hit point markers +- `show_labels::Bool = false`: Whether to show text labels at hit points +- `axis = nothing`: Optional axis to draw on (if not provided, creates new figure) + +# Example +```julia +using Raycore, GeometryBasics, GLMakie + +# Create geometry +sphere1 = Tesselation(Sphere(Point3f(0, 0, 1), 1.0f0), 20) +sphere2 = Tesselation(Sphere(Point3f(0, 0, 3), 1.0f0), 20) +bvh = Raycore.BVHAccel([sphere1, sphere2]) + +# Create rays +rays = [ + Raycore.Ray(Point3f(0, 0, -5), Vec3f(0, 0, 1)), + Raycore.Ray(Point3f(1, 0, -5), Vec3f(0, 0, 1)), +] + +# Create and visualize session +session = RayIntersectionSession(rays, bvh, Raycore.closest_hit) +plot(session) +``` +""" +@recipe(RayPlot, session) do scene + Attributes( + show_bvh = true, + bvh_alpha = 0.4, + bvh_colors = [:red, :yellow, :blue], + ray_colors = nothing, + ray_color = :black, + hit_color = :green, + miss_color = :gray, + ray_length = 15.0f0, + show_hit_points = true, + hit_markersize = 0.2, + show_labels = false, + ) +end + +Makie.plottype(::Raycore.RayIntersectionSession) = RayPlot +Makie.preferred_axis_type(::RayPlot) = LScene + +function Makie.plot!(plot::RayPlot) + session = plot[:session][] + + # Extract attributes + show_bvh = plot[:show_bvh][] + bvh_alpha = plot[:bvh_alpha][] + bvh_colors = plot[:bvh_colors][] + ray_colors = plot[:ray_colors][] + ray_color = plot[:ray_color][] + hit_color = plot[:hit_color][] + miss_color = plot[:miss_color][] + ray_length = plot[:ray_length][] + show_hit_points = plot[:show_hit_points][] + hit_markersize = plot[:hit_markersize][] + show_labels = plot[:show_labels][] + + # Draw BVH if requested + if show_bvh + draw_bvh!(plot, session.bvh, bvh_colors, bvh_alpha) + end + + # Determine ray colors if not provided + if isnothing(ray_colors) + # Use single color for all rays + ray_colors = fill(ray_color, length(session.rays)) + end + + # Collect all data for batch rendering + hit_ray_starts = Point3f[] + hit_ray_directions = Vec3f[] + hit_ray_colors = [] + + miss_ray_starts = Point3f[] + miss_ray_directions = Vec3f[] + + hit_points_pos = Point3f[] + hit_labels_pos = Point3f[] + hit_labels_text = String[] + + for (i, (ray, hit)) in enumerate(zip(session.rays, session.hits)) + hit_found, hit_primitive, distance, bary_coords = hit + + # Get color for this ray + color = i <= length(ray_colors) ? ray_colors[i] : ray_color + + if hit_found + # Calculate hit point + hit_point = Raycore.sum_mul(bary_coords, hit_primitive.vertices) + + # Collect ray data + push!(hit_ray_starts, ray.o) + push!(hit_ray_directions, hit_point - ray.o) + push!(hit_ray_colors, color) + + # Collect hit point data + if show_hit_points + push!(hit_points_pos, hit_point) + + # Collect label data + if show_labels + push!(hit_labels_pos, hit_point .+ Vec3f(0.2, 0.2, 0.2)) + push!(hit_labels_text, "Hit $i\nd=$(round(distance, digits=2))") + end + end + else + # Ray missed - collect miss ray data + push!(miss_ray_starts, ray.o) + push!(miss_ray_directions, ray.d * ray_length) + end + end + + # Draw all hit rays in one call + if !isempty(hit_ray_starts) + arrows3d!( + plot, + hit_ray_starts, + hit_ray_directions, + color = hit_ray_colors, + markerscale = 0.3 + ) + end + + # Draw all miss rays in one call + if !isempty(miss_ray_starts) + arrows3d!( + plot, + miss_ray_starts, + miss_ray_directions, + color = miss_color, + markerscale = 0.3 + ) + end + + # Draw all hit points in one call + if show_hit_points && !isempty(hit_points_pos) + meshscatter!( + plot, + hit_points_pos, + color = hit_color, + markersize = hit_markersize + ) + end + + # Draw all labels in one call + if show_labels && !isempty(hit_labels_pos) + text!( + plot, + hit_labels_pos, + text = hit_labels_text, + color = hit_color, + fontsize = 12 + ) + end + + return plot +end + +""" +Helper function to draw BVH geometry +""" +function draw_bvh!(plot, bvh::Raycore.BVHAccel, colors, alpha) + # Group primitives by their material_idx + primitive_groups = Dict{UInt32, Vector{Raycore.Triangle}}() + for prim in bvh.primitives + mat_idx = prim.material_idx + if !haskey(primitive_groups, mat_idx) + primitive_groups[mat_idx] = Raycore.Triangle[] + end + push!(primitive_groups[mat_idx], prim) + end + + # Draw each group with a different color + color_idx = 1 + for (mat_idx, prims) in primitive_groups + # Get all triangles for this mesh + vertices = Point3f[] + faces = GeometryBasics.TriangleFace{Int}[] + + for (i, prim) in enumerate(prims) + # Add vertices + start_idx = length(vertices) + for v in prim.vertices + push!(vertices, v) + end + # Add face (using 1-based indexing) + push!(faces, GeometryBasics.TriangleFace(start_idx + 1, start_idx + 2, start_idx + 3)) + end + + # Create mesh from vertices and faces + mesh_obj = GeometryBasics.normal_mesh(vertices, faces) + + # Pick color + color = colors[mod1(color_idx, length(colors))] + color_idx += 1 + + # Draw mesh + mesh!( + plot, + mesh_obj, + color = (color, alpha), + transparency = true + ) + end +end + +end # module diff --git a/src/RayCaster.jl b/src/Raycore.jl similarity index 84% rename from src/RayCaster.jl rename to src/Raycore.jl index 5cac992..dc2a77f 100644 --- a/src/RayCaster.jl +++ b/src/Raycore.jl @@ -1,4 +1,4 @@ -module RayCaster +module Raycore using GeometryBasics using LinearAlgebra @@ -42,10 +42,12 @@ include("ray.jl") include("bounds.jl") include("transformations.jl") include("math.jl") -include("surface_interaction.jl") -include("shapes/Shape.jl") +include("triangle_mesh.jl") include("bvh.jl") include("kernel-abstractions.jl") include("kernels.jl") +include("ray_intersection_session.jl") + +export RayIntersectionSession, hit_points, hit_distances, hit_count, miss_count end diff --git a/src/bounds.jl b/src/bounds.jl index 8224ece..ce971ba 100644 --- a/src/bounds.jl +++ b/src/bounds.jl @@ -22,11 +22,14 @@ end function Base.:≈(b1::Union{Bounds2,Bounds3}, b2::Union{Bounds2,Bounds3}) b1.p_min ≈ b2.p_min && b1.p_max ≈ b2.p_max end -function Base.getindex(b::Union{Bounds2,Bounds3}, i::Integer) - i == 1 && return b.p_min - i == 2 && return b.p_max - # error("Invalid index `$i`. Only `1` & `2` are valid.") + +function Base.getindex(b::Union{Bounds2, Bounds3}, i::T) where T<:Integer + i === T(1) && return b.p_min + i === T(2) && return b.p_max + N = b isa Bounds2 ? 2 : 3 + return Point{N, Float32}(NaN) end + function is_valid(b::Bounds3)::Bool all(b.p_min .!= Inf32) && all(b.p_max .!= -Inf32) end @@ -49,11 +52,10 @@ end # Index through 8 corners. function corner(b::Bounds3, c::Integer) c -= Int32(1) - Point3f( - b[(c&1)+1][1], - b[(c & 2) != 0 ? 2 : 1][2], - b[(c & 4) != 0 ? 2 : 1][3], - ) + x = (c & Int32(1)) == Int32(0) ? b.p_min[1] : b.p_max[1] + y = (c & Int32(2)) == Int32(0) ? b.p_min[2] : b.p_max[2] + z = (c & Int32(4)) == Int32(0) ? b.p_min[3] : b.p_max[3] + Point3f(x, y, z) end function Base.union(b1::B, b2::B) where B<:Union{Bounds2,Bounds3} diff --git a/src/bvh.jl b/src/bvh.jl index 9046d1b..02d354f 100644 --- a/src/bvh.jl +++ b/src/bvh.jl @@ -47,6 +47,7 @@ end function LinearBVHLeaf(bounds::Bounds3, primitives_offset::Integer, n_primitives::Integer) LinearBVH(bounds, primitives_offset, n_primitives, 0, false) end + function LinearBVHInterior(bounds::Bounds3, second_child_offset::Integer, split_axis::Integer) LinearBVH(bounds, second_child_offset, 0, split_axis, true) end @@ -86,7 +87,7 @@ to_triangle_mesh(x::TriangleMesh) = x function to_triangle_mesh(x::GeometryBasics.AbstractGeometry) m = GeometryBasics.uv_normal_mesh(x) - return create_triangle_mesh(m) + return TriangleMesh(m) end @@ -94,13 +95,11 @@ function BVHAccel( primitives::AbstractVector{P}, max_node_primitives::Integer=1, ) where {P} triangles = Triangle[] - prim_idx = 1 for (mi, prim) in enumerate(primitives) triangle_mesh = to_triangle_mesh(prim) vertices = triangle_mesh.vertices for i in 1:div(length(triangle_mesh.indices), 3) - push!(triangles, Triangle(triangle_mesh, i, prim_idx)) - prim_idx += 1 + push!(triangles, Triangle(triangle_mesh, i, mi)) end end ordered_primitives, max_prim, nodes = primitives_to_bvh(triangles, max_node_primitives) @@ -239,8 +238,17 @@ end length(bvh.nodes) > Int32(0) ? bvh.nodes[1].bounds : Bounds3() end +struct MemAllocator +end +@inline _allocate(::MemAllocator, T::Type, n::Val{N}) where {N} = zeros(MVector{N,T}) +Base.@propagate_inbounds function _setindex(arr::MVector{N, T}, idx::Integer, value::T) where {N, T} + arr[idx] = value + return arr +end + + """ - _traverse_bvh(bvh::BVHAccel{P}, ray::AbstractRay, hit_callback::F) where {P, F<:Function} + traverse_bvh(bvh::BVHAccel{P}, ray::AbstractRay, hit_callback::F) where {P, F<:Function} Internal function that traverses the BVH to find ray-primitive intersections. Uses a callback pattern to handle different intersection behaviors. @@ -254,10 +262,11 @@ Arguments: Returns: - The final result from the hit_callback """ -@inline function traverse_bvh(hit_callback::F, bvh::BVHAccel{P}, ray::AbstractRay) where {P, F<:Function} - # Early return if BVH is empty +@inline function traverse_bvh(hit_callback::F, bvh::BVHAccel{P}, ray::AbstractRay, allocator=MemAllocator()) where {P, F<:Function} if length(bvh.nodes) == 0 - return false, ray, nothing + # We dont handle the empty case yet, since its not that easy to make it type stable + # Its possible, but why would we intersect an empty BVH? + error("BVH is empty; cannot traverse.") end # Prepare ray for traversal @@ -266,21 +275,20 @@ Returns: dir_is_neg = is_dir_negative(ray.d) # Initialize traversal stack - to_visit_offset = Int32(1) + local to_visit_offset::Int32 = Int32(1) current_node_idx = Int32(1) - nodes_to_visit = zeros(MVector{64,Int32}) + nodes_to_visit = _allocate(allocator, Int32, Val(64)) primitives = bvh.primitives nodes = bvh.nodes # State variables to hold callback results continue_search = true prim1 = primitives[1] - result = hit_callback(prim1, ray, nothing) + _, ray, result = hit_callback(prim1, ray, nothing) # Traverse BVH @_inbounds while true current_node = nodes[current_node_idx] - # Test ray against current node's bounding box if intersect_p(current_node.bounds, ray, inv_dir, dir_is_neg) if !current_node.is_interior && current_node.n_primitives > Int32(0) @@ -292,7 +300,6 @@ Returns: # Call the callback for this primitive continue_search, ray, result = hit_callback(primitive, ray, result) - # Early exit if callback requests it if !continue_search return false, ray, result @@ -300,7 +307,7 @@ Returns: end # Done with leaf, pop next node from stack - if to_visit_offset == Int32(1) + if to_visit_offset === Int32(1) break end to_visit_offset -= Int32(1) @@ -308,82 +315,91 @@ Returns: else # Interior node - push children to stack if dir_is_neg[current_node.split_axis] == Int32(2) - nodes_to_visit[to_visit_offset] = current_node_idx + Int32(1) + nodes_to_visit = _setindex(nodes_to_visit, to_visit_offset, current_node_idx + Int32(1)) current_node_idx = current_node.offset % Int32 else - nodes_to_visit[to_visit_offset] = current_node.offset % Int32 + nodes_to_visit = _setindex(nodes_to_visit, to_visit_offset, current_node.offset % Int32) current_node_idx += Int32(1) end to_visit_offset += Int32(1) end else # Miss - pop next node from stack - if to_visit_offset == Int32(1) + if to_visit_offset === Int32(1) break end to_visit_offset -= Int32(1) current_node_idx = nodes_to_visit[to_visit_offset] end end - # Return final state return continue_search, ray, result end # Initialization -closest_hit_callback(primitive, ray, ::Nothing) = (false, primitive, Point3f(0.0)) +closest_hit_callback(primitive, ray, ::Nothing) = false, ray, (false, primitive, 0.0f0, Point3f(0.0)) -function closest_hit_callback(primitive, ray, prev_result::Tuple{Bool, P, Point3f}) where {P} +function closest_hit_callback(primitive, ray, prev_result::Tuple{Bool, P, Float32, Point3f}) where {P} # Test intersection and update if closer tmp_hit, ray, tmp_bary = intersect_p!(primitive, ray) + if tmp_hit + # Calculate distance from ray origin to hit point + distance = ray.t_max + return true, ray, (true, primitive, distance, tmp_bary) + end # Always continue search to find closest - return true, ray, ifelse(tmp_hit, (true, primitive, tmp_bary), prev_result) + return true, ray, prev_result end """ - intersect!(bvh::BVHAccel{P}, ray::AbstractRay) where {P} + closest_hit(bvh::BVHAccel{P}, ray::AbstractRay) where {P} Find the closest intersection between a ray and the primitives stored in a BVH. Returns: - `hit_found`: Boolean indicating if an intersection was found - `hit_primitive`: The primitive that was hit (if any) +- `distance`: Distance along the ray to the hit point (hit_point = ray.o + ray.d * distance) - `barycentric_coords`: Barycentric coordinates of the hit point """ -@inline function intersect!(bvh::BVHAccel{P}, ray::AbstractRay) where {P} +@inline function closest_hit(bvh::BVHAccel{P}, ray::AbstractRay, allocator=MemAllocator()) where {P} # Traverse BVH with closest-hit callback - _, _, result = traverse_bvh(closest_hit_callback, bvh, ray) - return result::Tuple{Bool, Triangle, Point3f} + _, _, result = traverse_bvh(closest_hit_callback, bvh, ray, allocator) + return result::Tuple{Bool, Triangle, Float32, Point3f} end -any_hit_callback(primitive, current_ray, result::Nothing) = () +any_hit_callback(primitive, current_ray, result::Nothing) = (false, current_ray, (false, primitive, 0.0f0, Point3f(0.0))) # Define any-hit callback -function any_hit_callback(primitive, current_ray, ::Tuple{}) +function any_hit_callback(primitive, current_ray, prev_result::Tuple{Bool, P, Float32, Point3f}) where {P} # Test for intersection - if intersect_p(primitive, current_ray) - # Stop traversal on first hit - return false, current_ray, true + tmp_hit, dist, tmp_bary = intersect(primitive, current_ray) + if tmp_hit + # Stop traversal on first hit and return hit info + return false, current_ray, (true, primitive, dist, tmp_bary) end # Continue search if no hit - return true, current_ray, false + return true, current_ray, prev_result end """ - intersect_p(bvh::BVHAccel, ray::AbstractRay) + any_hit(bvh::BVHAccel, ray::AbstractRay) Test if a ray intersects any primitive in the BVH (without finding the closest hit). +This function stops at the first intersection found, making it faster than closest_hit +when you only need to know if there's an intersection. Returns: - `hit_found`: Boolean indicating if any intersection was found +- `hit_primitive`: The primitive that was hit (if any) +- `distance`: Distance along the ray to the hit point (hit_point = ray.o + ray.d * distance) +- `barycentric_coords`: Barycentric coordinates of the hit point """ -@inline function intersect_p(bvh::BVHAccel, ray::AbstractRay) +@inline function any_hit(bvh::BVHAccel, ray::AbstractRay, allocator=MemAllocator()) # Traverse BVH with any-hit callback - continue_search, _, result = traverse_bvh(any_hit_callback, bvh, ray) - # If traversal completed without finding a hit, return false - # Otherwise return the hit result (true) - return !continue_search ? result : false + continue_search, _, result = traverse_bvh(any_hit_callback, bvh, ray, allocator) + return result::Tuple{Bool, Triangle, Float32, Point3f} end function calculate_ray_grid_bounds(bounds::GeometryBasics.Rect, ray_direction::Vec3f) @@ -481,3 +497,40 @@ function GeometryBasics.Mesh(bvh::BVHAccel) end return GeometryBasics.Mesh(points, faces) end + +# Pretty printing for BVHAccel +function Base.show(io::IO, ::MIME"text/plain", bvh::BVHAccel) + n_triangles = length(bvh.primitives) + n_nodes = length(bvh.nodes) + bounds = world_bound(bvh) + + # Count leaf vs interior nodes + n_leaves = count(node -> !node.is_interior, bvh.nodes) + n_interior = n_nodes - n_leaves + + # Calculate average primitives per leaf + total_leaf_prims = sum(node -> node.is_interior ? 0 : Int(node.n_primitives), bvh.nodes) + avg_prims_per_leaf = n_leaves > 0 ? total_leaf_prims / n_leaves : 0.0 + + println(io, "BVHAccel:") + println(io, " Triangles: ", n_triangles) + println(io, " BVH nodes: ", n_nodes, " (", n_interior, " interior, ", n_leaves, " leaves)") + println(io, " Bounds: ", bounds.p_min, " to ", bounds.p_max) + println(io, " Max prims: ", Int(bvh.max_node_primitives), " per leaf") + print(io, " Avg prims: ", round(avg_prims_per_leaf, digits=2), " per leaf") +end + +function Base.show(io::IO, bvh::BVHAccel) + if get(io, :compact, false) + n_triangles = length(bvh.primitives) + n_nodes = length(bvh.nodes) + n_leaves = count(node -> !node.is_interior, bvh.nodes) + n_interior = n_nodes - n_leaves + print(io, "BVHAccel(") + print(io, "triangles=", n_triangles, ", ") + print(io, "nodes=", n_nodes, " (", n_interior, " interior, ", n_leaves, " leaves)") + print(io, ")") + else + show(io, MIME("text/plain"), bvh) + end +end diff --git a/src/kernel-abstractions.jl b/src/kernel-abstractions.jl index f346a1c..4992b9d 100644 --- a/src/kernel-abstractions.jl +++ b/src/kernel-abstractions.jl @@ -16,8 +16,8 @@ end # Conversion constructor for e.g. GPU arrays # TODO, create tree on GPU? Not sure if that will gain much though... -function to_gpu(ArrayType, bvh::RayCaster.BVHAccel; preserve=[]) +function to_gpu(ArrayType, bvh::Raycore.BVHAccel; preserve=[]) primitives = to_gpu(ArrayType, bvh.primitives; preserve=preserve) nodes = to_gpu(ArrayType, bvh.nodes; preserve=preserve) - return RayCaster.BVHAccel(primitives, bvh.max_node_primitives, nodes) + return Raycore.BVHAccel(primitives, bvh.max_node_primitives, nodes) end diff --git a/src/kernels.jl b/src/kernels.jl index 2e7faed..db1b8e1 100644 --- a/src/kernels.jl +++ b/src/kernels.jl @@ -7,12 +7,12 @@ end function hits_from_grid(bvh, viewdir; grid_size=32) # Calculate grid bounds ray_direction = normalize(viewdir) - ray_origins = RayCaster.generate_ray_grid(bvh, ray_direction, grid_size) + ray_origins = Raycore.generate_ray_grid(bvh, ray_direction, grid_size) result = similar(ray_origins, RayHit) Threads.@threads for idx in CartesianIndices(ray_origins) o = ray_origins[idx] - ray = RayCaster.Ray(; o=o, d=ray_direction) - hit, prim, bary = RayCaster.intersect!(bvh, ray) + ray = Raycore.Ray(; o=o, d=ray_direction) + hit, prim, dist, bary = Raycore.closest_hit(bvh, ray) hitpoint = sum_mul(bary, prim.vertices) @inbounds result[idx] = RayHit(hit, hitpoint, prim.material_idx) end @@ -35,7 +35,7 @@ function view_factors!(result, bvh, rays_per_triangle=10000) point_on_triangle = random_triangle_point(triangle) o = point_on_triangle .+ (normal .* 0.01f0) # Offset so it doesn't self intersect ray = Ray(; o=o, d=random_hemisphere_uniform(normal, u, v)) - hit, prim, _ = intersect!(bvh, ray) + hit, prim, dist, _ = closest_hit(bvh, ray) if hit && prim.material_idx != triangle.material_idx # weigh by angle? result[triangle.material_idx, prim.material_idx] += 1 diff --git a/src/ray_intersection_session.jl b/src/ray_intersection_session.jl new file mode 100644 index 0000000..72cf025 --- /dev/null +++ b/src/ray_intersection_session.jl @@ -0,0 +1,95 @@ +""" + RayIntersectionSession{F} + +Represents a ray tracing session containing rays, a BVH structure, a hit function, +and the computed intersection results. + +# Fields +- `rays::Vector{<:AbstractRay}`: Array of rays to trace +- `bvh::BVHAccel`: BVH acceleration structure to intersect against +- `hit_function::F`: Function to use for intersection testing (e.g., `closest_hit` or `any_hit`) +- `hits::Vector{Tuple{Bool, Triangle, Float32, Point3f}}`: Results of hit_function applied to each ray + +# Example +```julia +using Raycore, GeometryBasics + +# Create BVH from geometry +sphere = Tesselation(Sphere(Point3f(0, 0, 1), 1.0f0), 20) +bvh = Raycore.BVHAccel([sphere]) + +# Create rays +rays = [ + Raycore.Ray(Point3f(0, 0, -5), Vec3f(0, 0, 1)), + Raycore.Ray(Point3f(1, 0, -5), Vec3f(0, 0, 1)), +] + +# Create session +session = RayIntersectionSession(rays, bvh, Raycore.closest_hit) + +# Access results +for (i, hit) in enumerate(session.hits) + hit_found, primitive, distance, bary_coords = hit + if hit_found + println("Ray \$i hit at distance \$distance") + end +end +``` +""" +struct RayIntersectionSession{Rays, F} + hit_function::F + rays::Rays + bvh::BVHAccel + hits::Vector{Tuple{Bool, Triangle, Float32, Point3f}} + + function RayIntersectionSession(hit_function::F, rays::Rays, bvh::BVHAccel) where {Rays,F} + # Compute all hits + hits = [hit_function(bvh, ray) for ray in rays] + new{Rays, F}(hit_function, rays, bvh, hits) + end +end + +""" + hit_points(session::RayIntersectionSession) + +Extract all valid hit points from a RayIntersectionSession. + +Returns a vector of `Point3f` containing the world-space hit points for all rays that intersected geometry. +""" +function hit_points(session::RayIntersectionSession) + return map(filter(first, session.hits)) do hit + _, hit_primitive, _, bary_coords = hit + return sum_mul(bary_coords, hit_primitive.vertices) + end +end + +""" + hit_distances(session::RayIntersectionSession) + +Extract all hit distances from a RayIntersectionSession. + +Returns a vector of `Float32` containing distances for all rays that intersected geometry. +""" +function hit_distances(session::RayIntersectionSession) + return map(filter(first, session.hits)) do hit + return hit[3] + end +end + +""" + hit_count(session::RayIntersectionSession) + +Count the number of rays that hit geometry in the session. +""" +function hit_count(session::RayIntersectionSession) + count(hit -> hit[1], session.hits) +end + +""" + miss_count(session::RayIntersectionSession) + +Count the number of rays that missed all geometry in the session. +""" +function miss_count(session::RayIntersectionSession) + count(hit -> !hit[1], session.hits) +end diff --git a/src/shapes/Shape.jl b/src/shapes/Shape.jl deleted file mode 100644 index a25e749..0000000 --- a/src/shapes/Shape.jl +++ /dev/null @@ -1,25 +0,0 @@ -struct ShapeCore - object_to_world::Transformation - world_to_object::Transformation - reverse_orientation::Bool - transform_swaps_handedness::Bool - - function ShapeCore( - object_to_world::Transformation, reverse_orientation::Bool = false, - ) - new( - object_to_world, inv(object_to_world), reverse_orientation, - swaps_handedness(object_to_world), - ) - end -end - -ShapeCore(reverse::Bool=false) = ShapeCore(Transformation(), reverse) -ShapeCore(offset::Vec3, reverse::Bool=false) = ShapeCore(translate(Vec3f(offset)), reverse) - -function world_bound(s::AbstractShape)::Bounds3 - s.core.object_to_world(object_bound(s)) -end - -include("sphere.jl") -include("triangle_mesh.jl") diff --git a/src/shapes/sphere.jl b/src/shapes/sphere.jl deleted file mode 100644 index b9b4371..0000000 --- a/src/shapes/sphere.jl +++ /dev/null @@ -1,195 +0,0 @@ -struct Sphere <: AbstractShape - core::ShapeCore - - radius::Float32 - # Implicit constraints. - z_min::Float32 - z_max::Float32 - # Parametric constraints. - θ_min::Float32 - θ_max::Float32 - ϕ_max::Float32 - - function Sphere( - core::ShapeCore, radius::Float32, - z_min::Float32, z_max::Float32, ϕ_max::Float32, - ) - new( - core, radius, - clamp(min(z_min, z_max), -radius, radius), - clamp(max(z_min, z_max), -radius, radius), - acos(clamp(min(z_min, z_max) / radius, -1f0, 1f0)), - acos(clamp(max(z_min, z_max) / radius, -1f0, 1f0)), - deg2rad(clamp(ϕ_max, 0f0, 360f0)), - ) - end -end - -function Sphere(core::ShapeCore, radius::Float32, ϕ_max::Float32) - Sphere(core, radius, -radius, radius, ϕ_max) -end - -function object_bound(s::Sphere) - Bounds3( - Point3f(-s.radius, -s.radius, s.z_min), - Point3f(s.radius, s.radius, s.z_max), - ) -end - -function solve_quadratic(a::Float32, b::Float32, c::Float32) - # Find disriminant. - d = b^2 - 4 * a * c - if d < 0 - return false, NaN32, NaN32 - end - d = sqrt(d) - # Compute roots. - q = -0.5f0 * (b + (b < 0 ? -d : d)) - t0 = q / a - t1 = c / q - if t0 > t1 - t0, t1 = t1, t0 - end - true, t0, t1 -end - -function refine_intersection(p::Point, s::Sphere) - p *= s.radius ./ distance(Point3f(0), p) - p[1] ≈ 0f0 && p[2] ≈ 0f0 && (p = Point3f(1f-6 * s.radius, p[2], p[3])) - p -end - -""" -Test if hit point exceeds clipping parameters of the sphere. -""" -function test_clipping(s::Sphere, p::Point3f, ϕ::Float32)::Bool - (s.z_min > -s.radius && p[3] < s.z_min) || - (s.z_max < s.radius && p[3] > s.z_max) || - ϕ > s.ϕ_max -end - -function compute_ϕ(p::Point3f)::Float32 - ϕ = atan(p[2], p[1]) - ϕ < 0f0 && (ϕ += 2f0 * π) - ϕ -end - -function precompute_ϕ(p::Point3f) - z_radius = sqrt(p[1] * p[1] + p[2] * p[2]) - inv_z_radius = 1f0 / z_radius - cos_ϕ = p[1] * inv_z_radius - sin_ϕ = p[2] * inv_z_radius - sin_ϕ, cos_ϕ -end - -""" -Compute partial derivatives of intersection point in parametric form. -""" -function partial_derivatives(s::Sphere, p::Point3f, θ::Float32, sin_ϕ::Float32, cos_ϕ::Float32) - ∂p∂u = Vec3f(-s.ϕ_max * p[2], s.ϕ_max * p[1], 0f0) - ∂p∂v = (s.θ_max - s.θ_min) * Vec3f( - p[3] * cos_ϕ, p[3] * sin_ϕ, -s.radius * sin(θ), - ) - ∂p∂u, ∂p∂v, sin_ϕ, cos_ϕ -end - -function normal_derivatives( - s::Sphere, p::Point3f, - sin_ϕ::Float32, cos_ϕ::Float32, - ∂p∂u::Vec3f, ∂p∂v::Vec3f, -) - ∂2p∂u2 = -s.ϕ_max * s.ϕ_max * Vec3f(p[1], p[2], 0f0) - ∂2p∂u∂v = (s.θ_max - s.θ_min) * p[3] * s.ϕ_max * Vec3f(-sin_ϕ, cos_ϕ, 0f0) - ∂2p∂v2 = (s.θ_max - s.θ_min)^2 * -p - # Compute coefficients for fundamental forms. - E = ∂p∂u ⋅ ∂p∂u - F = ∂p∂u ⋅ ∂p∂v - G = ∂p∂v ⋅ ∂p∂v - n = normalize(∂p∂u × ∂p∂v) - e = n ⋅ ∂2p∂u2 - f = n ⋅ ∂2p∂u∂v - g = n ⋅ ∂2p∂v2 - # Compute derivatives from fundamental form coefficients. - inv_egf = 1f0 / (E * G - F * F) - ∂n∂u = Normal3f( - (f * F - e * G) * inv_egf * ∂p∂u + - (e * F - f * E) * inv_egf * ∂p∂v, - ) - ∂n∂v = Normal3f( - (g * F - f * G) * inv_egf * ∂p∂u + - (f * F - g * E) * inv_egf * ∂p∂v, - ) - ∂n∂u, ∂n∂v -end - -function intersect( - s::Sphere, ray::Union{Ray,RayDifferentials}, ::Bool = false, - )::Tuple{Bool,Float32,SurfaceInteraction} - # Transform ray to object space. - sf = SurfaceInteraction() - or = apply(s.core.world_to_object, ray) - # Substitute ray into sphere equation. - a = norm(or.d)^2 - b = 2 * or.o ⋅ or.d - c = norm(or.o)^2 - s.radius^2 - # Solve quadratic equation for t. - exists, t0, t1 = solve_quadratic(a, b, c) - !exists && return false, 0.0f0, sf - (t0 > or.t_max || t1 < 0.0f0) && return false, 0.0f0, sf - t0 < 0 && (t0 = t1) - - shape_hit = t0 - hit_point = refine_intersection(apply(or, t0), s) - ϕ = compute_ϕ(hit_point) - # Test sphere intersection against clipping parameters. - if test_clipping(s, hit_point, ϕ) - shape_hit = t1 - hit_point = refine_intersection(apply(or, t1), s) - ϕ = compute_ϕ(hit_point) - test_clipping(s, hit_point, ϕ) && return false, 0.0f0, sf - end - # Find parametric representation of hit point. - u = ϕ / s.ϕ_max - θ = acos(clamp(hit_point[3] / s.radius, -1f0, 1f0)) - v = (θ - s.θ_min) / (s.θ_max - s.θ_min) - - sin_ϕ, cos_ϕ = precompute_ϕ(hit_point) - ∂p∂u, ∂p∂v = partial_derivatives(s, hit_point, θ, sin_ϕ, cos_ϕ) - ∂n∂u, ∂n∂v = normal_derivatives(s, hit_point, sin_ϕ, cos_ϕ, ∂p∂u, ∂p∂v) - reverse_normal = (s.core.reverse_orientation ⊻ s.core.transform_swaps_handedness) - si = SurfaceInteraction( - hit_point, ray.time, -ray.d, Point2f(u, v), - ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, reverse_normal - ) - si = apply(s.core.object_to_world, si) - true, shape_hit, si -end - -function intersect_p( - s::Sphere, ray::Union{Ray,RayDifferentials}, ::Bool=false, - )::Bool - - # Transform ray to object space. - or = apply(s.core.world_to_object, ray) - # Substitute ray into sphere equation. - a = norm(or.d)^2 - b = 2f0 * or.o ⋅ or.d - c = norm(or.o)^2 - s.radius^2 - # Solve quadratic equation for t. - exists, t0, t1 = solve_quadratic(a, b, c) - !exists && return false - (t0 > or.t_max || t1 < 0f0) && return false - t0 < 0 && (t0 = t1) - - hit_point = refine_intersection(apply(or, t0), s) - ϕ = compute_ϕ(hit_point) - # Test sphere intersection against clipping parameters. - if test_clipping(s, hit_point, ϕ) - hit_point = refine_intersection(apply(or, t1), s) - ϕ = compute_ϕ(hit_point) - test_clipping(s, hit_point, ϕ) && return false - end - true -end - -@inline area(s::Sphere) = s.ϕ_max * s.radius * (s.z_max - s.z_min) diff --git a/src/surface_interaction.jl b/src/surface_interaction.jl deleted file mode 100644 index 699e591..0000000 --- a/src/surface_interaction.jl +++ /dev/null @@ -1,240 +0,0 @@ -struct Interaction - """ - Intersection point in world coordinates. - """ - p::Point3f - """ - Time of intersection. - """ - time::Float32 - """ - Negative direction of ray (for ray-shape interactions) - in world coordinates. - """ - wo::Vec3f - """ - Surface normal at the point in world coordinates. - """ - n::Normal3f -end - -Interaction() = Interaction(Point3f(Inf), 0f0, Vec3f(0f0), Normal3f(0f0)) - -struct ShadingInteraction - n::Normal3f - ∂p∂u::Vec3f - ∂p∂v::Vec3f - ∂n∂u::Normal3f - ∂n∂v::Normal3f -end - -struct SurfaceInteraction - core::Interaction - shading::ShadingInteraction - uv::Point2f - - ∂p∂u::Vec3f - ∂p∂v::Vec3f - ∂n∂u::Normal3f - ∂n∂v::Normal3f - - ∂u∂x::Float32 - ∂u∂y::Float32 - ∂v∂x::Float32 - ∂v∂y::Float32 - ∂p∂x::Vec3f - ∂p∂y::Vec3f - - SurfaceInteraction() = new() - - function SurfaceInteraction( - core::Interaction, shading::ShadingInteraction, uv, - ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, - ∂u∂x, ∂u∂y, ∂v∂x, ∂v∂y, - ∂p∂x, ∂p∂y, - ) - new( - core, shading, uv, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, - ∂u∂x, ∂u∂y, ∂v∂x, ∂v∂y, ∂p∂x, ∂p∂y - ) - end -end - -@inline function SurfaceInteraction( - p::Point3f, time::Float32, wo::Vec3f, uv::Point2f, - ∂p∂u::Vec3f, ∂p∂v::Vec3f, ∂n∂u::Normal3f, ∂n∂v::Normal3f, reverse_normal::Bool - ) - - n = normalize((∂p∂u × ∂p∂v)) - - if reverse_normal - n *= -1 - end - - core = Interaction(p, time, wo, n) - shading = ShadingInteraction(n, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v) - return SurfaceInteraction( - core, shading, uv, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, - 0f0, 0f0, 0f0, 0f0, Vec3f(0f0), Vec3f(0f0) - ) -end - -@inline function SurfaceInteraction( - normal, hitpoint::Point3f, time::Float32, wo::Vec3f, uv::Point2f, - ∂p∂u::Vec3f, ∂p∂v::Vec3f, ∂n∂u::Normal3f, ∂n∂v::Normal3f - ) - core = Interaction(hitpoint, time, wo, normal) - shading = ShadingInteraction(normal, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v) - return SurfaceInteraction( - core, shading, uv, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, - 0.0f0, 0.0f0, 0.0f0, 0.0f0, Vec3f(0.0f0), Vec3f(0.0f0) - ) -end - -@inline function set_shading_geometry( - si::SurfaceInteraction, tangent::Vec3f, bitangent::Vec3f, - ∂n∂u::Normal3f, ∂n∂v::Normal3f, orientation_is_authoritative::Bool, - ) - shading_n = normalize(tangent × bitangent) - core_n = si.core.n - if orientation_is_authoritative - core_n = face_forward(si.core.n, si.shading.n) - else - shading_n = face_forward(si.shading.n, si.core.n) - end - - shading = ShadingInteraction(shading_n, tangent, bitangent, ∂n∂u, ∂n∂v) - core = Interaction(si.core.p, si.core.time, si.core.wo, core_n) - return SurfaceInteraction(si; shading=shading, core=core) -end - -is_surface_interaction(i::Interaction) = i.n != Normal3f(0) - -@inline function SurfaceInteraction( - si::SurfaceInteraction; - core=si.core , shading=si.shading, uv=si.uv, ∂p∂u=si.∂p∂u, ∂p∂v=si.∂p∂v, - ∂n∂u=si.∂n∂u, ∂n∂v=si.∂n∂v, ∂u∂x=si.∂u∂x, ∂u∂y=si.∂u∂y, - ∂v∂x=si.∂v∂x, ∂v∂y=si.∂v∂y, ∂p∂x=si.∂p∂x, ∂p∂y=si.∂p∂y - ) - SurfaceInteraction( - core, shading, uv, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, ∂u∂x, ∂u∂y, ∂v∂x, ∂v∂y, ∂p∂x, ∂p∂y - ) -end - -""" -Compute partial derivatives needed for computing sampling rates -for things like texture antialiasing. -""" -@inline function compute_differentials(si::SurfaceInteraction, ray::RayDifferentials) - - if !ray.has_differentials - return SurfaceInteraction(si; - ∂u∂x=0.0f0, ∂v∂x=0.0f0, ∂u∂y=0.0f0, ∂v∂y=0f0, ∂p∂x=Vec3f(0.0f0), ∂p∂y=Vec3f(0.0f0) - ) - end - - # Estimate screen change in p and (u, v). - # Compute auxiliary intersection points with plane. - - d = -(si.core.n ⋅ si.core.p) - tx = (-(si.core.n ⋅ ray.rx_origin) - d) / (si.core.n ⋅ ray.rx_direction) - ty = (-(si.core.n ⋅ ray.ry_origin) - d) / (si.core.n ⋅ ray.ry_direction) - px = ray.rx_origin + tx * ray.rx_direction - py = ray.ry_origin + ty * ray.ry_direction - - ∂p∂x = px - si.core.p - ∂p∂y = py - si.core.p - # Compute (u, v) offsets at auxiliary points. - # Choose two dimensions for ray offset computation. - n = abs.(si.core.n) - if n[1] > n[2] && n[1] > n[3] - dim = Point2(2, 3) - elseif n[2] > n[3] - dim = Point2(1, 3) - else - dim = Point2(1, 2) - end - # Initialization for offset computation. - a = Mat2f(dim[1], dim[1], dim[2], dim[2]) - bx = Point2f(px[dim[1]] - si.core.p[dim[1]], px[dim[2]] - si.core.p[dim[2]]) - by = Point2f(py[dim[1]] - si.core.p[dim[1]], py[dim[2]] - si.core.p[dim[2]]) - sx = a \ bx - sy = a \ by - - ∂u∂x, ∂v∂x = any(isnan.(sx)) ? (0f0, 0f0) : sx - ∂u∂y, ∂v∂y = any(isnan.(sy)) ? (0f0, 0f0) : sy - return SurfaceInteraction(si; ∂u∂x, ∂v∂x, ∂u∂y, ∂v∂y, ∂p∂x, ∂p∂y) -end - -""" -If an intersection was found, it is necessary to determine, how -the surface's material scatters light. -`compute_scattering!` method evaluates texture functions to determine -surface properties and then initializing a representation of the BSDF -at the point. -""" -@inline function compute_scattering!( - primitive, si::SurfaceInteraction, ray::RayDifferentials, - allow_multiple_lobes::Bool = false, transport = Radiance, - ) - si = compute_differentials(si, ray) - return si, compute_scattering!(primitive, si, allow_multiple_lobes, transport) -end - -@inline function le(::SurfaceInteraction, ::Vec3f)::RGBSpectrum - # TODO right now return 0, since there is no area lights implemented. - RGBSpectrum(0f0) -end - -@inline function apply(t::Transformation, si::Interaction) - return Interaction( - t(si.p), - si.time, - normalize(t(si.wo)), - normalize(t(si.n)), - ) -end - -@inline function apply(t::Transformation, si::ShadingInteraction) - n = normalize(t(si.n)) - ∂p∂u = t(si.∂p∂u) - ∂p∂v = t(si.∂p∂v) - ∂n∂u = t(si.∂n∂u) - ∂n∂v = t(si.∂n∂v) - return ShadingInteraction(n, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v) -end - -@inline function apply(t::Transformation, si::SurfaceInteraction) - # TODO compute shading normal separately - core = apply(t, si.core) - shading = apply(t, si.shading) - ∂p∂u = t(si.∂p∂u) - ∂p∂v = t(si.∂p∂v) - ∂n∂u = t(si.∂n∂u) - ∂n∂v = t(si.∂n∂v) - ∂p∂x = t(si.∂p∂x) - ∂p∂y = t(si.∂p∂y) - return SurfaceInteraction( - core, shading, si.uv, ∂p∂u, ∂p∂v, ∂n∂u, ∂n∂v, - si.∂u∂x, si.∂u∂y, si.∂v∂x, si.∂v∂y, ∂p∂x, ∂p∂y - ) -end - -@inline function spawn_ray( - p0::Interaction, p1::Interaction, δ::Float32 = 1f-6, - )::Ray - direction = p1.p - p0.p - origin = p0.p .+ δ .* direction - return Ray(origin, direction, Inf32, p0.time) -end - -@inline function spawn_ray(p0::SurfaceInteraction, p1::Interaction)::Ray - spawn_ray(p0.core, p1) -end - -@inline function spawn_ray( - si::SurfaceInteraction, direction::Vec3f, δ::Float32 = 1f-6, - )::Ray - origin = si.core.p .+ δ .* direction - return Ray(o=origin, d=direction, time=si.core.time) -end diff --git a/src/transformations.jl b/src/transformations.jl index 8fc24ac..178523a 100644 --- a/src/transformations.jl +++ b/src/transformations.jl @@ -223,6 +223,7 @@ Base.:+(q1::Quaternion, q2::Quaternion) = Quaternion(q1.v .+ q2.v, q1.w + q2.w) Base.:-(q1::Quaternion, q2::Quaternion) = Quaternion(q1.v .- q2.v, q1.w - q2.w) Base.:/(q::Quaternion, f::Float32) = Quaternion(q.v ./ f, q.w / f) Base.:*(q::Quaternion, f::Float32) = Quaternion(q.v .* f, q.w * f) +Base.:*(f::Float32, q::Quaternion) = q * f LinearAlgebra.dot(q1::Quaternion, q2::Quaternion) = q1.v ⋅ q2.v + q1.w * q2.w LinearAlgebra.normalize(q::Quaternion) = q / sqrt(q ⋅ q) @@ -252,11 +253,11 @@ end function slerp(q1::Quaternion, q2::Quaternion, t::Float32) - cos_θ = q1 ⋅ q2 + cos_θ = Float32(q1 ⋅ q2) cos_θ > 0.9995f0 && return normalize((1 - t) * q1 + t * q2) - θ = acos(cos_θ) + θ = Float32(acos(cos_θ)) θ_p = θ * t q_perp = normalize(q2 - q1 * cos_θ) - q1 * cos(θ_p) + q_perp * sin(θ_p) + q1 * Float32(cos(θ_p)) + q_perp * Float32(sin(θ_p)) end diff --git a/src/shapes/triangle_mesh.jl b/src/triangle_mesh.jl similarity index 63% rename from src/shapes/triangle_mesh.jl rename to src/triangle_mesh.jl index 21cb904..57e6d86 100644 --- a/src/shapes/triangle_mesh.jl +++ b/src/triangle_mesh.jl @@ -26,32 +26,6 @@ struct TriangleMesh{VT<:AbstractVector{Point3f}, IT<:AbstractVector{UInt32}, NT< end end -function TriangleMesh( - object_to_world::Transformation, - indices::Vector{UInt32}, - vertices::Vector{Point3f}, - normals::Vector{Normal3f} = Normal3f[], - tangents::Vector{Vec3f} = Vec3f[], - uv::Vector{Point2f} = Point2f[], - ) - vertices = object_to_world.(vertices) - return TriangleMesh( - vertices, - copy(indices), copy(normals), - copy(tangents), copy(uv), - ) -end - -function TriangleMesh(ArrType, mesh::TriangleMesh) - TriangleMesh( - ArrType(mesh.vertices), - ArrType(mesh.indices), - ArrType(mesh.normals), - ArrType(mesh.tangents), - ArrType(mesh.uv), - ) -end - struct Triangle <: AbstractShape vertices::SVector{3,Point3f} normals::SVector{3,Normal3f} @@ -77,21 +51,7 @@ function Triangle(m::TriangleMesh, face_indx, material_idx=0) return Triangle(vs, ns, ts, uv, material_idx) end -function create_triangle_mesh( - core::ShapeCore, - indices::Vector{UInt32}, - vertices::Vector{Point3f}, - normals::Vector{Normal3f} = Normal3f[], - tangents::Vector{Vec3f} = Vec3f[], - uv::Vector{Point2f} = Point2f[], - ) - return TriangleMesh( - core.object_to_world, indices, vertices, - normals, tangents, uv, - ) -end - -function create_triangle_mesh(mesh::GeometryBasics.Mesh, core::ShapeCore=ShapeCore()) +function TriangleMesh(mesh::GeometryBasics.Mesh) nmesh = GeometryBasics.expand_faceviews(mesh) fs = decompose(TriangleFace{UInt32}, nmesh) vertices = decompose(Point3f, nmesh) @@ -102,7 +62,7 @@ function create_triangle_mesh(mesh::GeometryBasics.Mesh, core::ShapeCore=ShapeCo end indices = collect(reinterpret(UInt32, fs)) return TriangleMesh( - core.object_to_world, indices, vertices, + vertices, indices, normals, Vec3f[], Point2f.(uvs), ) end @@ -201,7 +161,6 @@ end f(x[1]) && f(x[2]) && f(x[3]) end - @inline function normal_derivatives( t::Triangle, uv::AbstractVector{Point2f}, )::Tuple{Normal3f,Normal3f} @@ -219,84 +178,9 @@ end ∂n∂u, ∂n∂v end - -@inline function init_triangle_shading_geometry( - triangle::Triangle, surf_interact::SurfaceInteraction, - bary_coords::Point3f, tex_coords::AbstractVector{Point2f}, - ) - # Check if the triangle has valid normal and tangent vectors - has_normals = _all(x -> _all(isfinite, x), triangle.normals) - has_tangents = _all(x -> _all(isfinite, x), triangle.tangents) - - # If no valid shading geometry exists, return the original surface interaction - !has_normals && !has_tangents && return surf_interact - - # Initialize triangle shading geometry by computing shading normal, tangent & bitangent - shading_normal = surf_interact.core.n # Start with geometric normal - - # If we have valid normals, interpolate them using barycentric coordinates - if has_normals - shading_normal = normalize(sum_mul(bary_coords, triangle.normals)) - end - - # Calculate shading tangent - either from triangle tangents or from position derivatives - shading_tangent = Vector3f(0) - if has_tangents - shading_tangent = normalize(sum_mul(bary_coords, triangle.tangents)) - else - shading_tangent = normalize(surf_interact.pos_deriv_u) # Assuming ∂p∂u was renamed to pos_deriv_u - end - - # Calculate shading bitangent from normal and tangent - shading_bitangent = shading_normal × shading_tangent - - # Check if bitangent is valid, otherwise create a new coordinate system - if (shading_bitangent ⋅ shading_bitangent) > 0f0 - shading_bitangent = Vec3f(normalize(shading_bitangent)) - shading_tangent = Vec3f(shading_bitangent × shading_normal) # Ensure orthogonality - else - # Create a new coordinate system if the vectors are nearly parallel - _, shading_tangent, shading_bitangent = coordinate_system(Vec3f(shading_normal)) - end - - # Calculate normal derivatives - nd_u, nd_v = normal_derivatives(triangle, tex_coords) - - # Set the shading geometry on the surface interaction - return set_shading_geometry( - surf_interact, - shading_tangent, - shading_bitangent, - nd_u, - nd_v, - true - ) -end - - -function surface_interaction(triangle, ray, bary_coords) - - verts = vertices(triangle) - tex_coords = uvs(triangle) # Get texture coordinates - - # Calculate position derivatives and triangle edges - pos_deriv_u, pos_deriv_v, edge1, edge2 = partial_derivatives(triangle, verts, tex_coords) - - # Interpolate hit point and texture coordinates using barycentric coordinates - hit_point = sum_mul(bary_coords, verts) - hit_uv = sum_mul(bary_coords, tex_coords) - - # Calculate surface normal from triangle edges - normal = normalize(edge1 × edge2) - - # Create surface interaction data at hit point - surf_interact = SurfaceInteraction( - normal, hit_point, ray.time, -ray.d, hit_uv, - pos_deriv_u, pos_deriv_v, Normal3f(0f0), Normal3f(0f0) - ) - # TODO test against alpha texture if present. - return init_triangle_shading_geometry(triangle, surf_interact, bary_coords, tex_coords) -end +# Note: surface_interaction and init_triangle_shading_geometry have been removed +# These functions are now handled by Trace.jl's triangle_to_surface_interaction +# Raycore only provides low-level ray-triangle intersection via intersect_triangle @inline function intersect(triangle::Triangle, ray::AbstractRay)::Tuple{Bool,Float32,Point3f} verts = vertices(triangle) # Get triangle vertices diff --git a/test/Manifest.toml b/test/Manifest.toml deleted file mode 100644 index 73bc78b..0000000 --- a/test/Manifest.toml +++ /dev/null @@ -1,1714 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.11.6" -manifest_format = "2.0" -project_hash = "ef2755c0e3f2a8db5ebe99b40739c90d79947014" - -[[deps.AbstractFFTs]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef" -uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" -version = "1.5.0" -weakdeps = ["ChainRulesCore", "Test"] - - [deps.AbstractFFTs.extensions] - AbstractFFTsChainRulesCoreExt = "ChainRulesCore" - AbstractFFTsTestExt = "Test" - -[[deps.AbstractTrees]] -git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" -uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" -version = "0.4.5" - -[[deps.Adapt]] -deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "f7817e2e585aa6d924fd714df1e2a84be7896c60" -uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.3.0" -weakdeps = ["SparseArrays", "StaticArrays"] - - [deps.Adapt.extensions] - AdaptSparseArraysExt = "SparseArrays" - AdaptStaticArraysExt = "StaticArrays" - -[[deps.AdaptivePredicates]] -git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6" -uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7" -version = "1.2.0" - -[[deps.AliasTables]] -deps = ["PtrArrays", "Random"] -git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" -uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" -version = "1.1.3" - -[[deps.Animations]] -deps = ["Colors"] -git-tree-sha1 = "e092fa223bf66a3c41f9c022bd074d916dc303e7" -uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340" -version = "0.4.2" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.2" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -version = "1.11.0" - -[[deps.Atomix]] -deps = ["UnsafeAtomics"] -git-tree-sha1 = "29bb0eb6f578a587a49da16564705968667f5fa8" -uuid = "a9b6321e-bd34-4604-b9c9-b65b8de01458" -version = "1.1.2" - - [deps.Atomix.extensions] - AtomixCUDAExt = "CUDA" - AtomixMetalExt = "Metal" - AtomixOpenCLExt = "OpenCL" - AtomixoneAPIExt = "oneAPI" - - [deps.Atomix.weakdeps] - CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" - Metal = "dde4c033-4e86-420c-a63e-0dd931031962" - OpenCL = "08131aa3-fb12-5dee-8b74-c09406e224a2" - oneAPI = "8f75cd03-7ff8-4ecb-9b8f-daf728133b1b" - -[[deps.Automa]] -deps = ["PrecompileTools", "SIMD", "TranscodingStreams"] -git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2" -uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b" -version = "1.1.0" - -[[deps.AxisAlgorithms]] -deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"] -git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712" -uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950" -version = "1.1.0" - -[[deps.AxisArrays]] -deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"] -git-tree-sha1 = "16351be62963a67ac4083f748fdb3cca58bfd52f" -uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9" -version = "0.4.7" - -[[deps.BFloat16s]] -deps = ["LinearAlgebra", "Printf", "Random"] -git-tree-sha1 = "3b642331600250f592719140c60cf12372b82d66" -uuid = "ab4f0b2a-ad5b-11e8-123f-65d77653426b" -version = "0.5.1" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" -version = "1.11.0" - -[[deps.BaseDirs]] -git-tree-sha1 = "bca794632b8a9bbe159d56bf9e31c422671b35e0" -uuid = "18cc8868-cbac-4acf-b575-c8ff214dc66f" -version = "1.3.2" - -[[deps.BenchmarkTools]] -deps = ["Compat", "JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] -git-tree-sha1 = "e38fbc49a620f5d0b660d7f543db1009fe0f8336" -uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "1.6.0" - -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - -[[deps.CEnum]] -git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc" -uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" -version = "0.5.0" - -[[deps.CRC32c]] -uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" -version = "1.11.0" - -[[deps.CRlibm]] -deps = ["CRlibm_jll"] -git-tree-sha1 = "66188d9d103b92b6cd705214242e27f5737a1e5e" -uuid = "96374032-68de-5a5b-8d9e-752f78720389" -version = "1.0.2" - -[[deps.CRlibm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc" -uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" -version = "1.0.1+0" - -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "fde3bf89aead2e723284a8ff9cdf5b551ed700e8" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.5+0" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra"] -git-tree-sha1 = "e4c6a16e77171a5f5e25e9646617ab1c276c5607" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.26.0" -weakdeps = ["SparseArrays"] - - [deps.ChainRulesCore.extensions] - ChainRulesCoreSparseArraysExt = "SparseArrays" - -[[deps.CodeTracking]] -deps = ["InteractiveUtils", "UUIDs"] -git-tree-sha1 = "062c5e1a5bf6ada13db96a4ae4749a4c2234f521" -uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -version = "1.3.9" - -[[deps.CodecBzip2]] -deps = ["Bzip2_jll", "TranscodingStreams"] -git-tree-sha1 = "84990fa864b7f2b4901901ca12736e45ee79068c" -uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" -version = "0.8.5" - -[[deps.ColorBrewer]] -deps = ["Colors", "JSON"] -git-tree-sha1 = "e771a63cc8b539eca78c85b0cabd9233d6c8f06f" -uuid = "a2cac450-b92f-5266-8821-25eda20663c8" -version = "0.4.1" - -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "a656525c8b46aa6a1c76891552ed5381bb32ae7b" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.30.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.1" -weakdeps = ["StyledStrings"] - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.0" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] -git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.1" - -[[deps.Compat]] -deps = ["TOML", "UUIDs"] -git-tree-sha1 = "0037835448781bb46feb39866934e243886d756a" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.18.0" -weakdeps = ["Dates", "LinearAlgebra"] - - [deps.Compat.extensions] - CompatLinearAlgebraExt = "LinearAlgebra" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.1.1+0" - -[[deps.ComputePipeline]] -deps = ["Observables", "Preferences"] -git-tree-sha1 = "cb1299fee09da21e65ec88c1ff3a259f8d0b5802" -uuid = "95dc2771-c249-4cd0-9c9f-1f3b4330693c" -version = "0.1.4" - -[[deps.ConstructionBase]] -git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" -uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" -version = "1.6.0" -weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"] - - [deps.ConstructionBase.extensions] - ConstructionBaseIntervalSetsExt = "IntervalSets" - ConstructionBaseLinearAlgebraExt = "LinearAlgebra" - ConstructionBaseStaticArraysExt = "StaticArrays" - -[[deps.Contour]] -git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" -uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" -version = "0.6.3" - -[[deps.Cthulhu]] -deps = ["CodeTracking", "FoldingTrees", "InteractiveUtils", "JuliaSyntax", "PrecompileTools", "Preferences", "REPL", "TypedSyntax", "UUIDs", "Unicode", "WidthLimitedIO"] -git-tree-sha1 = "ae585c45a75f16445b68695aa8d370145c5d2d58" -uuid = "f68482b8-f384-11e8-15f7-abe071a5a75f" -version = "2.16.5" - - [deps.Cthulhu.extensions] - CthulhuCompilerExt = "Compiler" - - [deps.Cthulhu.weakdeps] - Compiler = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" - -[[deps.DataAPI]] -git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" -uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.16.0" - -[[deps.DataStructures]] -deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "4e1fe97fdaed23e9dc21d4d664bea76b65fc50a0" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.22" - -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -version = "1.11.0" - -[[deps.DelaunayTriangulation]] -deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"] -git-tree-sha1 = "5620ff4ee0084a6ab7097a27ba0c19290200b037" -uuid = "927a84f5-c5f4-47a5-9785-b46e178433df" -version = "1.6.4" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" -version = "1.11.0" - -[[deps.Distributions]] -deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] -git-tree-sha1 = "3e6d038b77f22791b8e3472b7c633acea1ecac06" -uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" -version = "0.25.120" - - [deps.Distributions.extensions] - DistributionsChainRulesCoreExt = "ChainRulesCore" - DistributionsDensityInterfaceExt = "DensityInterface" - DistributionsTestExt = "Test" - - [deps.Distributions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" - Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.DocStringExtensions]] -git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.5" - -[[deps.Downloads]] -deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" - -[[deps.EarCut_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053" -uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5" -version = "2.2.4+0" - -[[deps.EnumX]] -git-tree-sha1 = "bddad79635af6aec424f53ed8aad5d7555dc6f00" -uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" -version = "1.0.5" - -[[deps.ExactPredicates]] -deps = ["IntervalArithmetic", "Random", "StaticArrays"] -git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025" -uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" -version = "2.2.8" - -[[deps.Expat_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d55dffd9ae73ff72f1c0482454dcf2ec6c6c4a63" -uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.6.5+0" - -[[deps.ExprTools]] -git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" -uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" -version = "0.1.10" - -[[deps.Extents]] -git-tree-sha1 = "b309b36a9e02fe7be71270dd8c0fd873625332b4" -uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910" -version = "0.1.6" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "eaa040768ea663ca695d442be1bc97edfe6824f2" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "6.1.3+0" - -[[deps.FFTW]] -deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] -git-tree-sha1 = "797762812ed063b9b94f6cc7742bc8883bb5e69e" -uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" -version = "1.9.0" - -[[deps.FFTW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "6d6219a004b8cf1e0b4dbe27a2860b8e04eba0be" -uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a" -version = "3.3.11+0" - -[[deps.FileIO]] -deps = ["Pkg", "Requires", "UUIDs"] -git-tree-sha1 = "b66970a70db13f45b7e57fbda1736e1cf72174ea" -uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -version = "1.17.0" - - [deps.FileIO.extensions] - HTTPExt = "HTTP" - - [deps.FileIO.weakdeps] - HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" - -[[deps.FilePaths]] -deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"] -git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629" -uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824" -version = "0.8.3" - -[[deps.FilePathsBase]] -deps = ["Compat", "Dates"] -git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" -uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.24" -weakdeps = ["Mmap", "Test"] - - [deps.FilePathsBase.extensions] - FilePathsBaseMmapExt = "Mmap" - FilePathsBaseTestExt = "Test" - -[[deps.FileWatching]] -uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" -version = "1.11.0" - -[[deps.FillArrays]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a" -uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.13.0" -weakdeps = ["PDMats", "SparseArrays", "Statistics"] - - [deps.FillArrays.extensions] - FillArraysPDMatsExt = "PDMats" - FillArraysSparseArraysExt = "SparseArrays" - FillArraysStatisticsExt = "Statistics" - -[[deps.FixedPointNumbers]] -deps = ["Statistics"] -git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.5" - -[[deps.FoldingTrees]] -deps = ["AbstractTrees", "REPL"] -git-tree-sha1 = "c1b0164369256b26f71d9830df9000a9c39757fc" -uuid = "1eca21be-9b9b-4ed8-839a-6d8ae26b1781" -version = "1.2.2" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "301b5d5d731a0654825f1f2e906990f7141a106b" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.16.0+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - -[[deps.FreeType]] -deps = ["CEnum", "FreeType2_jll"] -git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999" -uuid = "b38be410-82b0-50bf-ab77-7b57e271db43" -version = "4.1.1" - -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.13.4+0" - -[[deps.FreeTypeAbstraction]] -deps = ["BaseDirs", "ColorVectorSpace", "Colors", "FreeType", "GeometryBasics", "Mmap"] -git-tree-sha1 = "4ebb930ef4a43817991ba35db6317a05e59abd11" -uuid = "663a7486-cb36-511b-a19d-713bb74d65c9" -version = "0.10.8" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.17+0" - -[[deps.GPUArrays]] -deps = ["Adapt", "GPUArraysCore", "KernelAbstractions", "LLVM", "LinearAlgebra", "Printf", "Random", "Reexport", "ScopedValues", "Serialization", "Statistics"] -git-tree-sha1 = "be941842a40b6daac98496994ea69054ba4c5144" -uuid = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" -version = "11.2.3" - -[[deps.GPUArraysCore]] -deps = ["Adapt"] -git-tree-sha1 = "83cf05ab16a73219e5f6bd1bdfa9848fa24ac627" -uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.2.0" - -[[deps.GPUCompiler]] -deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "PrecompileTools", "Preferences", "Scratch", "Serialization", "TOML", "Tracy", "UUIDs"] -git-tree-sha1 = "eb1e212e12cc058fa16712082d44be499d23638c" -uuid = "61eb1bfa-7361-4325-ad38-22787b887f55" -version = "1.6.1" - -[[deps.GPUToolbox]] -deps = ["LLVM"] -git-tree-sha1 = "5bfe837129bf49e2e049b4f1517546055cc16a93" -uuid = "096a3bc2-3ced-46d0-87f4-dd12716f4bfc" -version = "0.3.0" - -[[deps.GeometryBasics]] -deps = ["EarCut_jll", "Extents", "IterTools", "LinearAlgebra", "PrecompileTools", "Random", "StaticArrays"] -git-tree-sha1 = "1f5a80f4ed9f5a4aada88fc2db456e637676414b" -uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326" -version = "0.5.10" - - [deps.GeometryBasics.extensions] - GeometryBasicsGeoInterfaceExt = "GeoInterface" - - [deps.GeometryBasics.weakdeps] - GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" - -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "43ba3d3c82c18d88471cfd2924931658838c9d8f" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.0+4" - -[[deps.Giflib_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "6570366d757b50fabae9f4315ad74d2e40c0560a" -uuid = "59f7168a-df46-5410-90c8-f2779963d0ec" -version = "5.2.3+0" - -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "35fbd0cefb04a516104b8e183ce0df11b70a3f1a" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.84.3+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a6dbda1fd736d60cc477d99f2e7a042acfa46e8" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.15+0" - -[[deps.GridLayoutBase]] -deps = ["GeometryBasics", "InteractiveUtils", "Observables"] -git-tree-sha1 = "dc6bed05c15523624909b3953686c5f5ffa10adc" -uuid = "3955a311-db13-416c-9275-1d80ed98e5e9" -version = "0.11.1" - -[[deps.Grisu]] -git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" -uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" -version = "1.0.2" - -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "f923f9a774fcf3f5cb761bfa43aeadd689714813" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "8.5.1+0" - -[[deps.HashArrayMappedTries]] -git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" -uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" -version = "0.2.0" - -[[deps.HypergeometricFunctions]] -deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] -git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec" -uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" -version = "0.3.28" - -[[deps.ImageAxes]] -deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"] -git-tree-sha1 = "e12629406c6c4442539436581041d372d69c55ba" -uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac" -version = "0.6.12" - -[[deps.ImageBase]] -deps = ["ImageCore", "Reexport"] -git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909" -uuid = "c817782e-172a-44cc-b673-b171935fbb9e" -version = "0.1.7" - -[[deps.ImageCore]] -deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"] -git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3" -uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534" -version = "0.10.5" - -[[deps.ImageIO]] -deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"] -git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7" -uuid = "82e4d734-157c-48bb-816b-45c225c6df19" -version = "0.6.9" - -[[deps.ImageMagick]] -deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils"] -git-tree-sha1 = "8e64ab2f0da7b928c8ae889c514a52741debc1c2" -uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" -version = "1.4.2" - -[[deps.ImageMagick_jll]] -deps = ["Artifacts", "Ghostscript_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "afde851466407a99d48829051c36ac80749d8d7c" -uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7" -version = "7.1.1048+0" - -[[deps.ImageMetadata]] -deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"] -git-tree-sha1 = "2a81c3897be6fbcde0802a0ebe6796d0562f63ec" -uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49" -version = "0.9.10" - -[[deps.ImageShow]] -deps = ["Base64", "ColorSchemes", "FileIO", "ImageBase", "ImageCore", "OffsetArrays", "StackViews"] -git-tree-sha1 = "3b5344bcdbdc11ad58f3b1956709b5b9345355de" -uuid = "4e3cecfd-b093-5904-9786-8bbb286a6a31" -version = "0.3.8" - -[[deps.Imath_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "0936ba688c6d201805a83da835b55c61a180db52" -uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1" -version = "3.1.11+0" - -[[deps.IndirectArrays]] -git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f" -uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959" -version = "1.0.0" - -[[deps.Inflate]] -git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" -uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" -version = "0.1.5" - -[[deps.IntelOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] -git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303" -uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" -version = "2025.2.0+0" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -version = "1.11.0" - -[[deps.Interpolations]] -deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"] -git-tree-sha1 = "f2905febca224eade352a573e129ef43aa593354" -uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" -version = "0.16.1" - - [deps.Interpolations.extensions] - InterpolationsForwardDiffExt = "ForwardDiff" - InterpolationsUnitfulExt = "Unitful" - - [deps.Interpolations.weakdeps] - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.IntervalArithmetic]] -deps = ["CRlibm", "MacroTools", "OpenBLASConsistentFPCSR_jll", "Random", "RoundingEmulator"] -git-tree-sha1 = "79342df41c3c24664e5bf29395cfdf2f2a599412" -uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" -version = "0.22.36" - - [deps.IntervalArithmetic.extensions] - IntervalArithmeticArblibExt = "Arblib" - IntervalArithmeticDiffRulesExt = "DiffRules" - IntervalArithmeticForwardDiffExt = "ForwardDiff" - IntervalArithmeticIntervalSetsExt = "IntervalSets" - IntervalArithmeticLinearAlgebraExt = "LinearAlgebra" - IntervalArithmeticRecipesBaseExt = "RecipesBase" - IntervalArithmeticSparseArraysExt = "SparseArrays" - - [deps.IntervalArithmetic.weakdeps] - Arblib = "fb37089c-8514-4489-9461-98f9c8763369" - DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" - LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.IntervalSets]] -git-tree-sha1 = "5fbb102dcb8b1a858111ae81d56682376130517d" -uuid = "8197267c-284f-5f27-9208-e0e47529a953" -version = "0.7.11" - - [deps.IntervalSets.extensions] - IntervalSetsRandomExt = "Random" - IntervalSetsRecipesBaseExt = "RecipesBase" - IntervalSetsStatisticsExt = "Statistics" - - [deps.IntervalSets.weakdeps] - Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" - Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[deps.InverseFunctions]] -git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" -uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.17" -weakdeps = ["Dates", "Test"] - - [deps.InverseFunctions.extensions] - InverseFunctionsDatesExt = "Dates" - InverseFunctionsTestExt = "Test" - -[[deps.IrrationalConstants]] -git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c" -uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.4" - -[[deps.Isoband]] -deps = ["isoband_jll"] -git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137" -uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4" -version = "0.1.1" - -[[deps.IterTools]] -git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023" -uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" -version = "1.10.0" - -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JLLWrappers]] -deps = ["Artifacts", "Preferences"] -git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" -uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.7.1" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" - -[[deps.JpegTurbo]] -deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"] -git-tree-sha1 = "9496de8fb52c224a2e3f9ff403947674517317d9" -uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0" -version = "0.1.6" - -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "eac1206917768cb54957c65a615460d87b455fc1" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.1.1+0" - -[[deps.JuliaSyntax]] -git-tree-sha1 = "937da4713526b96ac9a178e2035019d3b78ead4a" -uuid = "70703baa-626e-46a2-a12c-08ffd08c73b4" -version = "0.4.10" - -[[deps.KernelAbstractions]] -deps = ["Adapt", "Atomix", "InteractiveUtils", "MacroTools", "PrecompileTools", "Requires", "StaticArrays", "UUIDs"] -git-tree-sha1 = "83c617e9e9b02306a7acab79e05ec10253db7c87" -uuid = "63c18a36-062a-441e-b654-da1e3ab1ce7c" -version = "0.9.38" - - [deps.KernelAbstractions.extensions] - EnzymeExt = "EnzymeCore" - LinearAlgebraExt = "LinearAlgebra" - SparseArraysExt = "SparseArrays" - - [deps.KernelAbstractions.weakdeps] - EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" - LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.KernelDensity]] -deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"] -git-tree-sha1 = "ba51324b894edaf1df3ab16e2cc6bc3280a2f1a7" -uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" -version = "0.6.10" - -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "3.0.0+1" - -[[deps.LLVM]] -deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Preferences", "Printf", "Unicode"] -git-tree-sha1 = "9c7c721cfd800d87d48c745d8bfb65144f0a91df" -uuid = "929cbde3-209d-540e-8aea-75f648917ca0" -version = "9.4.2" -weakdeps = ["BFloat16s"] - - [deps.LLVM.extensions] - BFloat16sExt = "BFloat16s" - -[[deps.LLVMDowngrader_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML", "Zlib_jll"] -git-tree-sha1 = "6c4eee9991684790dcd49c52508836c02ac36133" -uuid = "f52de702-fb25-5922-94ba-81dd59b07444" -version = "0.6.0+1" - -[[deps.LLVMExtra_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "2ea068aac1e7f0337d381b0eae3110581e3f3216" -uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab" -version = "0.0.37+2" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "eb62a3deb62fc6d8822c0c4bef73e4412419c5d8" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "18.1.8+0" - -[[deps.LZO_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a" -uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" -version = "2.10.3+0" - -[[deps.LaTeXStrings]] -git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" -uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.4.0" - -[[deps.LazyArtifacts]] -deps = ["Artifacts", "Pkg"] -uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" -version = "1.11.0" - -[[deps.LazyModules]] -git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e" -uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e" -version = "0.3.1" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.4" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "8.6.0+0" - -[[deps.LibGit2]] -deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" -version = "1.11.0" - -[[deps.LibGit2_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] -uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.7.2+0" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.11.0+1" - -[[deps.LibTracyClient_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d2bc4e1034b2d43076b50f0e34ea094c2cb0a717" -uuid = "ad6e5548-8b26-5c9f-8ef3-ef0ad883f3a5" -version = "0.9.1+6" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -version = "1.11.0" - -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - -[[deps.Libiconv_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" -uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" -version = "1.18.0+0" - -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a31572773ac1b745e0343fe5e2c8ddda7a37e997" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.41.0+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "2da088d113af58221c52828a80378e16be7d037a" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.5.1+1" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "321ccef73a96ba828cd51f2ab5b9f917fa73945a" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.41.0+0" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -version = "1.11.0" - -[[deps.LittleCMS_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll"] -git-tree-sha1 = "fa7fd067dca76cadd880f1ca937b4f387975a9f5" -uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" -version = "2.16.0+0" - -[[deps.LogExpFunctions]] -deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" -uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.29" - - [deps.LogExpFunctions.extensions] - LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" - LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" - LogExpFunctionsInverseFunctionsExt = "InverseFunctions" - - [deps.LogExpFunctions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" -version = "1.11.0" - -[[deps.MKL_jll]] -deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"] -git-tree-sha1 = "282cadc186e7b2ae0eeadbd7a4dffed4196ae2aa" -uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" -version = "2025.2.0+0" - -[[deps.MacroTools]] -git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" -uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.16" - -[[deps.Makie]] -deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "ComputePipeline", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "PNGFiles", "Packing", "Pkg", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"] -git-tree-sha1 = "c2dbe9f2b1360edb15d4f711e6cc3ca0cad1acde" -uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -version = "0.24.5" - -[[deps.MappedArrays]] -git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e" -uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" -version = "0.4.2" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" -version = "1.11.0" - -[[deps.MathTeXEngine]] -deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"] -git-tree-sha1 = "a370fef694c109e1950836176ed0d5eabbb65479" -uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53" -version = "0.6.6" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.6+0" - -[[deps.Metal]] -deps = ["Adapt", "BFloat16s", "CEnum", "CodecBzip2", "ExprTools", "GPUArrays", "GPUCompiler", "GPUToolbox", "KernelAbstractions", "LLVM", "LLVMDowngrader_jll", "LinearAlgebra", "ObjectiveC", "PrecompileTools", "Preferences", "Printf", "Random", "SHA", "ScopedValues", "StaticArrays", "UUIDs"] -git-tree-sha1 = "19e6e7b52ab1c0850e81438e6de5ed59ebd4fca9" -uuid = "dde4c033-4e86-420c-a63e-0dd931031962" -version = "1.7.0" -weakdeps = ["SpecialFunctions"] - - [deps.Metal.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.2.0" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" -version = "1.11.0" - -[[deps.MosaicViews]] -deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"] -git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe" -uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" -version = "0.3.4" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2023.12.12" - -[[deps.Netpbm]] -deps = ["FileIO", "ImageCore", "ImageMetadata"] -git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd" -uuid = "f09324ee-3d7c-5217-9330-fc30815ba969" -version = "1.1.1" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" - -[[deps.ObjectiveC]] -deps = ["CEnum", "Libdl", "Preferences"] -git-tree-sha1 = "a10d01e1a9683ffd64b450cc6c1419ee9a8f7ab4" -uuid = "e86c9b32-1129-44ac-8ea0-90d5bb39ded9" -version = "3.4.2" - -[[deps.Observables]] -git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" -uuid = "510215fc-4207-5dde-b226-833fc4488ee2" -version = "0.5.5" - -[[deps.OffsetArrays]] -git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151" -uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.17.0" -weakdeps = ["Adapt"] - - [deps.OffsetArrays.extensions] - OffsetArraysAdaptExt = "Adapt" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - -[[deps.OpenBLASConsistentFPCSR_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "567515ca155d0020a45b05175449b499c63e7015" -uuid = "6cdc7f73-28fd-5e50-80fb-958a8875b1af" -version = "0.3.29+0" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.27+1" - -[[deps.OpenEXR]] -deps = ["Colors", "FileIO", "OpenEXR_jll"] -git-tree-sha1 = "97db9e07fe2091882c765380ef58ec553074e9c7" -uuid = "52e1d378-f018-4a11-a4be-720524705ac7" -version = "0.3.3" - -[[deps.OpenEXR_jll]] -deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e" -uuid = "18a262bb-aa17-5467-a713-aee519bc75cb" -version = "3.2.4+0" - -[[deps.OpenJpeg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "libpng_jll"] -git-tree-sha1 = "7dc7028a10d1408e9103c0a77da19fdedce4de6c" -uuid = "643b3616-a352-519d-856d-80112ee9badc" -version = "2.5.4+0" - -[[deps.OpenLibm_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.5+0" - -[[deps.OpenSSL_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "2ae7d4ddec2e13ad3bddf5c0796f7547cf682391" -uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.5.2+0" - -[[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.6+0" - -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c392fc5dd032381919e3b22dd32d6443760ce7ea" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.5.2+0" - -[[deps.OrderedCollections]] -git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.8.1" - -[[deps.PCRE2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.42.0+1" - -[[deps.PDMats]] -deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "f07c06228a1c670ae4c87d1276b92c7c597fdda0" -uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" -version = "0.11.35" - -[[deps.PNGFiles]] -deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"] -git-tree-sha1 = "cf181f0b1e6a18dfeb0ee8acc4a9d1672499626c" -uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883" -version = "0.4.4" - -[[deps.Packing]] -deps = ["GeometryBasics"] -git-tree-sha1 = "bc5bf2ea3d5351edf285a06b0016788a121ce92c" -uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566" -version = "0.5.1" - -[[deps.PaddedViews]] -deps = ["OffsetArrays"] -git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f" -uuid = "5432bcbf-9aad-5242-b902-cca2824c8663" -version = "0.5.12" - -[[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.3" - -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.44.2+0" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.11.0" -weakdeps = ["REPL"] - - [deps.Pkg.extensions] - REPLExt = "REPL" - -[[deps.PkgVersion]] -deps = ["Pkg"] -git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da" -uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688" -version = "0.3.3" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"] -git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.4.3" - -[[deps.PolygonOps]] -git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6" -uuid = "647866c9-e3ac-4575-94e7-e3d426903924" -version = "0.1.2" - -[[deps.PrecompileTools]] -deps = ["Preferences"] -git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" -uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.1" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.5.0" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" -version = "1.11.0" - -[[deps.Profile]] -uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" -version = "1.11.0" - -[[deps.ProgressMeter]] -deps = ["Distributed", "Printf"] -git-tree-sha1 = "13c5103482a8ed1536a54c08d0e742ae3dca2d42" -uuid = "92933f4c-e287-5a05-a399-4b506db050ca" -version = "1.10.4" - -[[deps.PtrArrays]] -git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d" -uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.3.0" - -[[deps.QOI]] -deps = ["ColorTypes", "FileIO", "FixedPointNumbers"] -git-tree-sha1 = "8b3fc30bc0390abdce15f8822c889f669baed73d" -uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65" -version = "1.0.1" - -[[deps.QuadGK]] -deps = ["DataStructures", "LinearAlgebra"] -git-tree-sha1 = "9da16da70037ba9d701192e27befedefb91ec284" -uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -version = "2.11.2" - - [deps.QuadGK.extensions] - QuadGKEnzymeExt = "Enzyme" - - [deps.QuadGK.weakdeps] - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" -version = "1.11.0" - -[[deps.Random]] -deps = ["SHA"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -version = "1.11.0" - -[[deps.RandomNumbers]] -deps = ["Random"] -git-tree-sha1 = "c6ec94d2aaba1ab2ff983052cf6a606ca5985902" -uuid = "e6cf234a-135c-5ec9-84dd-332b85af5143" -version = "1.6.0" - -[[deps.RangeArrays]] -git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5" -uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d" -version = "0.3.2" - -[[deps.Ratios]] -deps = ["Requires"] -git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b" -uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439" -version = "0.4.5" -weakdeps = ["FixedPointNumbers"] - - [deps.Ratios.extensions] - RatiosFixedPointNumbersExt = "FixedPointNumbers" - -[[deps.RayCaster]] -deps = ["Atomix", "GeometryBasics", "KernelAbstractions", "LinearAlgebra", "Random", "RandomNumbers", "StaticArrays", "Statistics"] -path = "../" -uuid = "afc56b53-c9a9-482a-a956-d1d800e05558" -version = "0.1.0" - -[[deps.Reexport]] -git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "1.2.2" - -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - -[[deps.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.1" - -[[deps.Rmath]] -deps = ["Random", "Rmath_jll"] -git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4" -uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" -version = "0.8.0" - -[[deps.Rmath_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8" -uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" -version = "0.5.1+0" - -[[deps.RoundingEmulator]] -git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b" -uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705" -version = "0.2.1" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.SIMD]] -deps = ["PrecompileTools"] -git-tree-sha1 = "fea870727142270bdf7624ad675901a1ee3b4c87" -uuid = "fdea26ae-647d-5447-a871-4b548cad5224" -version = "3.7.1" - -[[deps.ScopedValues]] -deps = ["HashArrayMappedTries", "Logging"] -git-tree-sha1 = "7f44eef6b1d284465fafc66baf4d9bdcc239a15b" -uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" -version = "1.4.0" - -[[deps.Scratch]] -deps = ["Dates"] -git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" -uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.3.0" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -version = "1.11.0" - -[[deps.ShaderAbstractions]] -deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays"] -git-tree-sha1 = "818554664a2e01fc3784becb2eb3a82326a604b6" -uuid = "65257c39-d410-5151-9873-9b3e5be5013e" -version = "0.5.0" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" -version = "1.11.0" - -[[deps.Showoff]] -deps = ["Dates", "Grisu"] -git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.0.3" - -[[deps.SignedDistanceFields]] -deps = ["Random", "Statistics", "Test"] -git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9" -uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96" -version = "0.4.0" - -[[deps.SimpleTraits]] -deps = ["InteractiveUtils", "MacroTools"] -git-tree-sha1 = "be8eeac05ec97d379347584fa9fe2f5f76795bcb" -uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" -version = "0.9.5" - -[[deps.Sixel]] -deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"] -git-tree-sha1 = "0494aed9501e7fb65daba895fb7fd57cc38bc743" -uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47" -version = "0.1.5" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" -version = "1.11.0" - -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.2" - -[[deps.SparseArrays]] -deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.11.0" - -[[deps.SpecialFunctions]] -deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "41852b8679f78c8d8961eeadc8f62cef861a52e3" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.5.1" -weakdeps = ["ChainRulesCore"] - - [deps.SpecialFunctions.extensions] - SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" - -[[deps.StableRNGs]] -deps = ["Random"] -git-tree-sha1 = "95af145932c2ed859b63329952ce8d633719f091" -uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" -version = "1.0.3" - -[[deps.StackViews]] -deps = ["OffsetArrays"] -git-tree-sha1 = "be1cf4eb0ac528d96f5115b4ed80c26a8d8ae621" -uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15" -version = "0.1.2" - -[[deps.StaticArrays]] -deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "cbea8a6bd7bed51b1619658dec70035e07b8502f" -uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.14" -weakdeps = ["ChainRulesCore", "Statistics"] - - [deps.StaticArrays.extensions] - StaticArraysChainRulesCoreExt = "ChainRulesCore" - StaticArraysStatisticsExt = "Statistics" - -[[deps.StaticArraysCore]] -git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" -uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.3" - -[[deps.Statistics]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.11.1" -weakdeps = ["SparseArrays"] - - [deps.Statistics.extensions] - SparseArraysExt = ["SparseArrays"] - -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "9d72a13a3f4dd3795a195ac5a44d7d6ff5f552ff" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.7.1" - -[[deps.StatsBase]] -deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "2c962245732371acd51700dbb268af311bddd719" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.6" - -[[deps.StatsFuns]] -deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] -git-tree-sha1 = "8e45cecc66f3b42633b8ce14d431e8e57a3e242e" -uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" -version = "1.5.0" -weakdeps = ["ChainRulesCore", "InverseFunctions"] - - [deps.StatsFuns.extensions] - StatsFunsChainRulesCoreExt = "ChainRulesCore" - StatsFunsInverseFunctionsExt = "InverseFunctions" - -[[deps.StructArrays]] -deps = ["ConstructionBase", "DataAPI", "Tables"] -git-tree-sha1 = "8ad2e38cbb812e29348719cc63580ec1dfeb9de4" -uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" -version = "0.7.1" -weakdeps = ["Adapt", "GPUArraysCore", "KernelAbstractions", "LinearAlgebra", "SparseArrays", "StaticArrays"] - - [deps.StructArrays.extensions] - StructArraysAdaptExt = "Adapt" - StructArraysGPUArraysCoreExt = ["GPUArraysCore", "KernelAbstractions"] - StructArraysLinearAlgebraExt = "LinearAlgebra" - StructArraysSparseArraysExt = "SparseArrays" - StructArraysStaticArraysExt = "StaticArrays" - -[[deps.StyledStrings]] -uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" -version = "1.11.0" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" - -[[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] -uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.7.0+0" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.3" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] -git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.12.1" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -version = "1.10.0" - -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -version = "1.11.0" - -[[deps.TiffImages]] -deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "PrecompileTools", "ProgressMeter", "SIMD", "UUIDs"] -git-tree-sha1 = "02aca429c9885d1109e58f400c333521c13d48a0" -uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69" -version = "0.11.4" - -[[deps.Tracy]] -deps = ["ExprTools", "LibTracyClient_jll", "Libdl"] -git-tree-sha1 = "91dbaee0f50faa4357f7e9fc69442c7b6364dfe5" -uuid = "e689c965-62c8-4b79-b2c5-8359227902fd" -version = "0.1.5" - - [deps.Tracy.extensions] - TracyProfilerExt = "TracyProfiler_jll" - - [deps.Tracy.weakdeps] - TracyProfiler_jll = "0c351ed6-8a68-550e-8b79-de6f926da83c" - -[[deps.TranscodingStreams]] -git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" -uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.11.3" - -[[deps.TriplotBase]] -git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b" -uuid = "981d1d27-644d-49a2-9326-4793e63143c3" -version = "0.1.0" - -[[deps.TypedSyntax]] -deps = ["CodeTracking", "JuliaSyntax"] -git-tree-sha1 = "1465a8187b3d512a99fef13244c213b54e34615d" -uuid = "d265eb64-f81a-44ad-a842-4247ee1503de" -version = "1.4.2" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" -version = "1.11.0" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" -version = "1.11.0" - -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unitful]] -deps = ["Dates", "LinearAlgebra", "Random"] -git-tree-sha1 = "6258d453843c466d84c17a58732dda5deeb8d3af" -uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" -version = "1.24.0" - - [deps.Unitful.extensions] - ConstructionBaseUnitfulExt = "ConstructionBase" - ForwardDiffExt = "ForwardDiff" - InverseFunctionsUnitfulExt = "InverseFunctions" - PrintfExt = "Printf" - - [deps.Unitful.weakdeps] - ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.UnsafeAtomics]] -git-tree-sha1 = "b13c4edda90890e5b04ba24e20a310fbe6f249ff" -uuid = "013be700-e6cd-48c3-b4a1-df204f14c38f" -version = "0.3.0" -weakdeps = ["LLVM"] - - [deps.UnsafeAtomics.extensions] - UnsafeAtomicsLLVM = ["LLVM"] - -[[deps.WebP]] -deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"] -git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2" -uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1" -version = "0.1.3" - -[[deps.WidthLimitedIO]] -deps = ["Unicode"] -git-tree-sha1 = "71142739e695823729a335e9bc124ef41ec1433a" -uuid = "b8c1c048-cf81-46c6-9da0-18c1d99e41f2" -version = "1.0.1" - -[[deps.WoodburyMatrices]] -deps = ["LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511" -uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6" -version = "1.0.0" - -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "fee71455b0aaa3440dfdd54a9a36ccef829be7d4" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.1+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "b5899b25d17bf1889d25906fb9deed5da0c15b3b" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.12+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "a4c0ee07ad36bf8bbce1c3bb52d21fb1e0b987fb" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.7+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.12+0" - -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+1" - -[[deps.Zstd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308" -uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.7+1" - -[[deps.isoband_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c" -uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4" -version = "0.2.3+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "4bba74fa59ab0755167ad24f98800fe5d727175b" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.12.1+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "125eedcb0a4a0bba65b657251ce1d27c8714e9d6" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.4+0" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.11.0+0" - -[[deps.libfdk_aac_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "07b6a107d926093898e82b3b1db657ebe33134ec" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.50+0" - -[[deps.libsixel_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"] -git-tree-sha1 = "c1733e347283df07689d71d61e14be986e49e47a" -uuid = "075b6546-f08a-558a-be8f-8157d0f608a5" -version = "1.10.5+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - -[[deps.libwebp_jll]] -deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"] -git-tree-sha1 = "ccbb625a89ec6195856a50aa2b668a5c08712c94" -uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2" -version = "1.4.0+0" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.59.0+0" - -[[deps.oneTBB_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d5a767a3bb77135a99e433afe0eb14cd7f6914c3" -uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e" -version = "2022.0.0+0" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+2" - -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 5fe351b..0000000 --- a/test/Project.toml +++ /dev/null @@ -1,18 +0,0 @@ -[deps] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" -FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" -ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" -ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" -ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" -ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31" -KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -Metal = "dde4c033-4e86-420c-a63e-0dd931031962" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -RayCaster = "afc56b53-c9a9-482a-a956-d1d800e05559" - -[sources] -RayCaster = {path = "../"} diff --git a/test/bounds.jl b/test/bounds.jl new file mode 100644 index 0000000..6132d00 --- /dev/null +++ b/test/bounds.jl @@ -0,0 +1,250 @@ +@testset "Bounds construction" begin + # Test Bounds2 + b2 = Raycore.Bounds2(Point2f(1, 2), Point2f(3, 4)) + @test b2.p_min == Point2f(1, 2) + @test b2.p_max == Point2f(3, 4) + + # Test Bounds3 + b3 = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6)) + @test b3.p_min == Point3f(1, 2, 3) + @test b3.p_max == Point3f(4, 5, 6) + + # Test default constructors (invalid configuration) + b2_default = Raycore.Bounds2() + @test b2_default.p_min == Point2f(Inf32) + @test b2_default.p_max == Point2f(-Inf32) + + b3_default = Raycore.Bounds3() + @test b3_default.p_min == Point3f(Inf32) + @test b3_default.p_max == Point3f(-Inf32) + + # Test point constructors + b2_point = Raycore.Bounds2(Point2f(5, 6)) + @test b2_point.p_min == Point2f(5, 6) + @test b2_point.p_max == Point2f(5, 6) + + b3_point = Raycore.Bounds3(Point3f(7, 8, 9)) + @test b3_point.p_min == Point3f(7, 8, 9) + @test b3_point.p_max == Point3f(7, 8, 9) + + # Test corrected constructors (swap min/max if needed) + b2_corrected = Raycore.Bounds2c(Point2f(3, 4), Point2f(1, 2)) + @test b2_corrected.p_min == Point2f(1, 2) + @test b2_corrected.p_max == Point2f(3, 4) + + b3_corrected = Raycore.Bounds3c(Point3f(4, 5, 6), Point3f(1, 2, 3)) + @test b3_corrected.p_min == Point3f(1, 2, 3) + @test b3_corrected.p_max == Point3f(4, 5, 6) +end + +@testset "Bounds comparison" begin + b1 = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6)) + b2 = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6)) + b3 = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 7)) + + @test b1 == b2 + @test b1 != b3 + @test b1 ≈ b2 + + # Test approximate equality with small differences + b4 = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6.000001)) + @test b1 ≈ b4 +end + +@testset "Bounds getindex" begin + b = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6)) + @test b[1] == Point3f(1, 2, 3) + @test b[2] == Point3f(4, 5, 6) + @test all(isnan.(b[3])) # Invalid index returns NaN +end + +@testset "Bounds validity" begin + b_valid = Raycore.Bounds3(Point3f(1, 2, 3), Point3f(4, 5, 6)) + @test Raycore.is_valid(b_valid) + + b_invalid = Raycore.Bounds3() + @test !Raycore.is_valid(b_invalid) +end + +@testset "Bounds2 iteration" begin + b = Raycore.Bounds2(Point2f(1f0, 3f0), Point2f(4f0, 4f0)) + targets = [ + Point2f(1f0, 3f0), Point2f(2f0, 3f0), Point2f(3f0, 3f0), Point2f(4f0, 3f0), + Point2f(1f0, 4f0), Point2f(2f0, 4f0), Point2f(3f0, 4f0), Point2f(4f0, 4f0), + ] + @test length(b) == 8 + for (p, t) in zip(b, targets) + @test p == t + end + + b = Raycore.Bounds2(Point2f(-1f0), Point2f(1f0)) + targets = [ + Point2f(-1f0, -1f0), Point2f(0f0, -1f0), Point2f(1f0, -1f0), + Point2f(-1f0, 0f0), Point2f(0f0, 0f0), Point2f(1f0, 0f0), + Point2f(-1f0, 1f0), Point2f(0f0, 1f0), Point2f(1f0, 1f0), + ] + @test length(b) == 9 + for (p, t) in zip(b, targets) + @test p == t + end +end + +@testset "Bounds3 corner" begin + b = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(1, 1, 1)) + @test Raycore.corner(b, 1) == Point3f(0, 0, 0) + @test Raycore.corner(b, 2) == Point3f(1, 0, 0) + @test Raycore.corner(b, 3) == Point3f(0, 1, 0) + @test Raycore.corner(b, 4) == Point3f(1, 1, 0) + @test Raycore.corner(b, 5) == Point3f(0, 0, 1) + @test Raycore.corner(b, 6) == Point3f(1, 0, 1) + @test Raycore.corner(b, 7) == Point3f(0, 1, 1) + @test Raycore.corner(b, 8) == Point3f(1, 1, 1) +end + +@testset "Bounds union and intersect" begin + b1 = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(2, 2, 2)) + b2 = Raycore.Bounds3(Point3f(1, 1, 1), Point3f(3, 3, 3)) + + # Union should contain both bounds + b_union = union(b1, b2) + @test b_union.p_min == Point3f(0, 0, 0) + @test b_union.p_max == Point3f(3, 3, 3) + + # Intersection should be the overlap + b_intersect = intersect(b1, b2) + @test b_intersect.p_min == Point3f(1, 1, 1) + @test b_intersect.p_max == Point3f(2, 2, 2) +end + +@testset "Bounds overlap and containment" begin + b1 = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(2, 2, 2)) + b2 = Raycore.Bounds3(Point3f(1, 1, 1), Point3f(3, 3, 3)) + b3 = Raycore.Bounds3(Point3f(5, 5, 5), Point3f(6, 6, 6)) + + @test Raycore.overlaps(b1, b2) + @test !Raycore.overlaps(b1, b3) + + # Test point containment + @test Raycore.inside(b1, Point3f(1, 1, 1)) + @test Raycore.inside(b1, Point3f(0, 0, 0)) # On boundary + @test Raycore.inside(b1, Point3f(2, 2, 2)) # On boundary + @test !Raycore.inside(b1, Point3f(3, 3, 3)) + + # Test exclusive containment + @test Raycore.inside_exclusive(b1, Point3f(1, 1, 1)) + @test Raycore.inside_exclusive(b1, Point3f(0, 0, 0)) # On min boundary (inclusive) + @test !Raycore.inside_exclusive(b1, Point3f(2, 2, 2)) # On max boundary (exclusive) +end + +@testset "Bounds geometric properties" begin + b = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(2, 3, 4)) + + # Diagonal + @test Raycore.diagonal(b) == Point3f(2, 3, 4) + + # Surface area: 2*(2*3 + 2*4 + 3*4) = 2*(6 + 8 + 12) = 52 + @test Raycore.surface_area(b) == 52f0 + + # Volume: 2 * 3 * 4 = 24 + @test Raycore.volume(b) == 24f0 + + # Sides + @test Raycore.sides(b) == Point3f(2, 3, 4) + + # Inclusive sides + @test Raycore.inclusive_sides(b) == Point3f(3, 4, 5) + + # Expand + b_expanded = Raycore.expand(b, 1f0) + @test b_expanded.p_min == Point3f(-1, -1, -1) + @test b_expanded.p_max == Point3f(3, 4, 5) + + # Maximum extent (longest axis) + @test Raycore.maximum_extent(b) == 3 # z-axis is longest + + b2 = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(5, 2, 3)) + @test Raycore.maximum_extent(b2) == 1 # x-axis is longest +end + +@testset "Bounds2 area" begin + b = Raycore.Bounds2(Point2f(0, 0), Point2f(3, 4)) + @test Raycore.area(b) == 12f0 +end + +@testset "Bounds lerp and offset" begin + b = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(10, 10, 10)) + + # Lerp + p_lerped = Raycore.lerp(b, Point3f(0.5, 0.5, 0.5)) + @test p_lerped == Point3f(-4.5, -4.5, -4.5) + + # Offset + p = Point3f(5, 5, 5) + offset_result = Raycore.offset(b, p) + @test offset_result == Point3f(0.5, 0.5, 0.5) + + # Edge case: degenerate bounds + b_degenerate = Raycore.Bounds3(Point3f(5, 5, 5), Point3f(5, 5, 5)) + offset_degenerate = Raycore.offset(b_degenerate, Point3f(5, 5, 5)) + @test offset_degenerate == Point3f(0, 0, 0) +end + +@testset "Bounding sphere" begin + b = Raycore.Bounds3(Point3f(0, 0, 0), Point3f(2, 2, 2)) + center, radius = Raycore.bounding_sphere(b) + @test center == Point3f(1, 1, 1) + @test radius ≈ sqrt(3.0f0) +end + +@testset "Ray-Bounds intersection" begin + b = Raycore.Bounds3(Point3f(1), Point3f(2)) + + # Ray hitting the bounds + r1 = Raycore.Ray(o = Point3f(0), d = Vec3f(1)) + hit, t0, t1 = Raycore.intersect(b, r1) + @test hit + @test t0 ≈ 1f0 + @test t1 ≈ 2f0 + + # Ray missing the bounds + r2 = Raycore.Ray(o = Point3f(0), d = Vec3f(1, 0, 0)) + hit, t0, t1 = Raycore.intersect(b, r2) + @test !hit + + # Ray inside the bounds + r3 = Raycore.Ray(o = Point3f(1.5), d = Vec3f(1, 1, 0)) + hit, t0, t1 = Raycore.intersect(b, r3) + @test hit + @test t0 ≈ 0f0 + + # Test with precomputed inv_dir and dir_is_negative + inv_dir = 1f0 ./ r1.d + dir_is_negative = Raycore.is_dir_negative(r1.d) + @test Raycore.intersect_p(b, r1, inv_dir, dir_is_negative) + + inv_dir2 = 1f0 ./ r2.d + dir_is_negative2 = Raycore.is_dir_negative(r2.d) + @test !Raycore.intersect_p(b, r2, inv_dir2, dir_is_negative2) +end +@testset "Test Bounds2 iteration" begin + b = Raycore.Bounds2(Point2f(1f0, 3f0), Point2f(4f0, 4f0)) + targets = [ + Point2f(1f0, 3f0), Point2f(2f0, 3f0), Point2f(3f0, 3f0), Point2f(4f0, 3f0), + Point2f(1f0, 4f0), Point2f(2f0, 4f0), Point2f(3f0, 4f0), Point2f(4f0, 4f0), + ] + @test length(b) == 8 + for (p, t) in zip(b, targets) + @test p == t + end + + b = Raycore.Bounds2(Point2f(-1f0), Point2f(1f0)) + targets = [ + Point2f(-1f0, -1f0), Point2f(0f0, -1f0), Point2f(1f0, -1f0), + Point2f(-1f0, 0f0), Point2f(0f0, 0f0), Point2f(1f0, 0f0), + Point2f(-1f0, 1f0), Point2f(0f0, 1f0), Point2f(1f0, 1f0), + ] + @test length(b) == 9 + for (p, t) in zip(b, targets) + @test p == t + end +end diff --git a/test/gpu-threading-benchmarks.jl b/test/gpu-threading-benchmarks.jl index 90d0514..010bf2e 100644 --- a/test/gpu-threading-benchmarks.jl +++ b/test/gpu-threading-benchmarks.jl @@ -1,34 +1,18 @@ -using GeometryBasics, LinearAlgebra, RayCaster, BenchmarkTools -using ImageShow -using Makie -using KernelAbstractions -import KernelAbstractions as KA -using KernelAbstractions.Extras.LoopInfo: @unroll -using AMDGPU - -ArrayType = ROCArray +using GeometryBasics, LinearAlgebra, Raycore, BenchmarkTools + # using CUDA # ArrayType = CuArray -include("./../src/gpu-support.jl") - LowSphere(radius, contact=Point3f(0)) = Sphere(contact .+ Point3f(0, 0, radius), radius) function tmesh(prim, material) - prim = prim isa Sphere ? Tesselation(prim, 64) : prim - mesh = normal_mesh(prim) - m = RayCaster.create_triangle_mesh(mesh) - return RayCaster.GeometricPrimitive(m, material) + return normal_mesh(prim) end -material_red = RayCaster.MatteMaterial( - RayCaster.ConstantTexture(RayCaster.RGBSpectrum(0.796f0, 0.235f0, 0.2f0)), - RayCaster.ConstantTexture(0.0f0), -) - begin + material_red = nothing s1 = tmesh(LowSphere(0.5f0), material_red) s2 = tmesh(LowSphere(0.3f0, Point3f(0.5, 0.5, 0)), material_red) s3 = tmesh(LowSphere(0.3f0, Point3f(-0.5, 0.5, 0)), material_red) @@ -38,66 +22,9 @@ begin back = tmesh(Rect3f(Vec3f(-5, -3, 0), Vec3f(10, 0.01, 10)), material_red) l = tmesh(Rect3f(Vec3f(-2, -5, 0), Vec3f(0.01, 10, 10)), material_red) r = tmesh(Rect3f(Vec3f(2, -5, 0), Vec3f(0.01, 10, 10)), material_red) - bvh = RayCaster.BVHAccel([s1, s2, s3, s4, ground, back, l, r]); - res = 512 - resolution = Point2f(res) - f = RayCaster.LanczosSincFilter(Point2f(1.0f0), 3.0f0) - film = RayCaster.Film(resolution, - RayCaster.Bounds2(Point2f(0.0f0), Point2f(1.0f0)), - f, 1.0f0, 1.0f0, - "shadows_sppm_res.png", - ) - screen_window = RayCaster.Bounds2(Point2f(-1), Point2f(1)) - cam = RayCaster.PerspectiveCamera( - RayCaster.look_at(Point3f(0, 4, 2), Point3f(0, -4, -1), Vec3f(0, 0, 1)), - screen_window, 0.0f0, 1.0f0, 0.0f0, 1.0f6, 45.0f0, film, - ) - lights = ( - # RayCaster.PointLight(Vec3f(0, -1, 2), RayCaster.RGBSpectrum(22.0f0)), - RayCaster.PointLight(Vec3f(0, 0, 2), RayCaster.RGBSpectrum(10.0f0)), - RayCaster.PointLight(Vec3f(0, 3, 3), RayCaster.RGBSpectrum(25.0f0)), - ) - img = zeros(RGBf, res, res) + bvh = Raycore.BVHAccel([s1, s2, s3, s4, ground, back, l, r]); end -@inline function get_camera_sample(p_raster::Point2) - p_film = p_raster .+ rand(Point2f) - p_lens = rand(Point2f) - RayCaster.CameraSample(p_film, p_lens, rand(Float32)) -end - -# ray = RayCaster.Ray(o=Point3f(0.5, 0.5, 1.0), d=Vec3f(0.0, 0.0, -1.0)) -# l = RayCaster.RGBSpectrum(0.0f0) -# open("test3.llvm", "w") do io -# code_llvm(io, simple_shading, typeof.((bvh, bvh.primitives[1], RayCaster.RayDifferentials(ray), RayCaster.SurfaceInteraction(), l, 1, 1, lights))) -# end - -@inline function trace_pixel(camera, scene, xy) - pixel = Point2f(Tuple(xy)) - s = RayCaster.UniformSampler(8) - camera_sample = @inline RayCaster.get_camera_sample(s, pixel) - ray, ω = RayCaster.generate_ray_differential(camera, camera_sample) - if ω > 0.0f0 - l = @inline RayCaster.li(s, 5, ray, scene, 1) - end - return l -end - -@kernel function ka_trace_image!(img, camera, scene) - xy = @index(Global, Cartesian) - if checkbounds(Bool, img, xy) - l = trace_pixel(camera, scene, xy) - @_inbounds img[xy] = RGBf(l.c...) - end -end - -function launch_trace_image!(img, camera, scene) - backend = KA.get_backend(img) - kernel! = ka_trace_image!(backend) - kernel!(img, camera, scene, lights, ndrange=size(img), workgroupsize=(16, 16)) - KA.synchronize(backend) - return img -end # using AMDGPU # ArrayType = ROCArray # using CUDA @@ -212,13 +139,13 @@ v3 = Vec3f(0.0, 1.0, 0.0) ray_origin = Vec3f(0.5, 0.5, 1.0) ray_direction = Vec3f(0.0, 0.0, -1.0) -using RayCaster: Normal3f -m = RayCaster.create_triangle_mesh(RayCaster.ShapeCore(), UInt32[1, 2, 3], Point3f[v1, v2, v3], [Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0)]) +using Raycore: Normal3f +m = Raycore.TriangleMesh(Raycore.ShapeCore(), UInt32[1, 2, 3], Point3f[v1, v2, v3], [Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0)]) -t = RayCaster.Triangle(m, 1) -r = RayCaster.Ray(o=Point3f(ray_origin), d=ray_direction) -RayCaster.intersect_p(t, r) -RayCaster.intersect_triangle(r.o, r.d, t.vertices...) +t = Raycore.Triangle(m, 1) +r = Raycore.Ray(o=Point3f(ray_origin), d=ray_direction) +Raycore.intersect_p(t, r) +Raycore.intersect_triangle(r.o, r.d, t.vertices...) # function launch_trace_image_ir!(img, camera, bvh, lights) # backend = KA.get_backend(img) @@ -232,20 +159,37 @@ RayCaster.intersect_triangle(r.o, r.d, t.vertices...) # return img # end -ray = RayCaster.RayDifferentials(RayCaster.Ray(o=Point3f(0.5, 0.5, 1.0), d=Vec3f(0.0, 0.0, -1.0))) +ray = Raycore.RayDifferentials(Raycore.Ray(o=Point3f(0.5, 0.5, 1.0), d=Vec3f(0.0, 0.0, -1.0))) open("li.llvm", "w") do io - code_llvm(io, RayCaster.li, typeof.((RayCaster.UniformSampler(8), 5, ray, scene, 1))) + code_llvm(io, Raycore.li, typeof.((Raycore.UniformSampler(8), 5, ray, scene, 1))) end open("li-wt.jl", "w") do io - code_warntype(io, RayCaster.li, typeof.((RayCaster.UniformSampler(8), 5, ray, scene, 1))) + code_warntype(io, Raycore.li, typeof.((Raycore.UniformSampler(8), 5, ray, scene, 1))) +end + +camera_sample = Raycore.get_camera_sample(integrator.sampler, Point2f(512)) +ray, ω = Raycore.generate_ray_differential(integrator.camera, camera_sample) + + +ray = Raycore.Ray(o=Point3f(0.0, 0.0, 2.0), d=Vec3f(0.0, 0.0, -1.0)) +function test(results, bvh, ray) + for i in 1:100000 + results[i] = Raycore.any_hit(bvh, ray, PerfNTuple) + end + return results end -camera_sample = RayCaster.get_camera_sample(integrator.sampler, Point2f(512)) -ray, ω = RayCaster.generate_ray_differential(integrator.camera, camera_sample) +@profview test(results, bvh, ray) +@btime Raycore.closest_hit(bvh, ray) +results = Vector{Tuple{Bool, Raycore.Triangle, Float32, Point3f}}(undef, 100000); +@btime test(results, bvh, ray); + +@btime Raycore.any_hit(bvh, ray) -@btime RayCaster.intersect_p(bvh, ray) -@btime RayCaster.intersect!(bvh, ray) +@code_typed Raycore.traverse_bvh(Raycore.any_hit_callback, bvh, ray, Raycore.MemAllocator()) + +sizeof(zeros(Raycore.MVector{64,Int32})) ### # Int32 always @@ -253,10 +197,38 @@ ray, ω = RayCaster.generate_ray_differential(integrator.camera, camera_sample) # Tuple instead of vector for nodes_to_visit # 43.400 μs (1 allocation: 624 bytes) # AFTER GPU rework -# intersect! +# closest_hit # 40.500 μs (1 allocation: 368 bytes) # intersect_p # 11.500 μs (0 allocations: 0 bytes) ### LinearBVHLeaf as one type # 5.247460 seconds (17.55 k allocations: 19.783 MiB, 46 lock conflicts) + +struct PerfNTuple{N,T} + data::NTuple{N,T} +end + +@generated function Raycore._setindex(r::PerfNTuple{N,T}, idx::IT, value::T) where {N,T, IT <: Integer} + expr = Expr(:tuple) + for i in 1:N + idxt = IT(i) + push!(expr.args, :(idx === $idxt ? value : r.data[$idxt])) + end + return :($(PerfNTuple)($expr)) +end + +Base.@propagate_inbounds Base.getindex(r::PerfNTuple, idx::Integer) = r.data[idx] + +@generated function Raycore._allocate(::Type{PerfNTuple}, ::Type{T}, ::Val{N}) where {T,N} + expr = Expr(:tuple) + for i in 1:N + push!(expr.args, :($(T(0)))) + end + return :($(PerfNTuple){$N, $T}($expr)) +end + +m = Raycore._allocate(PerfNTuple, Int32, Val(64)) +m2 = Raycore._setindex(m, 10, Int32(42)) + +@btime Raycore.any_hit(bvh, ray, PerfNTuple) diff --git a/test/runtests.jl b/test/runtests.jl index fef5a3c..c9dd81f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,40 +1,17 @@ using Test using GeometryBasics using LinearAlgebra -using RayCaster -using FileIO -using ImageCore +using Raycore +using JET -include("test_intersection.jl") - -@testset "Test Bounds2 iteration" begin - b = RayCaster.Bounds2(Point2f(1f0, 3f0), Point2f(4f0, 4f0)) - targets = [ - Point2f(1f0, 3f0), Point2f(2f0, 3f0), Point2f(3f0, 3f0), Point2f(4f0, 3f0), - Point2f(1f0, 4f0), Point2f(2f0, 4f0), Point2f(3f0, 4f0), Point2f(4f0, 4f0), - ] - @test length(b) == 8 - for (p, t) in zip(b, targets) - @test p == t +@testset "Raycore Tests" begin + @testset "Intersection" begin + include("test_intersection.jl") end - - b = RayCaster.Bounds2(Point2f(-1f0), Point2f(1f0)) - targets = [ - Point2f(-1f0, -1f0), Point2f(0f0, -1f0), Point2f(1f0, -1f0), - Point2f(-1f0, 0f0), Point2f(0f0, 0f0), Point2f(1f0, 0f0), - Point2f(-1f0, 1f0), Point2f(0f0, 1f0), Point2f(1f0, 1f0), - ] - @test length(b) == 9 - for (p, t) in zip(b, targets) - @test p == t + @testset "Type Stability" begin + include("test_type_stability.jl") + end + @testset "Bounds" begin + include("bounds.jl") end -end - -@testset "Sphere bound" begin - core = RayCaster.ShapeCore(RayCaster.translate(Vec3f(0)), false) - s = RayCaster.Sphere(core, 1f0, -1f0, 1f0, 360f0) - - sb = RayCaster.object_bound(s) - @test sb[1] == Point3f(-1f0) - @test sb[2] == Point3f(1f0) end diff --git a/test/test_intersection.jl b/test/test_intersection.jl index 1d6a5a3..09a0ef0 100644 --- a/test/test_intersection.jl +++ b/test/test_intersection.jl @@ -1,150 +1,83 @@ @testset "Ray-Bounds intersection" begin - b = RayCaster.Bounds3(Point3f(1), Point3f(2)) - b_neg = RayCaster.Bounds3(Point3f(-2), Point3f(-1)) - r0 = RayCaster.Ray(o = Point3f(0), d = Vec3f(1, 0, 0)) - r1 = RayCaster.Ray(o = Point3f(0), d = Vec3f(1)) - ri = RayCaster.Ray(o = Point3f(1.5), d = Vec3f(1, 1, 0)) + b = Raycore.Bounds3(Point3f(1), Point3f(2)) + b_neg = Raycore.Bounds3(Point3f(-2), Point3f(-1)) + r0 = Raycore.Ray(o = Point3f(0), d = Vec3f(1, 0, 0)) + r1 = Raycore.Ray(o = Point3f(0), d = Vec3f(1)) + ri = Raycore.Ray(o = Point3f(1.5), d = Vec3f(1, 1, 0)) - r, t0, t1 = RayCaster.intersect(b, r1) + r, t0, t1 = Raycore.intersect(b, r1) @test r && t0 ≈ 1f0 && t1 ≈ 2f0 - r, t0, t1 = RayCaster.intersect(b, r0) + r, t0, t1 = Raycore.intersect(b, r0) @test !r && t0 ≈ 0f0 && t1 ≈ 0f0 - r, t0, t1 = RayCaster.intersect(b, ri) + r, t0, t1 = Raycore.intersect(b, ri) @test r && t0 ≈ 0f0 && t1 ≈ 0.5f0 # Test intersection with precomputed direction reciprocal. inv_dir = 1f0 ./ r1.d - dir_is_negative = RayCaster.is_dir_negative(r1.d) - @test RayCaster.intersect_p(b, r1, inv_dir, dir_is_negative) - @test !RayCaster.intersect_p(b_neg, r1, inv_dir, dir_is_negative) + dir_is_negative = Raycore.is_dir_negative(r1.d) + @test Raycore.intersect_p(b, r1, inv_dir, dir_is_negative) + @test !Raycore.intersect_p(b_neg, r1, inv_dir, dir_is_negative) end -@testset "Ray-Sphere intersection" begin - # Sphere at the origin. - core = RayCaster.ShapeCore(RayCaster.Transformation(), false) - s = RayCaster.Sphere(core, 1f0, 360f0) - - r = RayCaster.Ray(o = Point3f(0, -2, 0), d = Vec3f(0, 1, 0)) - i, t, interaction = RayCaster.intersect(s, r, false) - ip = RayCaster.intersect_p(s, r, false) - @test i == ip - @test t ≈ 1f0 - @test RayCaster.apply(r, t) ≈ Point3f(0, -1, 0) # World intersection. - @test interaction.core.p ≈ Point3f(0, -1, 0) # Object intersection. - @test interaction.core.n ≈ RayCaster.Normal3f(0, -1, 0) - @test norm(interaction.core.n) ≈ 1f0 - @test norm(interaction.shading.n) ≈ 1f0 - # Spawn new ray from intersection. - spawn_direction = Vec3f(0, -1, 0) - spawned_ray = RayCaster.spawn_ray(interaction, spawn_direction) - @test spawned_ray.o ≈ Point3f(interaction.core.p) - @test spawned_ray.d ≈ Vec3f(spawn_direction) - i, t, interaction = RayCaster.intersect(s, spawned_ray, false) - @test !i - - r = RayCaster.Ray(o = Point3f(0, 0, -2), d = Vec3f(0, 0, 1)) - i, t, interaction = RayCaster.intersect(s, r, false) - ip = RayCaster.intersect_p(s, r, false) - @test i == ip - @test t ≈ 1f0 - @test RayCaster.apply(r, t) ≈ Point3f(0, 0, -1) # World intersection. - @test interaction.core.p ≈ Point3f(0, 0, -1) # Object intersection. - @test interaction.core.n ≈ RayCaster.Normal3f(0, 0, -1) - @test norm(interaction.core.n) ≈ 1f0 - @test norm(interaction.shading.n) ≈ 1f0 - - # Test ray inside a sphere. - r0 = RayCaster.Ray(o = Point3f(0), d = Vec3f(0, 1, 0)) - i, t, interaction = RayCaster.intersect(s, r0, false) - @test i - @test t ≈ 1f0 - @test RayCaster.apply(r0, t) ≈ Point3f(0f0, 1f0, 0f0) - @test interaction.core.n ≈ RayCaster.Normal3f(0, 1, 0) - @test norm(interaction.core.n) ≈ 1f0 - @test norm(interaction.shading.n) ≈ 1f0 - - # Test ray at the edge of the sphere. - ray_at_edge = RayCaster.Ray(o = Point3f(0, -1, 0), d = Vec3f(0, -1, 0)) - i, t, interaction = RayCaster.intersect(s, ray_at_edge, false) - @test i - @test t ≈ 0f0 - @test RayCaster.apply(ray_at_edge, t) ≈ Point3f(0, -1, 0) - @test interaction.core.p ≈ Point3f(0, -1, 0) - @test interaction.core.n ≈ RayCaster.Normal3f(0, -1, 0) - - # Translated sphere. - core = RayCaster.ShapeCore(RayCaster.translate(Vec3f(0, 2, 0)), false) - s = RayCaster.Sphere(core, 1f0, 360f0) - r = RayCaster.Ray(o = Point3f(0, 0, 0), d = Vec3f(0, 1, 0)) - - i, t, interaction = RayCaster.intersect(s, r, false) - ip = RayCaster.intersect_p(s, r, false) - @test i == ip - @test t ≈ 1f0 - @test RayCaster.apply(r, t) ≈ Point3f(0, 1, 0) # World intersection. - @test interaction.core.p ≈ Point3f(0, 1, 0) # Object intersection. - @test interaction.core.n ≈ RayCaster.Normal3f(0, -1, 0) -end +# Note: Ray-Sphere intersection tests moved to Trace.jl +# Raycore no longer has Sphere shapes - only low-level triangle intersection @testset "Test triangle" begin - core = RayCaster.ShapeCore(RayCaster.translate(Vec3f(0, 0, 2)), false) - triangles = RayCaster.create_triangle_mesh( - core, + triangles = Raycore.TriangleMesh( + [Point3f(0, 0, 2), Point3f(1, 0, 2), Point3f(1, 1, 2)], UInt32[1, 2, 3], - [Point3f(0, 0, 0), Point3f(1, 0, 0), Point3f(1, 1, 0)], - [RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1)], + [Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1)], ) - triangle = RayCaster.Triangle(triangles, 1) - tv = RayCaster.vertices(triangle) + triangle = Raycore.Triangle(triangles, 1) + tv = Raycore.vertices(triangle) a = norm(tv[1] - tv[2])^2 * 0.5f0 - @test RayCaster.area(triangle) ≈ a + @test Raycore.area(triangle) ≈ a - target_wb = RayCaster.Bounds3(Point3f(0, 0, 2), Point3f(1, 1, 2)) + target_wb = Raycore.Bounds3(Point3f(0, 0, 2), Point3f(1, 1, 2)) # In the refactored API, object_bound returns world bounds since transformation is applied during creation - @test RayCaster.object_bound(triangle) ≈ target_wb + @test Raycore.object_bound(triangle) ≈ target_wb # Test ray intersection - API has changed: intersect now returns (Bool, Float32, Point3f) with barycentric coords - ray = RayCaster.Ray(o = Point3f(0, 0, -2), d = Vec3f(0, 0, 1)) - intersects_p = RayCaster.intersect_p(triangle, ray) - intersects, t_hit, bary_coords = RayCaster.intersect(triangle, ray) + ray = Raycore.Ray(o = Point3f(0, 0, -2), d = Vec3f(0, 0, 1)) + intersects_p = Raycore.intersect_p(triangle, ray) + intersects, t_hit, bary_coords = Raycore.intersect(triangle, ray) @test intersects_p == intersects == true @test t_hit ≈ 4f0 - @test RayCaster.apply(ray, t_hit) ≈ Point3f(0, 0, 2) + @test Raycore.apply(ray, t_hit) ≈ Point3f(0, 0, 2) # Barycentric coordinates for vertex 0 (corner hit) @test bary_coords ≈ Point3f(1, 0, 0) - + # Test ray intersection (different point). - ray = RayCaster.Ray(o = Point3f(0.5, 0.25, 0), d = Vec3f(0, 0, 1)) - intersects_p = RayCaster.intersect_p(triangle, ray) - intersects, t_hit, bary_coords = RayCaster.intersect(triangle, ray) + ray = Raycore.Ray(o = Point3f(0.5, 0.25, 0), d = Vec3f(0, 0, 1)) + intersects_p = Raycore.intersect_p(triangle, ray) + intersects, t_hit, bary_coords = Raycore.intersect(triangle, ray) @test intersects_p == intersects == true @test t_hit ≈ 2f0 - @test RayCaster.apply(ray, t_hit) ≈ Point3f(0.5, 0.25, 2) + @test Raycore.apply(ray, t_hit) ≈ Point3f(0.5, 0.25, 2) end -# BVH tests with spheres removed - refactored RayCaster only supports triangle meshes in BVH +# BVH tests with spheres removed - refactored Raycore only supports triangle meshes in BVH @testset "BVH" begin # Create triangle meshes instead of spheres triangle_meshes = [] for i in 0:1:3 # Use fewer triangles for simpler test - core = RayCaster.ShapeCore(RayCaster.translate(Vec3f(i*3, i*3, 0)), false) - mesh = RayCaster.create_triangle_mesh( - core, + core = Raycore.translate(Vec3f(i*3, i*3, 0)) + mesh = Raycore.TriangleMesh( + core.([Point3f(0, 0, 0), Point3f(1, 0, 0), Point3f(1, 1, 0)]), UInt32[1, 2, 3], - [Point3f(0, 0, 0), Point3f(1, 0, 0), Point3f(1, 1, 0)], - [RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1)], + [Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1)], ) push!(triangle_meshes, mesh) end - bvh = RayCaster.BVHAccel(triangle_meshes) + bvh = Raycore.BVHAccel(triangle_meshes) # Test basic BVH functionality with triangle meshes - @test !isnothing(RayCaster.world_bound(bvh)) - + @test !isnothing(Raycore.world_bound(bvh)) + # Simple intersection test - ray = RayCaster.Ray(o = Point3f(0.5, 0.5, -1), d = Vec3f(0, 0, 1)) - intersects, interaction = RayCaster.intersect!(bvh, ray) + ray = Raycore.Ray(o = Point3f(0.5, 0.5, -1), d = Vec3f(0, 0, 1)) + intersects, interaction = Raycore.closest_hit(bvh, ray) @test intersects end @@ -154,26 +87,27 @@ end # Create triangle meshes at different z positions positions = [0, 4, 8] + vertices = [Point3f(-1, -1, 0), Point3f(1, -1, 0), Point3f(0, 1, 0)] for (i, z) in enumerate(positions) - core = RayCaster.ShapeCore(RayCaster.translate(Vec3f(0, 0, z)), false) - mesh = RayCaster.create_triangle_mesh( - core, + core = Raycore.translate(Vec3f(0, 0, z)) + vs = core.(vertices) + mesh = Raycore.TriangleMesh( + vs, UInt32[1, 2, 3], - [Point3f(-1, -1, 0), Point3f(1, -1, 0), Point3f(0, 1, 0)], - [RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1), RayCaster.Normal3f(0, 0, -1)], + [Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1), Raycore.Normal3f(0, 0, -1)], ) push!(triangle_meshes, mesh) end - bvh = RayCaster.BVHAccel(triangle_meshes) + bvh = Raycore.BVHAccel(triangle_meshes) # Test that BVH can be created and has a valid bound - bound = RayCaster.world_bound(bvh) + bound = Raycore.world_bound(bvh) @test !isnothing(bound) # Test intersection with the first triangle - ray = RayCaster.Ray(o = Point3f(0, 0, -2), d = Vec3f(0, 0, 1)) - intersects, triangle = RayCaster.intersect!(bvh, ray) + ray = Raycore.Ray(o = Point3f(0, 0, -2), d = Vec3f(0, 0, 1)) + intersects, triangle = Raycore.closest_hit(bvh, ray) @test intersects - # BVH intersect! returns Triangle object, not SurfaceInteraction - @test typeof(triangle) == RayCaster.Triangle + # BVH closest_hit returns Triangle object, not SurfaceInteraction + @test typeof(triangle) == Raycore.Triangle end diff --git a/test/test_type_stability.jl b/test/test_type_stability.jl new file mode 100644 index 0000000..c811ed1 --- /dev/null +++ b/test/test_type_stability.jl @@ -0,0 +1,451 @@ +using LinearAlgebra +using Raycore.StaticArrays +# ==================== Test Data Generators ==================== + +# Basic geometric types +gen_point3f() = Point3f(1.0f0, 2.0f0, 3.0f0) +gen_point2f() = Point2f(0.5f0, 0.5f0) +gen_vec3f() = Vec3f(0.0f0, 0.0f0, 1.0f0) +gen_normal3f() = Raycore.Normal3f(0.0f0, 0.0f0, 1.0f0) + +# Bounds +gen_bounds2() = Raycore.Bounds2(Point2f(0.0f0), Point2f(1.0f0)) +gen_bounds3() = Raycore.Bounds3(Point3f(0.0f0), Point3f(1.0f0, 1.0f0, 1.0f0)) + +# Rays +gen_ray() = Raycore.Ray(o=Point3f(0.0f0), d=Vec3f(0.0f0, 0.0f0, 1.0f0)) +gen_ray_differentials() = Raycore.RayDifferentials(o=Point3f(0.0f0), d=Vec3f(0.0f0, 0.0f0, 1.0f0)) + +# Transformations +gen_transformation() = Raycore.Transformation() +gen_transformation_translate() = Raycore.translate(Vec3f(1.0f0, 0.0f0, 0.0f0)) +gen_transformation_rotate() = Raycore.rotate_x(45.0f0) +gen_transformation_scale() = Raycore.scale(2.0f0, 2.0f0, 2.0f0) + +# Triangle +function gen_triangle() + v1 = Point3f(0.0f0, 0.0f0, 0.0f0) + v2 = Point3f(1.0f0, 0.0f0, 0.0f0) + v3 = Point3f(0.0f0, 1.0f0, 0.0f0) + n1 = Raycore.Normal3f(0.0f0, 0.0f0, 1.0f0) + uv1 = Point2f(0.0f0, 0.0f0) + uv2 = Point2f(1.0f0, 0.0f0) + uv3 = Point2f(0.0f0, 1.0f0) + Raycore.Triangle( + SVector(v1, v2, v3), + SVector(n1, n1, n1), + SVector(Vec3f(NaN), Vec3f(NaN), Vec3f(NaN)), + SVector(uv1, uv2, uv3), + UInt32(1) + ) +end + +# Triangle Mesh +function gen_triangle_mesh() + vertices = [Point3f(0, 0, 0), Point3f(1, 0, 0), Point3f(0, 1, 0)] + indices = UInt32[1, 2, 3] # 1-based indices for Julia + normals = [Raycore.Normal3f(0, 0, 1), Raycore.Normal3f(0, 0, 1), Raycore.Normal3f(0, 0, 1)] + Raycore.TriangleMesh(vertices, indices, normals) +end + +# BVH +function gen_bvh_accel() + mesh = Rect3f(Point3f(0), Vec3f(1)) + Raycore.BVHAccel([mesh], 1) +end + +# Quaternion +gen_quaternion() = Raycore.Quaternion() + +# ==================== Custom Test Macros ==================== + +""" + @test_opt_alloc expr + +Combined macro that tests both type stability (via @test_opt) and zero allocations. +This is equivalent to: + @test_opt expr + @test @allocated(expr) == 0 +""" +macro test_opt_alloc(expr) + return esc(quote + $expr # warmup + JET.@test_opt $expr + @test @allocated($expr) == 0 + end) +end + +# ==================== Bounds Tests ==================== + +@testset "Type Stability: bounds.jl" begin + @testset "Bounds2" begin + @test_opt_alloc Raycore.Bounds2() + + @test_opt_alloc Raycore.Bounds2(gen_point2f()) + + @test_opt_alloc Raycore.Bounds2c(gen_point2f(), Point2f(1.0f0, 1.0f0)) + end + + @testset "Bounds3" begin + @test_opt_alloc Raycore.Bounds3() + + @test_opt_alloc Raycore.Bounds3(gen_point3f()) + + @test_opt_alloc Raycore.Bounds3c(gen_point3f(), Point3f(2.0f0, 2.0f0, 2.0f0)) + end + + @testset "Bounds operations" begin + b1 = gen_bounds3() + b2 = Raycore.Bounds3(Point3f(0.5f0), Point3f(1.5f0, 1.5f0, 1.5f0)) + p = gen_point3f() + + @test_opt_alloc Base.:(==)(b1, b2) + @test_opt_alloc Base.:≈(b1, b2) + @test_opt_alloc Base.getindex(b1, 1) + @test_opt_alloc Raycore.is_valid(b1) + @test_opt_alloc Raycore.corner(b1, 1) + @test_opt_alloc Base.union(b1, b2) + @test_opt_alloc Base.intersect(b1, b2) + @test_opt_alloc Raycore.overlaps(b1, b2) + @test_opt_alloc Raycore.inside(b1, p) + @test_opt_alloc Raycore.inside_exclusive(b1, p) + @test_opt_alloc Raycore.expand(b1, 0.1f0) + @test_opt_alloc Raycore.diagonal(b1) + @test_opt_alloc Raycore.surface_area(b1) + @test_opt_alloc Raycore.volume(b1) + @test_opt_alloc Raycore.maximum_extent(b1) + @test_opt_alloc Raycore.sides(b1) + @test_opt_alloc Raycore.inclusive_sides(b1) + @test_opt_alloc Raycore.bounding_sphere(b1) + @test_opt_alloc Raycore.offset(b1, p) + end + + @testset "Bounds with Ray" begin + b = gen_bounds3() + r = gen_ray() + + @test_opt_alloc Raycore.intersect(b, r) + @test_opt_alloc Raycore.is_dir_negative(r.d) + + inv_dir = 1.0f0 ./ r.d + dir_neg = Raycore.is_dir_negative(r.d) + @test_opt_alloc Raycore.intersect_p(b, r, inv_dir, dir_neg) + end + + @testset "Bounds2 iteration" begin + b = gen_bounds2() + @test_opt_alloc Base.length(b) + @test_opt_alloc Base.iterate(b) + @test_opt_alloc Base.iterate(b, Int32(1)) + end + + @testset "Distance functions" begin + p1 = gen_point3f() + p2 = Point3f(2.0f0, 3.0f0, 4.0f0) + + @test_opt_alloc Raycore.distance(p1, p2) + @test_opt_alloc Raycore.distance_squared(p1, p2) + end + + @testset "Lerp functions" begin + b = gen_bounds3() + p = gen_point3f() + + @test_opt_alloc Raycore.lerp(0.0f0, 1.0f0, 0.5f0) + @test_opt_alloc Raycore.lerp(Point3f(0), Point3f(1), 0.5f0) + @test_opt_alloc Raycore.lerp(b, Point3f(0.5f0)) + end + + @testset "Bounds2 area" begin + b = gen_bounds2() + @test_opt_alloc Raycore.area(b) + end +end + +# ==================== Ray Tests ==================== + +@testset "Type Stability: ray.jl" begin + @testset "Ray construction" begin + @test_opt_alloc Raycore.Ray(o=gen_point3f(), d=gen_vec3f()) + @test_opt_alloc Raycore.Ray(o=gen_point3f(), d=gen_vec3f(), t_max=10.0f0) + @test_opt_alloc Raycore.Ray(o=gen_point3f(), d=gen_vec3f(), t_max=10.0f0, time=0.5f0) + end + + @testset "Ray copy constructor" begin + r = gen_ray() + @test_opt_alloc Raycore.Ray(r; o=Point3f(1.0f0)) + @test_opt_alloc Raycore.Ray(r; d=Vec3f(1.0f0, 0.0f0, 0.0f0)) + @test_opt_alloc Raycore.Ray(r; t_max=5.0f0) + end + + @testset "RayDifferentials construction" begin + @test_opt_alloc Raycore.RayDifferentials(o=gen_point3f(), d=gen_vec3f()) + @test_opt_alloc Raycore.RayDifferentials(gen_ray()) + end + + @testset "Ray operations" begin + r = gen_ray() + rd = gen_ray_differentials() + + @test_opt_alloc Raycore.set_direction(r, Vec3f(1.0f0, 0.0f0, 0.0f0)) + @test_opt_alloc Raycore.set_direction(rd, Vec3f(1.0f0, 0.0f0, 0.0f0)) + @test_opt_alloc Raycore.check_direction(r) + @test_opt_alloc Raycore.check_direction(rd) + @test_opt_alloc Raycore.apply(r, 1.0f0) + @test_opt_alloc Raycore.increase_hit(r, 0.5f0) + @test_opt_alloc Raycore.increase_hit(rd, 0.5f0) + end + + @testset "RayDifferentials operations" begin + rd = gen_ray_differentials() + @test_opt_alloc Raycore.scale_differentials(rd, 0.5f0) + end + + @testset "Intersection helpers" begin + t = gen_triangle() + r = gen_ray() + @test_opt_alloc Raycore.intersect_p!(t, r) + end +end + +# ==================== Transformation Tests ==================== + +@testset "Type Stability: transformations.jl" begin + @testset "Transformation construction" begin + @test_opt_alloc Raycore.Transformation() + @test_opt_alloc Raycore.Transformation(Mat4f(I)) + end + + @testset "Basic transformations" begin + @test_opt_alloc Raycore.translate(gen_vec3f()) + @test_opt_alloc Raycore.scale(2.0f0, 2.0f0, 2.0f0) + @test_opt_alloc Raycore.rotate_x(45.0f0) + @test_opt_alloc Raycore.rotate_y(45.0f0) + @test_opt_alloc Raycore.rotate_z(45.0f0) + @test_opt_alloc Raycore.rotate(45.0f0, Vec3f(0, 0, 1)) + end + + @testset "Transformation operations" begin + t1 = gen_transformation_translate() + t2 = gen_transformation_rotate() + + @test_opt_alloc Raycore.is_identity(t1) + @test_opt_alloc Base.transpose(t1) + @test_opt_alloc Base.inv(t1) + @test_opt_alloc Base.:(==)(t1, t2) + @test_opt_alloc Base.:≈(t1, t2) + @test_opt_alloc Base.:*(t1, t2) + end + + @testset "Transformation application" begin + t = gen_transformation_translate() + + @test_opt_alloc t(gen_point3f()) + @test_opt_alloc t(gen_vec3f()) + @test_opt_alloc t(gen_normal3f()) + @test_opt_alloc t(gen_bounds3()) + end + + @testset "Advanced transformations" begin + @test_opt_alloc Raycore.look_at(Point3f(0, 0, 5), Point3f(0), Vec3f(0, 1, 0)) + @test_opt_alloc Raycore.perspective(60.0f0, 0.1f0, 100.0f0) + end + + @testset "Transformation properties" begin + t = gen_transformation_scale() + @test_opt_alloc Raycore.has_scale(t) + @test_opt_alloc Raycore.swaps_handedness(t) + end + + @testset "Transformation with Ray" begin + t = gen_transformation_translate() + r = gen_ray() + rd = gen_ray_differentials() + + @test_opt_alloc Raycore.apply(t, r) + @test_opt_alloc Raycore.apply(t, rd) + end + + @testset "Quaternion" begin + @test_opt_alloc Raycore.Quaternion() + @test_opt_alloc Raycore.Quaternion(gen_transformation()) + + q1 = gen_quaternion() + q2 = Raycore.Quaternion(Vec3f(1, 0, 0), 0.5f0) + + @test_opt_alloc Base.:+(q1, q2) + @test_opt_alloc Base.:-(q1, q2) + @test_opt_alloc Base.:/(q1, 2.0f0) + @test_opt_alloc Base.:*(q1, 2.0f0) + @test_opt_alloc LinearAlgebra.dot(q1, q2) + @test_opt_alloc LinearAlgebra.normalize(q1) + @test_opt_alloc Raycore.Transformation(q1) + @test_opt_alloc Raycore.slerp(q1, q2, 0.5f0) + end +end + +# ==================== Math Tests ==================== + +@testset "Type Stability: math.jl" begin + @testset "Sampling functions" begin + u = gen_point2f() + + @test_opt_alloc Raycore.concentric_sample_disk(u) + @test_opt_alloc Raycore.cosine_sample_hemisphere(u) + @test_opt_alloc Raycore.uniform_sample_sphere(u) + @test_opt_alloc Raycore.uniform_sample_cone(u, 0.5f0) + @test_opt_alloc Raycore.uniform_sample_cone(u, 0.5f0, Vec3f(1,0,0), Vec3f(0,1,0), Vec3f(0,0,1)) + end + + @testset "PDF functions" begin + @test_opt_alloc Raycore.uniform_sphere_pdf() + @test_opt_alloc Raycore.uniform_cone_pdf(0.5f0) + end + + @testset "Shading coordinate system" begin + w = gen_vec3f() + + @test_opt_alloc Raycore.cos_θ(w) + @test_opt_alloc Raycore.sin_θ2(w) + @test_opt_alloc Raycore.sin_θ(w) + @test_opt_alloc Raycore.tan_θ(w) + @test_opt_alloc Raycore.cos_ϕ(w) + @test_opt_alloc Raycore.sin_ϕ(w) + end + + @testset "Vector operations" begin + wo = gen_vec3f() + n = Vec3f(0, 1, 0) + + @test_opt_alloc Raycore.reflect(wo, n) + @test_opt_alloc Raycore.face_forward(n, wo) + end + + @testset "Coordinate system" begin + v = gen_vec3f() + @test_opt_alloc Raycore.coordinate_system(v) + end + + @testset "Spherical functions" begin + @test_opt_alloc Raycore.spherical_direction(0.5f0, 0.5f0, 1.0f0) + @test_opt_alloc Raycore.spherical_direction(0.5f0, 0.5f0, 1.0f0, Vec3f(1,0,0), Vec3f(0,1,0), Vec3f(0,0,1)) + + v = gen_vec3f() + @test_opt_alloc Raycore.spherical_θ(v) + @test_opt_alloc Raycore.spherical_ϕ(v) + end + + @testset "Helper functions" begin + v = gen_vec3f() + @test_opt_alloc Raycore.get_orthogonal_basis(v) + + t = gen_triangle() + @test_opt_alloc Raycore.random_triangle_point(t) + end + + @testset "sum_mul" begin + a = Point3f(0.2f0, 0.3f0, 0.5f0) + b = Raycore.StaticArrays.SVector(Point3f(0,0,0), Point3f(1,0,0), Point3f(0,1,0)) + @test_opt_alloc Raycore.sum_mul(a, b) + end +end + +@testset "Type Stability: triangle_mesh.jl" begin + @testset "TriangleMesh construction" begin + vertices = [Point3f(0, 0, 0), Point3f(1, 0, 0), Point3f(0, 1, 0)] + indices = UInt32[0, 1, 2] + normals = [Raycore.Normal3f(0, 0, 1), Raycore.Normal3f(0, 0, 1), Raycore.Normal3f(0, 0, 1)] + + @test_opt Raycore.TriangleMesh(vertices, indices, normals) + @test_opt Raycore.TriangleMesh(vertices, indices) + end + + @testset "Triangle construction" begin + mesh = gen_triangle_mesh() + @test_opt_alloc Raycore.Triangle(mesh, 1, UInt32(1)) + end + + @testset "Triangle operations" begin + t = gen_triangle() + + @test_opt_alloc Raycore.vertices(t) + @test_opt_alloc Raycore.normals(t) + @test_opt_alloc Raycore.tangents(t) + @test_opt_alloc Raycore.uvs(t) + @test_opt_alloc Raycore.area(t) + @test_opt_alloc Raycore.object_bound(t) + @test_opt_alloc Raycore.world_bound(t) + end + + @testset "Triangle intersection" begin + t = gen_triangle() + r = gen_ray() + + @test_opt_alloc Raycore.intersect(t, r) + @test_opt_alloc Raycore.intersect_p(t, r) + @test_opt_alloc Raycore.intersect_triangle(t.vertices, r) + end + + @testset "Triangle helper functions" begin + t = gen_triangle() + r = gen_ray() + + # Test _to_ray_coordinate_space + @test_opt_alloc Raycore._to_ray_coordinate_space(t.vertices, r) + + # Test partial_derivatives + @test_opt_alloc Raycore.partial_derivatives(t, t.vertices, t.uv) + + # Test normal_derivatives + @test_opt_alloc Raycore.normal_derivatives(t, t.uv) + end + + @testset "Triangle utilities" begin + t = gen_triangle() + @test_opt_alloc Raycore.is_degenerate(t.vertices) + end +end + +# ==================== BVH Tests ==================== + +@testset "Type Stability: bvh.jl" begin + @testset "BVHPrimitiveInfo" begin + b = gen_bounds3() + @test_opt_alloc Raycore.BVHPrimitiveInfo(UInt32(1), b) + end + + @testset "BVHNode construction" begin + b = gen_bounds3() + @test_opt_alloc Raycore.BVHNode(UInt32(0), UInt32(1), b) + end + + @testset "LinearBVH construction" begin + b = gen_bounds3() + @test_opt_alloc Raycore.LinearBVHLeaf(b, UInt32(0), UInt32(1)) + @test_opt_alloc Raycore.LinearBVHInterior(b, UInt32(1), UInt8(0)) + end + + @testset "BVH operations" begin + bvh = gen_bvh_accel() + r = gen_ray() + + @test_opt Raycore.world_bound(bvh) + @test_opt Raycore.closest_hit(bvh, r) + @test_opt Raycore.any_hit(bvh, r) + end + + @testset "Ray grid generation" begin + bvh = gen_bvh_accel() + direction = Vec3f(0, 0, 1) + # generate_ray_grid allocates - needs optimization + @test_opt Raycore.generate_ray_grid(bvh, direction, 10) + end +end + +# ==================== Kernels Tests ==================== + +@testset "Type Stability: kernels.jl" begin + @testset "RayHit construction" begin + @test_opt_alloc Raycore.RayHit(true, gen_point3f(), UInt32(1)) + end +end diff --git a/test/type-stability.jl b/test/type-stability.jl deleted file mode 100644 index 830afab..0000000 --- a/test/type-stability.jl +++ /dev/null @@ -1,28 +0,0 @@ -using RayCaster, GeometryBasics, StaticArrays - -code_warntype(RayCaster._to_ray_coordinate_space, (SVector{3,Point3f}, RayCaster.Ray)) -code_warntype(RayCaster.∂p, (RayCaster.Triangle, SVector{3,Point3f}, SVector{3,Point2f})) -code_warntype(RayCaster.∂n, (RayCaster.Triangle, SVector{3,Point2f})) -code_warntype(RayCaster.intersect, (RayCaster.Triangle, RayCaster.Ray, Bool)) -code_warntype(RayCaster.intersect_triangle, (RayCaster.Triangle, RayCaster.Ray)) -code_warntype(RayCaster.intersect_triangle, (RayCaster.Triangle, RayCaster.Ray)) -code_warntype(RayCaster.intersect_p, (RayCaster.Triangle, RayCaster.Ray)) - -########################## -########################## -########################## -# Random benchmarks -v1 = Vec3f(0.0, 0.0, 0.0) -v2 = Vec3f(1.0, 0.0, 0.0) -v3 = Vec3f(0.0, 1.0, 0.0) - -ray_origin = Vec3f(0.5, 0.5, 1.0) -ray_direction = Vec3f(0.0, 0.0, -1.0) - -using RayCaster: Normal3f -m = RayCaster.create_triangle_mesh(RayCaster.ShapeCore(), UInt32[1, 2, 3], Point3f[v1, v2, v3], [Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0), Normal3f(0.0, 0.0, 1.0)]) - -t = RayCaster.Triangle(m, 1) -r = RayCaster.Ray(o=Point3f(ray_origin), d=ray_direction) -RayCaster.intersect_p(t, r) -RayCaster.intersect_triangle(r.o, r.d, t.vertices...)