From 61481f86f316957914ca0c1ba1a637b74fe26e6a Mon Sep 17 00:00:00 2001 From: Alfred Wong Date: Fri, 19 Mar 2021 05:24:30 +0000 Subject: [PATCH] Test rework (#23) * split tests up by test set * JuliaLang testset rewrite * mv Benchmarks, Diagnostics, TestData to test/ * rm src/Data/Spectra.jl * rm Precompile.jl src/Sysimage.jl * rm src/Miscellaneous.jl * split off test/Project.toml * Revert "rm Precompile.jl src/Sysimage.jl" This reverts commit 55e124d54b824a32992601d2fb03d1786a8c97d2. * fix imports/includes in vis.md * skip glasscat docstring generation for CI builds --- Project.toml | 8 - deps/utils.jl | 27 +- docs/Project.toml | 2 + docs/src/vis.md | 35 +- src/Data/Spectra.jl | 26 - src/Miscellaneous.jl | 1138 ----------------- src/OpticSim.jl | 7 - {src => test}/Benchmarks.jl | 9 +- {src => test}/Diagnostics.jl | 10 +- test/Project.toml | 13 + {src => test}/TestData.jl | 4 +- test/runtests.jl | 2201 +------------------------------- test/testsets/Allocations.jl | 6 + test/testsets/BVH.jl | 35 + test/testsets/Comparison.jl | 278 ++++ test/testsets/Emitters.jl | 3 + test/testsets/Examples.jl | 4 + test/testsets/General.jl | 239 ++++ test/testsets/Intersection.jl | 1215 ++++++++++++++++++ test/testsets/JuliaLang.jl | 50 + test/testsets/Lenses.jl | 81 ++ test/testsets/SurfaceDefs.jl | 220 ++++ test/testsets/TestData.jl | 3 + test/testsets/Visualization.jl | 15 + 24 files changed, 2222 insertions(+), 3407 deletions(-) delete mode 100644 src/Data/Spectra.jl delete mode 100644 src/Miscellaneous.jl rename {src => test}/Benchmarks.jl (93%) rename {src => test}/Diagnostics.jl (99%) create mode 100644 test/Project.toml rename {src => test}/TestData.jl (99%) create mode 100644 test/testsets/Allocations.jl create mode 100644 test/testsets/BVH.jl create mode 100644 test/testsets/Comparison.jl create mode 100644 test/testsets/Emitters.jl create mode 100644 test/testsets/Examples.jl create mode 100644 test/testsets/General.jl create mode 100644 test/testsets/Intersection.jl create mode 100644 test/testsets/JuliaLang.jl create mode 100644 test/testsets/Lenses.jl create mode 100644 test/testsets/SurfaceDefs.jl create mode 100644 test/testsets/TestData.jl create mode 100644 test/testsets/Visualization.jl diff --git a/Project.toml b/Project.toml index ed66ccc10..13905284b 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ repository = "https://github.com/microsoft/OpticSim.jl" version = "0.2.8" [deps] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" @@ -13,7 +12,6 @@ Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" ImageView = "86fae568-95e7-573e-a6b2-d8a6b900c9ef" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" @@ -31,20 +29,16 @@ PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StringEncodings = "69024149-9ee7-55f6-a4c4-859efe599b68" -Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" UrlDownload = "856ac37a-3032-4c1c-9122-f86d88358c8b" ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] -BenchmarkTools = "0.5, 0.6" CSV = "0.8" ColorSchemes = "3.10" ColorTypes = "0.10" @@ -52,7 +46,6 @@ Colors = "0.12" DataFrames = "0.22" Distributions = "0.24" FileIO = "1.6" -FiniteDifferences = "0.12" ForwardDiff = "0.10" ImageView = "0.10" Images = "0.23" @@ -70,7 +63,6 @@ ReverseDiff = "1.7" Revise = "3.1" StaticArrays = "1.0" StringEncodings = "0.3" -Suppressor = "0.2" Unitful = "1.6" UrlDownload = "1.0" ZipFile = "0.9" diff --git a/deps/utils.jl b/deps/utils.jl index 195b08c3c..9cb57deea 100644 --- a/deps/utils.jl +++ b/deps/utils.jl @@ -74,18 +74,21 @@ function generate_cat_jl(cat, jlpath) push!(vals, repr(get(glass_info, fn, NaN))) end end - raw_name = glass_info["raw_name"] == glass_name ? "" : " ($(glass_info["raw_name"]))" - doc_string = "\"\"\" $catalog_name.$glass_name$raw_name\n" - doc_string *= "```\n$(rpad("ID:", 25))AGF:$idnum\n" - doc_string *= "$(rpad("RI @ 587nm:", 25))$(get(glass_info, "Nd", 0.0))\n" - doc_string *= "$(rpad("Abbe Number:", 25))$(get(glass_info, "Vd", 0.0))\n" - doc_string *= "$(rpad("ΔPgF:", 25))$(get(glass_info, "ΔPgF", 0.0))\n" - doc_string *= "$(rpad("TCE (÷1e-6):", 25))$(get(glass_info, "TCE", 0.0))\n" - doc_string *= "$(rpad("Density:", 25))$(get(glass_info, "p", 0.0))g/m³\n" - doc_string *= "$(rpad("Valid wavelengths:", 25))$(get(glass_info, "λmin", 0.0))μm to $(get(glass_info, "λmax", 0.0))μm\n" - doc_string *= "$(rpad("Reference Temp:", 25))$(get(glass_info, "temp", 20.0))°C\n" - doc_string *= "```\n\"\"\"" - push!(eval_string, doc_string) + # skip docstrings for CI builds - this prevents missing docstring warnings in makedocs + if isnothing(get(ENV, "CI", nothing)) + raw_name = glass_info["raw_name"] == glass_name ? "" : " ($(glass_info["raw_name"]))" + doc_string = "\"\"\" $catalog_name.$glass_name$raw_name\n" + doc_string *= "```\n$(rpad("ID:", 25))AGF:$idnum\n" + doc_string *= "$(rpad("RI @ 587nm:", 25))$(get(glass_info, "Nd", 0.0))\n" + doc_string *= "$(rpad("Abbe Number:", 25))$(get(glass_info, "Vd", 0.0))\n" + doc_string *= "$(rpad("ΔPgF:", 25))$(get(glass_info, "ΔPgF", 0.0))\n" + doc_string *= "$(rpad("TCE (÷1e-6):", 25))$(get(glass_info, "TCE", 0.0))\n" + doc_string *= "$(rpad("Density:", 25))$(get(glass_info, "p", 0.0))g/m³\n" + doc_string *= "$(rpad("Valid wavelengths:", 25))$(get(glass_info, "λmin", 0.0))μm to $(get(glass_info, "λmax", 0.0))μm\n" + doc_string *= "$(rpad("Reference Temp:", 25))$(get(glass_info, "temp", 20.0))°C\n" + doc_string *= "```\n\"\"\"" + push!(eval_string, doc_string) + end push!(eval_string, "const $glass_name = Glass($(join(vals, ", "))) \n export $glass_name") end idnum += 1 diff --git a/docs/Project.toml b/docs/Project.toml index dee8d0e56..fb2cc5781 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,6 +3,8 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" OpticSim = "24114763-4efb-45e7-af0e-cde916beb153" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/docs/src/vis.md b/docs/src/vis.md index f5f9ac9b6..b66e41d96 100644 --- a/docs/src/vis.md +++ b/docs/src/vis.md @@ -8,14 +8,16 @@ There are a number of helper methods, as well as the ability to draw objects, su Vis.drawtracerays(Examples.cooketriplet(), trackallrays=true, test=true, numdivisions=100) ``` -```@eval +```@setup base using OpticSim +``` + +```@example base Vis.drawtracerays(Examples.cooketriplet(), trackallrays=true, test=true, numdivisions=100) -Vis.save("assets/vis_ex_3d.png") +Vis.save("assets/vis_ex_3d.png") # hide Vis.drawtracerays(Examples.cooketriplet(), trackallrays=true, test=true, numdivisions=100, drawsys=true, resolution = (1000, 700)) Vis.make2dy() -Vis.save("assets/vis_ex_2d.png") -nothing +Vis.save("assets/vis_ex_2d.png"); nothing #hide ``` ![3D visualization example](assets/vis_ex_3d.png) @@ -27,12 +29,10 @@ And the image on the detector for a trace of a system: Vis.drawtraceimage(Examples.cooketriplet(), test=true) ``` -```@eval -using OpticSim -using Images +```@example base +using Images # hide im = Vis.drawtraceimage(Examples.cooketriplet(Float64, 400), test=true) -save("assets/vis_ex_im.png", colorview(Gray, real.(im ./ maximum(im)))) -nothing +save("assets/vis_ex_im.png", colorview(Gray, real.(im ./ maximum(im)))); nothing # hide ``` ![detector image example](assets/vis_ex_im.png) @@ -42,8 +42,7 @@ nothing These methods are all you need to build up a visualization piece by piece. For example: -```@example -using OpticSim # hide +```@example base obj = csgintersection(Sphere(0.5), Plane(0.0, 1.0, 0.0, 0.0, 0.1, 0.0))() ray1 = Ray([0.0, -0.1, 1.0], [0.0, 0.0, -1.0]) ray2 = Ray([0.8, 0.0, 0.0], [-1.0, 0.0, 0.0]) @@ -52,8 +51,7 @@ Vis.draw!(ray1, rayscale=0.2) Vis.draw!(ray2, rayscale=0.2, color=:blue) Vis.draw!(surfaceintersection(obj, ray1), color=:red) Vis.draw!(surfaceintersection(obj, ray2), color=:green) -Vis.save("assets/vis_ex_3d_parts.png") # hide -nothing # hide +Vis.save("assets/vis_ex_3d_parts.png"); nothing # hide ``` ![basic drawing example](assets/vis_ex_3d_parts.png) @@ -73,13 +71,10 @@ These are the helper methods to provide common visualizations more easily, as us Vis.surfacesag(AcceleratedParametricSurface(TestData.zernikesurface2()), (256, 256), (1.55, 1.55)) ``` -```@eval -using OpticSim -using Plots -Vis.surfacesag(AcceleratedParametricSurface(TestData.zernikesurface2()), (256, 256), (1.55, 1.55)) -p = Vis.surfacesag(AcceleratedParametricSurface(TestData.zernikesurface2()), (256, 256), (1.55, 1.55)) # hide -Plots.savefig(p, "assets/surface_sag.svg") -nothing +```@example base +using Plots; include("../../test/TestData.jl") # hide +p = Vis.surfacesag(AcceleratedParametricSurface(TestData.zernikesurface2()), (256, 256), (1.55, 1.55)) +Plots.savefig(p, "assets/surface_sag.svg"); nothing # hide ``` ![surface sag example](assets/surface_sag.svg) diff --git a/src/Data/Spectra.jl b/src/Data/Spectra.jl deleted file mode 100644 index 2dd1a4d9c..000000000 --- a/src/Data/Spectra.jl +++ /dev/null @@ -1,26 +0,0 @@ -# MIT License - -# Copyright (c) Microsoft Corporation. - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE - -import CSV, DataFrames - -kopinoledpanel() = MeasuredSpectrum(CSV.read(joinpath(@__DIR__, "OLED Spectrum Kopin panel.csv"))) -export kopinoledpanel diff --git a/src/Miscellaneous.jl b/src/Miscellaneous.jl deleted file mode 100644 index 86bdb9f1f..000000000 --- a/src/Miscellaneous.jl +++ /dev/null @@ -1,1138 +0,0 @@ -# MIT License - -# Copyright (c) Microsoft Corporation. - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE - -module Junk -# TODO clean this up - -using ..OpticSim -using BenchmarkTools -import Unitful - -# space for junk code which should ultimately be deleted or cleaned up and moved elsewhere - -function drawbezier() - curve = TestData.onespanspline() - drawcurve(curve, 100, 1000) -end - -function drawbsplinecurve() - curve = TestData.bsplinecurve() - drawcurve(curve, 100, 1000) -end - -function drawbeziersurface() - vx = 0.0:0.1:1 - vy = 0.0:0.1:1 - surface = TestData.beziersurface() - - f(x, y) = point(surface, x, y)[3] - scene = Makie.Scene(resolution = (1000, 1000)) - # One way to style the axis is to pass a nested dictionary / named tuple to it. - - # for i in 1:10000 - # surface.controlpolygon.controlpoints[2,2].point[3] = i/100.0 - # Makie.wireframe!(vx,vy,zvals) - # end - # record(scene, "surface.mp4", 1:255; framerate = 60) do i - # surface.controlpolygon[2,2][3] = Float64(i)/40.0 - zvals = [f(x, y) for x in vx, y in vy] - # Makie.wireframe!(vx,vy,zvals,axis = (frame = (linewidth = 2.0,),)) - # Makie.surface!(scene, vx, vy, f, axis = (frame = (linewidth = 2.0,),)) - Makie.wireframe!(vx, vy, zvals) - # end -end - -function plotcurveintersections() - curve = TestData.curvecoefficients() - maxtheta = 1.01 - - let p - curvepts = collect((evaluatecurve(curve, theta) for theta in 0.0:0.01:maxtheta)) - let x = Array{Real,1}(undef, 0), y = Array{Real,1}(undef, 0) - for pt in curvepts - push!(x, pt[1]) - push!(y, pt[2]) - end - p = Plots.plot(x, y) - end - - origin = [0, 0] - - for theta in 0.01:0.2:maxtheta - endpoint = evaluatecurve(curve, theta) - line = Ray([0.0, 0.0], endpoint) - points = intersections(line, curve) - maxalpha = 0 - - for int in points - pt = int[1] - theta = int[3] - - if 0 <= theta <= maxtheta # only plot intersection points in the curve parameter range from 0..maxtheta - if maxalpha < int[2] - maxalpha = int[2] - end - - ptx = [pt[1]] - pty = [pt[2]] - Plots.scatter!(p, ptx, pty, legend = false) - end - end - - line = hcat([0, 0], endpoint .* maxalpha) - x = line'[:, 1] - y = line'[:, 2] - Plots.plot!(p, x, y) - end - Plots.display(p) - end -end - -function makiemeshexample5() - points = [(0, 0, 0), (1, 0, 2), (2, 1, 0), (0, 2, 1)] - color = [:red, :green, :blue, :yellow] - - # indices interpreted as triangles (every 3 sequential indices) - indices = [1 2 3; 1 3 4; 1 4 2; 2 3 4] - for i in 1:1 - Makie.mesh!(points, indices, color = color) - end -end - -function makiemeshexample4() - points = [(0, 0, 0), (1, 0, 2), (2, 1, 0), (0, 2, 1)] - color = [:red, :green, :blue, :yellow] - - # indices interpreted as triangles (every 3 sequential indices) - indices = [1 2 3; 1 3 4; 1 4 2; 2 3 4] - # replprint(indices) - # for i in 1:1 - scene = Makie.Scene(resolution = (1000, 1000)) - Makie.mesh!(points, indices, color = color) - scene - # end -end - -function makiemeshexample3() - points = [(0, 0, 0), (1, 0, 2), (2, 1, 0), (0, 2, 1)] - color = [:red, :green, :blue, :yellow] - - # indices interpreted as triangles (every 3 sequential indices) - indices = [1, 2, 3, 1, 3, 4, 1, 4, 2, 2, 3, 4] - Makie.mesh(points, indices, color = color) -end - -function makiemeshexample2() - x = [0, 1, 2, 0] - y = [0, 0, 1, 2] - z = [0, 2, 0, 1] - color = [:red, :green, :blue, :yellow] - i = [0, 0, 0, 1] - j = [1, 2, 3, 2] - k = [2, 3, 1, 3] - # indices interpreted as triangles (every 3 sequential indices) - indices = [1, 2, 3, 1, 3, 4, 1, 4, 2, 2, 3, 4] - Makie.mesh(x, y, z, indices, color = color) -end - -function makiemeshexample() - coordinates = [ - 0.0 0.0 - 0.5 0.0 - 1.0 0.0 - 0.0 0.5 - 0.5 0.5 - 1.0 0.5 - 0.0 1.0 - 0.5 1.0 - 1.0 1.0 - ] - connectivity = [ - 1 2 5 - 1 4 5 - 2 3 6 - 2 5 6 - 4 5 8 - 4 7 8 - 5 6 9 - 5 8 9 - ] - color = [0.0, 0.0, 0.0, 0.0, -0.375, 0.0, 0.0, 0.0, 0.0] - scene = Makie.mesh(coordinates, connectivity, color = color, shading = false) - - Makie.wireframe!(scene[end][1], color = (:black, 0.6), linewidth = 3) -end - - - - -# function simplerender(samples::Int = 100) -# cyl = Cylinder(0.3, 2.0) -# cyl2 = Cylinder(0.2, 2.0) -# rbt = translation(0.5, 0.5, 0.0) -# # generator = csgintersection(leaf(cyl),leaf(cyl2,rbt)) -# generator = csgunion(leaf(cyl), leaf(cyl2, rbt)) -# # generator = leaf(cyl2,rbt) - -# # surf = rbt*AcceleratedParametricSurface(TestData.beziersurface(),10) -# # generator = csgintersection(leaf(cyl),leaf(surf)) - -# csg = generator(identitytransform()) -# view = Vis.CSGViewer([3.0, 3.0, 20.0], 2.0, samples, (csg,)) -# image = Vis.render(view) -# Vis.show(image) -# end - -# function csgrender(samples::Int = 100) -# # generator = TestData.planecylindercsg() -# # csg = generator(identitytransform()) - -# # generator = TestData.trianglemeshcsg() -# # csg = generator(RigidBodyTransform(-.5, -.5, 0.0)) -# # generator = TestData.csgobject() -# # csg = generator(RigidBodyTransform(-.5, -.5, 0.0)) - -# generator = TestData.lensarray(2) -# csg = generator(identitytransform()) -# # view = CSGViewer(SVector{3}(0.0, 60.0, 80.0), .5, samples, (csg,)) -# # generator = leaf(AcceleratedParametricSurface(beziersurface(),1 - -# # generator = leaf(Cylinder(.3,10.0)) -# # csg = generator(RigidBodyTransform(0.0,0.0,0.0)) - -# # csg = AcceleratedParametricSurface(beziersurface(), 10) -# view = Vis.CSGViewer([0.0, 4.0, 8.0], 1.4, samples, (csg,)) -# image = Vis.render(view) - -# # Vis.show(image) -# end - -function timebezierpoint() - surf = TestData.beziersurface() - res = @benchmark point($surf, 0.5, 0.5) - show(res) -end - -function timereflectedray() - res = @benchmark TestData.reflectedray(rand(3), rand(3)) - println(" Array{Float64,1} time") - show(res) - res = @benchmark TestData.reflectedray(SVector{3,Float64}(rand(3)), SVector{3,Float64}(rand(3))) - println("SVector time") - show(res) -end - -function timebezierrayintersection() - u = 0.5 - v = 0.5 - surf, r = TestData.beziersurfaceandray(u, v) - surf = AcceleratedParametricSurface(surf, 5) - bnch = @benchmark surfaceintersection($surf, $r) - println(surfaceintersection(surf, r), " =? ", point(surf, u, v)) - println() - show(bnch) -end - -function timebboxrayintersection() - bbox, r = TestData.bboxray() - println(doesintersect(bbox, r)) - res = @benchmark doesintersect($bbox, $r) - show(res) -end - -function timecylinderrayintersection() - cyl = Cylinder(0.5) - r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) - bnch = @benchmark surfaceintersection($cyl, $r) - println(surfaceintersection(cyl, r)) - println() - show(bnch) -end - -function timesphererayintersection() - sph = Sphere(0.5) - r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) - bnch = @benchmark surfaceintersection($sph, $r) - println(surfaceintersection(sph, r)) - println() - show(bnch) -end - -function timetrianglerayintersection() - tri, r = TestData.triangleray() - res = @benchmark surfaceintersection($tri, $r) - println(surfaceintersection(tri, r)) - println() - show(res) -end - -function bboxrayintersection(numits) - bbox, r = TestData.bbox() - - for i in 1:numits - res = surfaceintersection(bbox, r) - end -end - -function timeplanerayintersection() - pln, r = TestData.planeray() - res = @benchmark surfaceintersection($pln, $r) - println(surfaceintersection(pln, r)) - println() - show(res) -end - -function onaxissphereintersection() - println(surfaceintersection(Sphere(1.0), Ray([0.0, 0.0, -4.0], [0.0, 0.0, 1.0]))) - println(surfaceintersection(Sphere(1.0), Ray([0.0, 0.0, 4.0], [0.0, 0.0, -1.0]))) - println(surfaceintersection(Sphere(1.0), Ray([0.0, 4.0, 0.0], [0.0, -1.0, 0.0]))) - println(surfaceintersection(Sphere(1.0), Ray([0.0, -4.0, 0.0], [0.0, 1.0, 0.0]))) - println(surfaceintersection(Sphere(1.0), Ray([4.0, 0.0, 0.0], [-1.0, 0.0, 0.0]))) - println(surfaceintersection(Sphere(1.0), Ray([-4.0, 0.0, 0.0], [1.0, 0.0, 0.0]))) -end - -function makeimagedata(samples) - zeropix = (0.0f0, 0.0f0, 0.0f0) - return (fill(zeropix, samples, samples), fill(zeropix, samples, samples)) -end - -function threadingtest(samples) - image, image2 = makeimagedata(samples) - - function innerloop(x, image, image2, samples, convolutionsize) - for y in (convolutionsize + 1):samples - for l in 1:convolutionsize - for m in 1:convolutionsize - pix = image[y - l, x - m] - oldpix = image2[y, x] - image2[y, x] = (oldpix[1] + pix[1] * 0.8, oldpix[2] + pix[2] * 0.8, oldpix[3] + pix[3] * 0.8) - end - end - end - end - - convolutionsize = 30 - # println("multithreaded") - # @sync - for x in (convolutionsize + 1):samples - # for x in convolutionsize + 1:samples - # Threads.@spawn - innerloop(x, image, image2, samples, convolutionsize) - end - return image -end - -function timeconvolution() - result = @benchmark threadingtest(1000) - show(result) -end - -function testdrawmesh() - Vis.draw(TestData.beziersurface()) -end - -function testintervalsforcsg() - surf1 = AcceleratedParametricSurface(TestData.beziersurface(), 5) - surf2 = AcceleratedParametricSurface(TestData.upsidedownbeziersurface(), 5) - r = Ray([0.5, 0.5, 2.0], [0.0, 0.0, -1.0]) - int1 = DisjointUnion(surfaceintersection(surf1, r)...) - int2 = DisjointUnion(surfaceintersection(surf2, r)...) - intsct = intervalintersection(int1, int2) - if !(intsct isa EmptyInterval) - s = Vis.scene() - Vis.draw!(s, surf1) - Vis.draw!(s, surf2) - Vis.draw!(s, intsct) - Vis.display(s) - else - throw(ErrorException("This should intersect (testintervalsforcsg)")) - end -end - -function testclippedbeziersurface() - r = Ray([0.5, 0.5, 3.0], [0.0, 0.0, -1.0]) - gen = TestData.clippedbeziersurface() - csg = gen(identitytransform()) - intsct = OpticSim.evalcsg(csg, r) - # println(intsct) - view = Vis.CSGViewer(SVector{3}(2.0, 2.0, 5.0), 2.0, 1000, (csg,)) - Vis.imshow(Vis.render(view)) -end - -function testdrawcylinder() - surf = Cylinder(1.0) - s = Vis.scene() - Vis.draw!(s, surf, numdivisions = 100) - r = Ray([0.0, 2.0, 0.0], [0.0, -1.0, 0.5]) - int = surfaceintersection(surf, r) - Vis.draw!(s, int) - Vis.display(s) -end - -function testdrawsphere() - surf = Sphere(1.0) - s = Vis.scene() - Vis.draw!(s, surf, numdivisions = 100) - r = Ray([0.0, 2.0, 0.0], [0.0, -1.0, 0.5]) - int = surfaceintersection(surf, r) - Vis.draw!(s, int) - Vis.display(s) -end - -function testcsgeval() - surf1 = AcceleratedParametricSurface(TestData.beziersurface(), 5) - surf2 = AcceleratedParametricSurface(TestData.upsidedownbeziersurface(), 5) - r = Ray([0.5, 0.5, 2.0], [0.0, 0.0, -1.0]) - gen = csgintersection(leaf(surf1), leaf(surf2)) - csgtree = gen(identitytransform()) - intsct = surfaceintersection(csgtree, r) - if !(intsct isa EmptyInterval) - s = Vis.scene() - Vis.draw!(s, surf1) - Vis.draw!(s, surf2) - Vis.draw!(s, intsct) - Vis.display(s) - else - throw(ErrorException("This should intersect (testcsgeval)")) - end -end - -function testcsgwithmultipleobjects() - generator, _, surf1, surf2 = TestData.csgobject() - csg = generator(identitytransform()) - r = Ray{Float64,3}(SVector(0.5, 0.5, 4.0), SVector(0.0, 0.0, -1.0)) - - intscts = OpticSim.evalcsg(csg, r)[1] - # println(intscts) - s = Vis.scene() - Vis.draw!(s, surf1) - Vis.draw!(s, surf2) - Vis.draw!(s, intscts) - Vis.display(s) -end - -function testmeshcsgsurf1(subdiv = 20) - _, _, surf, _ = TestData.csgobject() - m = csgintersection(leaf(surf, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf, RigidBodyTransform(rotmatd(0, 180, 0), SVector(0.5, -0.5, 0.0)))))() - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testmeshcsgsurf2(subdiv = 20) - _, _, surf1, surf2 = TestData.csgobject() - m = csgintersection(leaf(surf1, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf2, translation(-0.5, -0.5, 0.0))))() - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testmeshcsguni(subdiv = 20) - m = csgunion(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testmeshcsgint(subdiv = 20) - m = csgintersection(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testmeshcsgsub(subdiv = 20) - m = csgdifference(leaf(Cylinder(0.5, 3.0)), leaf(Sphere(1.0)))() - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testmeshcsgmla(subdiv = 20) - topsphere = leaf(Sphere(2.0), translation(0.05, 0.0, -1.5)) - botsphere = leaf(Sphere(3.0), translation(0.0, 0.1, 1.5)) - lens = csgintersection(leaf(Cylinder(0.7, 5.0)), csgintersection(botsphere, topsphere)) - d = 0.8 - MLA = lens - for x in 0:2 - for y in 0:2 - if x == 0 && y == 0 - continue - end - MLA = csgunion(MLA, leaf(lens, RigidBodyTransform(rotmatd(0, 0, rand(Int) % 180), SVector{3}(x * d, y * d, 0.0)))) - end - end - m = MLA(identitytransform()) - b = @benchmark makemesh($m, $subdiv) - Vis.draw(m) - return b -end - -function testorthogonalitymatrix() - x = [0 4 -4.5 4.5; 0 11.25 -31.5 20.25] - correctCmatrix = [ - 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 - 4.0 0.0 0.0 0.0 11.25 0.0 0.0 0.0 0.0 1.0 0.0 0.0 - -4.5 4.0 0.0 0.0 -31.5 11.25 0.0 0.0 0.0 0.0 1.0 0.0 - 4.5 -4.5 4.0 0.0 20.25 -31.5 11.25 0.0 0.0 0.0 0.0 1.0 - 0.0 4.5 -4.5 4.0 0.0 20.25 -31.5 11.25 0.0 0.0 0.0 0.0 - 0.0 0.0 4.5 -4.5 0.0 0.0 20.25 -31.5 0.0 0.0 0.0 0.0 - 0.0 0.0 0.0 4.5 0.0 0.0 0.0 20.25 0.0 0.0 0.0 0.0 - ] - cmat = OpticSim.orthogonalitymatrix(x, 3) - show(IOContext(stdout), "text/plain", cmat) - for i in CartesianIndices(cmat) - if cmat[i] != correctCmatrix[i] - println("did not pass test") - break - end - end -end - -function testtriangulate() - surf = TestData.beziersurface() - numtris = 20 # number of triangles per row or column - tris = triangulate(surf, numtris) - tmesh = TriangleMesh(tris) - Vis.draw(tmesh) -end - -function testcurvetobeziersegments() - orig = TestData.homogeneousbsplinecurve() - segments = tobeziersegments(orig) - allsegments = [BezierCurve{OpticSim.Rational,Float64,3,3}(segment) for segment in segments] - Vis.draw(orig, allsegments...) -end - -function testsurfacetobeziersegments() - orig = TestData.bsplinesurface() - segments = tobeziersegments(orig) - Vis.draw(segments...) -end - -function testinsertknot() - orig = TestData.bsplinecurve() - curve1 = TestData.bsplinecurve() - let curve2 = curve1 - for i in 1:2 - curve2 = insertknot(curve1, 5 + i - 1) - curve1 = curve2 - end - Vis.draw(orig, curve2) - end -end - -# function timelinalg(vec::Bool = true, mat::Bool = true) -# # pf(q) = print(BenchmarkTools.prettytime(time(median(q))), "\n") - -# if vec -# print("VECTOR OPS\n") -# vec3a = @SVector rand(3) -# vec3b = @SVector rand(3) -# vec1000a = @SVector rand(1000) -# vec1000b = @SVector rand(1000) - -# for (vec1, vec2) in [(vec3a, vec3b), (vec1000a, vec1000b)] -# print("SIZE: $(length(vec1))\n") -# # NORMS -# print("NORMALIZE\n") -# @btime (OpticSim.mnormalize($vec1)) -# @btime (LinearAlgebra.normalize($vec1)) - -# # DOT -# print("DOT\n") -# @btime (OpticSim.dot($vec1, $vec2)) -# @btime (LinearAlgebra.dot($vec1, $vec2)) - -# # CROSS -# if length(vec1) == 3 -# print("CROSS\n") -# @btime (OpticSim.cross($vec1, $vec2)) -# @btime (LinearAlgebra.cross($vec1, $vec2)) -# end -# end -# end - -# if mat -# print("MATRIX OPS\n") -# mat3a = @SMatrix rand(3, 3) -# mat3b = @SMatrix rand(3, 3) -# mat100a = @SMatrix rand(12, 12) -# mat100b = @SMatrix rand(12, 12) -# vec100 = @SVector rand(12) - -# for (mat1, mat2) in [(mat3a, mat3b), (mat100a, mat100b)] -# print("SIZE: $(size(mat1))\n") - -# # DETERMINANT -# print("DET\n") -# @btime (OpticSim.det($mat1)) -# @btime (LinearAlgebra.det($mat1)) - -# # INVERSE -# print("INV\n") -# @btime (OpticSim.minv($mat1)) -# @btime (LinearAlgebra.inv($mat1)) - -# # DIVISON -# print("DIV\n") -# @btime (OpticSim.mdiv($mat1, $mat2)) -# @btime ($mat1 \ $mat2) -# end -# end -# end - -function testniandnt() - r = Ray([0.0, 0.0, 7.0], [0.0, 0.0, -1.0]) - normal = SVector{3,Float64}(0.0, 0.0, 1.0) - - -end - -function testrefractedray() - elt = TestData.planoconvexelement(OpticSim.GlassCat.SCHOTT.N_BK7, 0.0, 5.0, 60.0) - lens = OpticSim.LensAssembly(elt.lens) - r = Ray([0.0, 0.0, 7.0], [0.0, 0.0, -1.0]) - - green = 500 * Unitful.u"nm" - glass = OpticSim.GlassCat.SCHOTT.BAK50 - - intsct = OpticSim.closestintersection(lens, r) - - pt = point(intsct[1]) - nml = normal(intsct[1]) - - rdir = direction(r) - (nᵢ, nₜ) = OpticSim.nᵢandnₜ(index(OpticSim.GlassCat.Air, green), index(glass, green), nml, r) - - refracted = OpticSim.refractedray(nᵢ, nₜ, nml, rdir) -end - -badray() = Ray([5.000000042187876, 5.000000042187876, 0.6143579891640621], [0.042187876699796796, 0.042187876699796796, -0.9982185963600987]) - -function printtrace(raytrace) - for trace in raytrace - println("RAY***************") - println(trace) - println("END RAY***********") - end -end -function testdoubleconcave() - system = TestData.doubleconcave() - green = 500 * Unitful.u"nm" - r1 = Ray([5.0, 5.0, 2.0], [0.0, 0.0, -1.0]) - r2 = badray() - elt = system.system.assembly.elements[1] - # intsct = OpticSim.surfaceintersection(elt,r) - # println(intsct) - for r in (r1,) - allrays = Array{OpticSim.LensTrace{Float64,3},1}(undef, 0) - - res = OpticSim.trace(system, r, green, trackrays = allrays) - printtrace(allrays) - end -end - -function testplane() - rparallel = Ray([0.0, 0.0, 2.0], [1.0, 1.0, 0.0]) - rinplane = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 0.0]) - routside = Ray([0.0, 0.0, 2.0], [1.0, 1.0, 1.0]) - rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) - routintersects = Ray([0.0, 0.0, 2.0], [0.0, 0.0, -1.0]) - rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) - pln = Plane(0.0, 0.0, 1.0, 0.0, 0.0, 1.0) - - surfaceintersection(pln, routside) - if !(nothing === surfaceintersection(pln, rparallel)) - @error "should have returned nothing for ray parallel to plane and outside" - else - if !(nothing === surfaceintersection(pln, routside)) - @error "shouldhave returned nothing for entirely outside plane but not parallel" - else - # NOTE for coplanar faces to work with visualization, rays in the plane must count as being 'inside' - res = surfaceintersection(pln, rinplane) - if !(lower(res) isa RayOrigin && upper(res) isa Infinity) - @error "should have returned (rayorigin,∞)" - return false - else - res = surfaceintersection(pln, rinside) - if !(lower(res) isa RayOrigin && upper(res) isa Infinity) - @error "should have returned (rayorigin,∞)" - return false - else - res = surfaceintersection(pln, routintersects) - if !(samepoint(point(lower(res)), [0.0, 0.0, 1.0]) && upper(res) isa Infinity) - @error "should have returned ([0.0,0.0,0.0],∞)" - return false - else - res = surfaceintersection(pln, rinintersects) - if !(lower(res) isa RayOrigin && samepoint(point(upper(res)), [0.0, 0.0, 1.0])) - @error "should have returned(rayorigin,[0.0,0.0,0.0])" - return false - else - return true - end - end - end - end - end - end -end - -function testintervalcomplement() - intvl = surfaceintersection((leaf(Sphere(1.0)))(identitytransform()), Ray([2.0, 0.0, 0.0], [-1.0, 0.0, 0.0])) - comp = OpticSim.intervalcomplement(intvl) - println(comp) -end - -function testcsgoperations() - pln1 = Plane(SVector{3}(0.0, 0.0, -1.0), SVector{3}(0.0, 0.0, -1.0)) - pln2 = Plane(SVector{3}(0.0, 0.0, 1.0), SVector{3}(0.0, 0.0, 1.0)) - r = Ray{Float64,3}(SVector{3}(0.0, 0, 2.0), SVector{3}(0.0, 0.0, -1.0)) - gen1 = csgintersection(pln1, pln2) - gen2 = csgunion(pln1, pln2) - - csg = gen1(identitytransform()) - intsct = surfaceintersection(csg, r) - - intvlpt1 = lower(intsct) - intvlpt2 = upper(intsct) - println(intvlpt1, " ", intvlpt2) - if !(isapprox(point(intvlpt1), SVector{3}(0.0, 0.0, 1.0)) && isapprox(point(intvlpt2), SVector{3}(0.0, 0.0, -1.0))) - @error "Should have two intersections with the two planes" - return false - end - csg = gen2(identitytransform()) - intsct = surfaceintersection(csg, r) - if !((lower(intsct) isa RayOrigin) && (upper(intsct) isa Infinity)) - @error "Union space should be half infinite" - return false - end - return true -end - - -function testinsideplane() - r = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - p = Plane(0.0, 0.0, 1.0, 0.0, 0.0, 1.5) - res = surfaceintersection(p, r) - println(res) -end - -function testsinglenegsurface() - frontradius = -60.0 - frontvertex = 0.0 - semidiameter = 9.0 - r = Ray([5.0, 5.0, 1.0], [0.0, 0.0, -1.0]) - - temp = leaf(Sphere(abs(frontradius)), OpticSim.translation(0.0, 0.0, frontvertex - frontradius)) - d = -frontradius - sqrt(frontradius^2 - semidiameter^2) #offset from vertex to cutting plane. Plane is necessary to prevent parts of the complement from showing up in the final CSG - p₀ = frontvertex + d - plane = Plane(0.0, 0.0, 1.0, 0.0, 0.0, p₀) - gen = csgdifference(leaf(plane), temp) - - concave = gen() - intsct = surfaceintersection(concave, r) - println(intsct) -end - -# function testsinglepossurface() -# backradius = 60.0 -# frontvertex = 0.0 -# thickness = 10.0 -# semidiameter = 9.0 -# r = Ray([5.000000042187876, 5.000000042187876, 0.6143579891640621], [0.042187876699796796, 0.042187876699796796, -0.9982185963600987]) - -# temp = leaf(Sphere(abs(backradius)), OpticSim.translation(0.0, 0.0, (frontvertex - thickness) - backradius)) -# d = backradius - sqrt(backradius^2 - semidiameter^2) -# p₀ = frontvertex - thickness - d -# plane = Plane(0.0, 0.0, -1.0, 0.0, 0.0, p₀) -# # gen = csgintersection(leaf(plane),csgcomplement(temp)) -# gen = csgcomplement(temp) -# concave = gen() -# intsct = closestintersection(surfaceintersection(concave, r)) -# println(intsct) -# end - - -# function testnegcurveintersection() -# gen = OpticSim.csgcomplement(leaf(Sphere(60.0), OpticSim.translation(0.0, 0.0, 60.0))) -# concave = gen() - -# r = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) -# intsct = surfaceintersection(concave, r) - -# println(intsct) -# end - -function testdoubleconvex() - system = Examples.doubleconvex() - green = 500 * Unitful.u"nm" - r = Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0]) - - res = OpticSim.trace(system, r, green) - println(res) -end - -function testcooketriplet() - system = Examples.cooketriplet() - - green = 500 * Unitful.u"nm" - temperature = 20 * Unitful.u"°C" - - r = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - - res = OpticSim.trace(system, r, green, temperature) -end - -function testopticalsystem(lenssystem::T) where {T<:OpticalSystem} - # lenssystem = Examples.cooketriplet() - # lenssystem = Examples.planoconvex() - lenssystem = Examples.doubleconvex() - green = 500 * Unitful.u"nm" - - rad = OpticSim.semidiameter(lenssystem) - points = Array{SVector{3,Float64},1}(undef, 0) - - count = 0 - - for x in (-rad):0.5:rad, y in (-rad):0.5:rad - r = Ray([x, y, 1.0], [0.0, 0.0, -1.0]) - if mod(count, 100) == 0 - println(count) - end - - - count += 1 - res = OpticSim.trace(lenssystem, r, green) - - if !(nothing === res) - push!(points, point(res.intersection)) - end - end - - twodpoints = map(x -> (x[1], x[2]), points) - - # scatter(twodpoints, markersize = 3) -end - -function testelementsurfaceintersection() - elt = OpticSim.SphericalLens(OpticSim.GlassCat.SCHOTT.BK6, 0.0, 60.0, Inf64, 5.0, 9.0) - r = Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0]) - res = surfaceintersection(elt, r) - println(res) -end - -function testsurfaceintersection() - # hit from inside - surf = TestData.beziersurface() - accelsurf = AcceleratedParametricSurface(surf) - TOLERANCE = 1e-9 - - r = Ray([0.5, 0.5, 0.2], [0.0, 1.0, 0.0]) - println("1") - res = surfaceintersection(accelsurf, r) - lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.8996900152986361, 0.2], atol = TOLERANCE) - - # hit from outside - r = Ray([0.0, 0.5, 0.2], [1.0, 0.0, -1.0]) - println("2") - res = surfaceintersection(accelsurf, r) - isapprox(point(lower(res)), [0.06398711204047353, 0.5, 0.13601288795952649], atol = TOLERANCE) && upper(res) isa Infinity - - # two hits from outside - r = Ray([0.0, 0.5, 0.25], [1.0, 0.0, 0.0]) - println("3") - res = surfaceintersection(accelsurf, r) - isapprox(point(lower(res)), [0.12607777053030267, 0.5, 0.25], atol = TOLERANCE) && isapprox(point(upper(res)), [0.8705887077060419, 0.5, 0.25], atol = TOLERANCE) - - surf = TestData.wavybeziersurface() - accelsurf = AcceleratedParametricSurface(surf) - - # 3 hit starting outside - r = Ray([0.5, 0.0, 0.0], [0.0, 1.0, 0.0]) - println("4") - res = surfaceintersection(accelsurf, r) - isa(res, DisjointUnion) && length(res) == 2 && isapprox(point(lower(res[1])), [0.5, 0.10013694786182059, 0.0], atol = TOLERANCE) && isapprox(point(upper(res[1])), [0.5, 0.49625, 0.0], atol = TOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.8971357794109067, 0.0], atol = TOLERANCE) && (upper(res[2]) isa Infinity) - - # 3 hit starting inside - # r = Ray([0.5, 1.0, 0.0], [0.0, -1.0, 0.0]) - # println("5") - # res = surfaceintersection(accelsurf, r) - # isa(res, DisjointUnion) && length(res) == 2 && (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.5, 0.8971357794109067, 0.0], atol = TOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.49625, 0.0], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.5, 0.10013694786182059, 0.0], atol = TOLERANCE) - # println("after 5") - - surf = TestData.verywavybeziersurface() - accelsurf = AcceleratedParametricSurface(surf) - - # five hits starting outside - r = Ray([0.9, 0.0, -0.3], [0.0, 1.0, 0.7]) - println("6") - res = surfaceintersection(accelsurf, r) - a = isapprox(point(lower(res[1])), [0.9, 0.03172286522032046, -0.2777939943457758], atol = TOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.1733979947040411, -0.17862140370717122], atol = TOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], atol = TOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.9830891958374246, 0.3881624370861975], atol = TOLERANCE) && (upper(res[3]) isa Infinity) - isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # five hits starting inside - r = Ray([0.9, 1.0, 0.4], [0.0, -1.0, -0.7]) - println("7") - res = surfaceintersection(accelsurf, r) - a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.9, 0.9830891958374246, 0.3881624370861975], atol = TOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], atol = TOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.17339799470404108, -0.1786214037071712], atol = TOLERANCE) && isapprox(point(upper(res[3])), [0.9, 0.03172286522032046, -0.27779399434577573], atol = TOLERANCE) - isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # 4 hits starting inside - r = Ray([0.1, 0.0, -0.3], [0.0, 1.0, 0.7]) - println("8") - res = surfaceintersection(accelsurf, r) - a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.1, 0.2851860296285551, -0.10036977926001144], atol = TOLERANCE) - b = isapprox(point(lower(res[2])), [0.1, 0.5166793625025807, 0.06167555375180668], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.1, 0.7770862508789854, 0.24396037561528983], atol = TOLERANCE) - c = isapprox(point(lower(res[3])), [0.1, 0.98308919558696, 0.3881624369108719], atol = TOLERANCE) && (upper(res[3]) isa Infinity) - isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # 4 hits starting outside - r = Ray([0.9, 0.9, 0.4], [0.0, -0.9, -0.7]) - println("9") - res = surfaceintersection(accelsurf, r) - a = isapprox(point(lower(res[1])), [0.9, 0.736072142615238, 0.2725005553674076], atol = TOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.567439326091764, 0.141341698071372], atol = TOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.16601081959179267, -0.1708804736508277], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.032434058775915924, -0.274773509840954], atol = TOLERANCE) - isa(res, DisjointUnion) && length(res) == 2 && a && b - -end - -function errortest() - surf = TestData.verywavybeziersurface() - accelsurf = AcceleratedParametricSurface(surf) - - TOLERANCE = 1e-9 - r = Ray([0.5, 1.0, 0.0], [0.0, -1.0, 0.0]) - - res = surfaceintersection(accelsurf, r) - isa(res, DisjointUnion) && length(res) == 2 && (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.5, 0.8971357794109067, 0.0], atol = TOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.49625, 0.0], atol = TOLERANCE) && isapprox(point(upper(res[2])), [0.5, 0.10013694786182059, 0.0], atol = TOLERANCE) -end - -function testintervalunion() - alphasequal(int1, int2) = α(lower(int1)) == α(lower(int2)) && α(upper(int1)) == α(upper(int2)) - intersectionat(a) = TestData.intersectionat(a) - a = Interval(intersectionat(0.0), intersectionat(0.2)) - aa = Interval(intersectionat(0.1), intersectionat(0.3)) - - b = DisjointUnion(aa, Interval(intersectionat(0.5), intersectionat(0.6))) - res = intervalunion(a, b) - - @assert res == intervalunion(b, a) - println("alphas equal $(alphasequal(res[1],Interval(intersectionat(0.0), intersectionat(0.3)))) ") - - - # @assert res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) this equality test does not work - @assert res[2] == b[2] -end - - - -function fixequalitybugintests() - #conclusion: putting BezierSurface as a field in Intersection causes simple value semantics == to fail. Maybe BezierSurface field becomes a ref and is heap allocated? - a = DisjointUnion(Interval(TestData.intersectionat(0.2), TestData.intersectionat(0.5)), Interval(TestData.intersectionat(0.7), TestData.intersectionat(0.8))) - b = DisjointUnion(Interval(TestData.intersectionat(0.1), TestData.intersectionat(0.3)), Interval(TestData.intersectionat(0.4), TestData.intersectionat(0.6))) - res = intervalunion(a, b) - - # println("a∪b $(intervalunion(a,b))") - # println("identity $(intervalunion(a,b) == intervalunion(a,b))") - # println("b∪a $(intervalunion(b,a))") - @assert res == intervalunion(b, a) - temp = Interval(TestData.intersectionat(0.1), TestData.intersectionat(0.6)) - println(temp) - println(TestData.intersectionat(0.1)) - @assert res[1] == temp - - @assert res[2] == a[2] - -end - -function testcylinder() - sph = Sphere(1.0) - cyl = Cylinder(0.2) - - csg = csgdifference(leaf(sph), leaf(cyl)) - Vis.draw(csg) -end - -function testtransmission() - lens = TestData.planoplano() - green = 500 * Unitful.u"nm" - - # result = trace(lens,r,green) - # println(result) - - r = Ray([10.0, 0.0, 0.0], [-1.0, 0.0, -1.0]) - result = trace(lens, r, green) - println(result) - - lenselement = lens.system.assembly.elements[1].objecttree - println("Direct result of surface intersection on csg tree $(OpticSim.evalcsg(lenselement,r))") - # pln1 = Plane(0.0,0.0,1.0,0.0,0.0,0.0) - # pln2 = Plane(0.0,0.0,-1.0,0.0,0.0,-10.0) - - pln1 = Rectangle([0.0, 0.0, 1.0], [0.0, 0.0, 0.0], 9.0, 9.0) - pln2 = Rectangle([0.0, 0.0, -1.0], [0.0, 0.0, -10.0], 9.0, 9.0) - - println("intersection top rectangle $(surfaceintersection(pln1,r)) \n\n\n intersection bottom rectangle $(surfaceintersection(pln2,r))") - - cyl = Cylinder(9.0, 10.0) - csg = csgintersection(leaf(pln1), csgintersection(leaf(pln2), leaf(cyl))) - intsct = OpticSim.evalcsg(csg(), r) - println("rectangle top and bottom CSG intsct $intsct") - # result = surfaceintersection(cyl, r) - # println(result) -end - -function testgenerateray() - gen = OpticSim.RayGeneratorUniform(10, 2.0, 10.0) - - for i in gen - println(i) - end -end - -function testrectangle() - r = Rectangle([0.0, 0.0, 1.0], [0.0, 0.0, 0.0], 1.0, 1.0) - ry = Ray([2.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - println(surfaceintersection(r, ry)) - -end - -function randunit() - let v = rand(SVector{3,Float64}) - while (norm(v) > 1.0) - v = rand(SVector{3,Float64}) - end - return normalize(v) - end -end - -function makeeye() - eye = CSGOpticalSystem(OpticSim.ModelEye(), Rectangle(10.0, 10.0, SVector{3,Float64}(0.0, 0.0, 1.0), SVector{3,Float64}(0.0, 0.0, -26.0))) - # Vis.drawtracerays(eye) - ray = OpticalRay([0.0, 0.0, 10.0], [0.0, 0.001, -1.0], 1.0, 0.55) - - return ray, eye -end - -function dorays(ray, eye) - # Threads.@threads for i in 1:1000000 - for i in 1:1000 - trace(eye, ray) - end -end - -function testnearestsquareroots() - for i in 1:10:1000 - a, b = OpticSim.nearestsqrts(i) - approx = a * b - println(approx - i) - end -end - -function hierarchicalimage() - a = OpticSim.HierarchicalImage{Float32}(100, 200) - - for i in CartesianIndices(a) - a[i] = 2.0 - end - return a -end - - - -struct SpecialNumber{T<:Real} <: Real - val::T - - function SpecialNumber(num::T) where {T<:Real} - if isnan(num) - println("here") - else - return new{T}(num) - end - end -end - -# const operators = (:+,:-,:*,:/,:sqrt,:cos,:sin,:exp,:ln) -for op in (:sin, :cos, :tan, :log, :exp, :sqrt, :-) - eval(quote - Base.$op(a::SpecialNumber) = SpecialNumber($op(a.val)) - end) -end - -for op in (:+, :-, :*, :/) - eval(quote - Base.$op(a::SpecialNumber, b::SpecialNumber) = SpecialNumber($op(a.val, b.val)) - end) -end - -Base.promote_rule(::Type{T}, ::Type{SpecialNumber{T1}}) where {T<:Real,T1<:Real} = SpecialNumber{promote_type(T, T1)} - - - - -function singleemitter(numrays) - T = Float64 - temp = SVector{3,T}(0.0, 0.0, 0.0) - # λ = T(0) - a = Source(UniformSpectrum{T}(), PointOrigin{T}(SVector{3,T}(0.0, 0.0, 0.0)), ConeDistribution(π / 5, numrays), Lambertian{T}()) - - # function tracerays() - # temp = T(0) - # for ray in a - # temp = ray.power - # end - # return temp - # end - - for ray in a - temp = OpticSim.direction(a, 1) - # temp = OpticSim.spectrumsample(a) - # temp = OpticSim.spectrumpower(a.spectrum,.6) - end - - return temp -end -export singleemitter - -printrays(a::Source) = - for ray in a - println(ray) - end - -function diagdistributions(numsamples) - temp = 0.0 - - for i in 1:numsamples - temp = rand(Distributions.Uniform{Float64}(400.0, 680.0)) - end - return temp -end -export diagdistributions - - -end # module Junk diff --git a/src/OpticSim.jl b/src/OpticSim.jl index b16c509c7..8002bb4d6 100644 --- a/src/OpticSim.jl +++ b/src/OpticSim.jl @@ -25,8 +25,6 @@ module OpticSim import Unitful using LinearAlgebra: eigen, svd, I, qr, dot, cross, norm, det, normalize, inv using StaticArrays -using FiniteDifferences -using BenchmarkTools using DataFrames: DataFrame using Images using Base: @. @@ -44,13 +42,8 @@ include("Utilities.jl") include("Geometry/Geometry.jl") include("Optical/Optical.jl") include("Visualization.jl") -include("TestData.jl") include("Examples.jl") include("Optimization/Optimizable.jl") -include("Diagnostics.jl") - -include("Benchmarks.jl") -include("Data/Spectra.jl") include("Sysimage.jl") diff --git a/src/Benchmarks.jl b/test/Benchmarks.jl similarity index 93% rename from src/Benchmarks.jl rename to test/Benchmarks.jl index 6a672b6a8..4329b1af2 100644 --- a/src/Benchmarks.jl +++ b/test/Benchmarks.jl @@ -26,10 +26,11 @@ using BenchmarkTools using Unitful using StaticArrays -using ..OpticSim -using ..OpticSim: Sphere, Ray, replprint, trace, Cylinder, AcceleratedParametricSurface, newton, Infinity, RayOrigin, NullInterface, IntervalPoint, intervalintersection, LensTrace, FresnelInterface, wavelength, mᵢandmₜ, refractedray, direction, origin, pathlength, LensAssembly, NoPower, power, snell, fresnel, reflectedray, BoundingBox -using ..OpticSim.TestData -using ..OpticSim.Examples +using OpticSim +using OpticSim: Sphere, Ray, replprint, trace, Cylinder, AcceleratedParametricSurface, newton, Infinity, RayOrigin, NullInterface, IntervalPoint, intervalintersection, LensTrace, FresnelInterface, wavelength, mᵢandmₜ, refractedray, direction, origin, pathlength, LensAssembly, NoPower, power, snell, fresnel, reflectedray, BoundingBox +using OpticSim.Examples + +include("TestData.jl") rayz() = Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0]) perturbrayz() = Ray([0.0, 0.0, 10.0], [0.001, 0.001, -1.0]) diff --git a/src/Diagnostics.jl b/test/Diagnostics.jl similarity index 99% rename from src/Diagnostics.jl rename to test/Diagnostics.jl index 12df4abb1..39bb4a861 100644 --- a/src/Diagnostics.jl +++ b/test/Diagnostics.jl @@ -23,16 +23,18 @@ # Programs used to visualize output, profile code or perform debugging tasks, as opposed to unit testing module Diagnostics -using ..OpticSim -using ..OpticSim: replprint -using ..OpticSim.Vis -using ..OpticSim.Optimizable +using OpticSim +using OpticSim: replprint +using OpticSim.Vis +using OpticSim.Optimizable using LinearAlgebra using StaticArrays import Unitful import Plots +include("TestData.jl") + function testbigfloat() λ = 550 * Unitful.u"nm" temp = 20 * Unitful.u"°C" diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 000000000..822f024e1 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,13 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" +Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" +XUnit = "3e3c03f2-1a94-11e9-2981-050a4ca824ab" diff --git a/src/TestData.jl b/test/TestData.jl similarity index 99% rename from src/TestData.jl rename to test/TestData.jl index a63eae20c..0477f97a4 100644 --- a/src/TestData.jl +++ b/test/TestData.jl @@ -25,8 +25,8 @@ Contains all complex data used for testing and benchmarking. """ module TestData -using ..OpticSim -using ..OpticSim: tobeziersegments # try to use only exported functions so this list should stay short +using OpticSim +using OpticSim: tobeziersegments # try to use only exported functions so this list should stay short using StaticArrays using DataFrames using Unitful diff --git a/test/runtests.jl b/test/runtests.jl index caa47e74e..942ff523e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -33,7 +33,7 @@ using OpticSim using OpticSim: findspan, makemesh, knotstoinsert, coefficients, inside, quadraticroots, tobeziersegments, evalcsg, makiemesh # interval imports using OpticSim: α, halfspaceintersection, positivehalfspace, lower, upper, EmptyInterval, rayorigininterval, intervalcomplement, intervalintersection, intervalunion, RayOrigin, Infinity, Intersection -using OpticSim.TestData: intersectionat +include("TestData.jl") # bounding box imports using OpticSim: doesintersect # RBT imports @@ -62,16 +62,6 @@ macro otestset(name, expr) end end -"""Creates a 3D vector uniformly distributed on the sphere by rejection sampling, i.e., discarding all points with norm > 1.0""" -function randunit() - let v = rand(3) - while (norm(v) > 1.0) - v = rand(3) - end - return normalize(v) - end -end - """Evalute all functions not requiring arguments in a given module and test they don't throw anything""" macro test_all_no_arg_functions(m) quote @@ -96,2178 +86,17 @@ macro test_all_no_arg_functions(m) end end -###################################################################################################### - - -# FIXING ERRONEOUS UNBOUND TYPE ERRORS THAT OCCUR WITH VARARG -#= -The set will contain something like this: - MultiHologramInterface(interfaces::Vararg{HologramInterface{T}, N}) where {T<:Real, N} -To get the signature run: - methods(OpticSim.MultiHologramInterface).ms[I].sig where I is typically 1 or 2 depending on the number of methods -This gives: - Tuple{Type{MultiHologramInterface}, Vararg{HologramInterface{T}, N}} where N where T<:Real -We then have to use which to find the method: - which(OpticSim.MultiHologramInterface, Tuple{Vararg{HologramInterface{T}, N}} where N where T<:Real) -and pop it from the set -=# - -@otestset "JuliaLang" begin - # ensure there aren't any ambiguities or unbound args - let unbound = Set{Method}(detect_unbound_args(OpticSim)) - # Pretty hacky way to ignore specific methods, for some weird reason the unbound args check seems to fail for some (seemingly random) methods with Vararg arguments - # here we ignore the specific methods which we know are ok but are still failing - if VERSION >= v"1.6.0-DEV" - pop!(unbound, which(OpticSim.LensAssembly, Tuple{Vararg{Union{CSGTree{T},LensAssembly{T},Surface{T}},N} where N} where {T<:Real})) - pop!(unbound, which(OpticSim.RayListSource, Tuple{Vararg{OpticalRay{T,3},N} where N} where {T<:Real})) - pop!(unbound, which(OpticSim.OpticalSourceGroup, Tuple{Vararg{OpticalRayGenerator{T},N} where N} where {T<:Real})) - pop!(unbound, which(OpticSim.PixelSource, Tuple{Vararg{P,N} where N} where {P<:OpticalRayGenerator{T}} where {T<:Real})) - pop!(unbound, which(OpticSim.MultiHologramInterface, Tuple{Vararg{HologramInterface{T},N}} where {N} where {T<:Real})) - end - # and ignore any generate methods created due to default or keyword arguments - for m in unbound - if occursin("#", string(m.name)) - pop!(unbound, m) - end - end - @test unbound == Set() - end - let ambiguous = Set{Method}(detect_ambiguities(OpticSim)) - @test ambiguous == Set() - end - let unbound = Set{Method}(detect_unbound_args(OpticSim.Zernike)) - @test unbound == Set() - end - let ambiguous = Set{Method}(detect_ambiguities(OpticSim.Zernike)) - @test ambiguous == Set() - end - let unbound = Set{Method}(detect_unbound_args(OpticSim.QType)) - @test unbound == Set() - end - let ambiguous = Set{Method}(detect_ambiguities(OpticSim.QType)) - @test ambiguous == Set() - end - let unbound = Set{Method}(detect_unbound_args(OpticSim.Vis)) - # Pretty hacky way to ignore specific methods, for some weird reason the unbound args check seems to fail for some (seemingly random) methods with Vararg arguments - # here we ignore the specific methods which we know are ok but are still failing - pop!(unbound, which(OpticSim.Vis.drawcurves, Tuple{Vararg{Spline{P,S,N,M},N1} where N1} where {M} where {N} where {S} where {P})) - pop!(unbound, which(OpticSim.Vis.draw, Tuple{Vararg{S,N} where N} where {S<:Union{OpticSim.Surface{T},OpticSim.TriangleMesh{T}}} where {T<:Real})) - pop!(unbound, which(OpticSim.Vis.draw!, Tuple{OpticSim.Vis.MakieLayout.LScene,Vararg{S,N} where N} where {S<:Union{OpticSim.Surface{T},OpticSim.TriangleMesh{T}}} where {T<:Real})) - # and ignore any generate methods created due to default or keyword arguments - for m in unbound - if occursin("#", string(m.name)) - pop!(unbound, m) - end - end - @test unbound == Set() - end - let ambiguous = Set{Method}(detect_ambiguities(OpticSim.Vis)) - @test ambiguous == Set() - end - let unbound = Set{Method}(detect_unbound_args(OpticSim.TestData)) - @test unbound == Set() - end - let ambiguous = Set{Method}(detect_ambiguities(OpticSim.TestData)) - @test ambiguous == Set() - end -end # testset JuliaLang - -# @otestset "BVH" begin -# @testset "partition!" begin -# split = 0.5 - -# for i in 1:100000 -# a = rand(5) -# b = copy(a) -# badresult::Bool = false - -# lower, upper = partition!(a, (x) -> x, split) - -# if lower !== nothing -# for i in lower -# if i >= split -# badresult = true -# break -# end -# end -# end - -# if upper !== nothing -# for i in upper -# if i <= split -# badresult = true -# end -# end -# end - -# if badresult -# throw(ErrorException("array didn't partition: $(b)")) -# end - -# end -# end -# end # testset BVH - -@otestset "TestData" begin - @test_all_no_arg_functions TestData -end # testset TestData - -# TODO may want to test this but it's very slow on azure -# @otestset "Examples" begin -# @test_all_no_arg_functions Examples -# end # testset Examples - -@otestset "General" begin - @testset "QuadraticRoots" begin - Random.seed!(SEED) - similarroots(r1, r2, x1, x2) = isapprox([r1, r2], [x1, x2], rtol = 1e-9) || isapprox([r2, r1], [x1, x2], rtol = 1e-9) - - for i in 1:10000 - r1, r2, scale = rand(3) .- 0.5 - a, b, c = scale .* (1, r1 + r2, r1 * r2) - x1, x2 = quadraticroots(a, b, c) - @test similarroots(-r1, -r2, x1, x2) - end - - a, b, c = 1, 0, -1 - x1, x2 = quadraticroots(a, b, c) - @test similarroots(1, -1, x1, x2) - - a, b, c = 1, 2, 1 # (x+1)(x+1)= x^2 + 2x + 1, double root at one - x1, x2 = quadraticroots(a, b, c) - @test similarroots(-1, -1, x1, x2) - end # testset QuadraticRoots - - @testset "RigidBodyTransform" begin - Random.seed!(SEED) - @test isapprox(rotmatd(180, 0, 0), [1.0 0.0 0.0; 0.0 -1.0 0.0; 0.0 0.0 -1.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(rotmatd(0.0, 180.0, 0.0), [-1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 -1.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(rotmatd(0, 0, 180), [-1.0 0.0 0.0; 0.0 -1.0 0.0; 0.0 0.0 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(rotmatd(0, 90, 0), [0.0 0.0 1.0; 0.0 1.0 0.0; -1.0 0.0 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(rotmatd(45, -45, 45), [0.5 -0.8535533905932737 0.1464466094067261; 0.5 0.14644660940672644 -0.8535533905932737; 0.7071067811865475 0.5 0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - x, y, z = rand(3) - @test isapprox(rotmatd(x * 180 / π, y * 180 / π, z * 180 / π), rotmat(x, y, z), rtol = RTOLERANCE, atol = ATOLERANCE) - - @test isapprox(RigidBodyTransform(rotmatd(0, 90, 0), SVector(0.0, 0.0, 1.0)) * SVector(1.0, 0.0, 0.0), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - ta = RigidBodyTransform(rotmatd(0, 90, 0), SVector(0.0, 0.0, 1.0)) - tb = RigidBodyTransform(rotmatd(90, 0, 0), SVector(1.0, 0.0, 0.0)) - @test isapprox(collect(ta * tb), collect(RigidBodyTransform(rotmatd(90, 90, 0), SVector(0.0, 0.0, 0.0))), rtol = RTOLERANCE, atol = ATOLERANCE) - - @test isapprox(collect(ta * inv(ta)), collect(identitytransform()), rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(collect(tb * inv(tb)), collect(identitytransform()), rtol = RTOLERANCE, atol = ATOLERANCE) - @test isapprox(collect(inv(ta)), collect(RigidBodyTransform(rotmatd(0, -90, 0), SVector(1.0, 0.0, 0.0))), rtol = RTOLERANCE, atol = ATOLERANCE) - end # testset RigidBodyTransform - - @testset "Interval" begin - pt1 = Intersection(0.3, [4.0, 5.0, 6.0], normalize(rand(3)), 0.4, 0.5, NullInterface()) - pt2 = Intersection(0.5, [1.0, 2.0, 3.0], normalize(rand(3)), 0.2, 0.3, NullInterface()) - @test pt1 < pt2 && pt1 <= pt2 && pt2 > pt1 && pt2 >= pt1 && pt1 != pt2 && pt1 <= pt1 - intvl2 = positivehalfspace(pt1) - @test α(halfspaceintersection(intvl2)) == α(pt1) - - ### interval intersection - ## interval/interval - # one in another - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.0), intersectionat(0.3)) - @test intervalintersection(a, b) == intervalintersection(b, a) == a - # overlap - a = Interval(intersectionat(0.1), intersectionat(0.3)) - b = Interval(intersectionat(0.2), intersectionat(0.4)) - @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.2), intersectionat(0.3)) - # no overlap - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.3), intersectionat(0.4)) - @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval - # start == end - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.2), intersectionat(0.3)) - @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval - ## interval/du - # encompass one - a = Interval(intersectionat(0.0), intersectionat(0.3)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalintersection(a, b) == intervalintersection(b, a) == b[1] - # encompass two - a = Interval(intersectionat(0.0), intersectionat(0.8)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalintersection(a, b) == intervalintersection(b, a) == b - # overlap one - a = Interval(intersectionat(0.0), intersectionat(0.2)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.1), intersectionat(0.2)) - # overlap two - a = Interval(intersectionat(0.2), intersectionat(0.5)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) - res = intervalintersection(a, b) - @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.2), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.5)) - # no overlap - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = DisjointUnion(Interval(intersectionat(0.3), intersectionat(0.4)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval - ## du/du - # no overlap - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.1)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) - @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval - # one in a overlaps one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) - @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.1), intersectionat(0.2)) - # one in a encompasses one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.6), intersectionat(0.7))) - @test intervalintersection(a, b) == intervalintersection(b, a) == b[1] - # one in a overlaps two in b - a = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) - res = intervalintersection(a, b) - @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.2), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.5)) - # one in a encompasses two in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.3), intersectionat(0.4))) - @test intervalintersection(a, b) == intervalintersection(b, a) == b - # each in a overlap one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.6))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.7))) - res = intervalintersection(a, b) - @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.1), intersectionat(0.2)) && res[2] == Interval(intersectionat(0.5), intersectionat(0.6)) - # each in a encompass one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalintersection(a, b) == intervalintersection(b, a) == b - ## empties - intvl = positivehalfspace(pt1) - du = intervalcomplement(Interval(pt1, pt2)) - @test intervalintersection(EmptyInterval(), EmptyInterval()) isa EmptyInterval - @test intervalintersection(EmptyInterval(), intvl) isa EmptyInterval - @test intervalintersection(intvl, EmptyInterval()) isa EmptyInterval - @test intervalintersection(EmptyInterval(), du) isa EmptyInterval - @test intervalintersection(du, EmptyInterval()) isa EmptyInterval - - ### interval union - ## interval/interval - # one in another - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.0), intersectionat(0.3)) - @test intervalunion(a, b) == intervalunion(b, a) == b - # overlap - a = Interval(intersectionat(0.1), intersectionat(0.3)) - b = Interval(intersectionat(0.2), intersectionat(0.4)) - @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.4)) - # no overlap - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.3), intersectionat(0.4)) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == a && res[2] == b - # start == end - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = Interval(intersectionat(0.2), intersectionat(0.3)) - @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.3)) - ## interval/du - # encompass one - a = Interval(intersectionat(0.0), intersectionat(0.3)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == a && res[2] == b[2] - # encompass two - a = Interval(intersectionat(0.0), intersectionat(0.8)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalunion(a, b) == intervalunion(b, a) == a - # overlap one - a = Interval(intersectionat(0.0), intersectionat(0.2)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.6))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == b[2] - # overlap two - a = Interval(intersectionat(0.2), intersectionat(0.5)) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) - @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.6)) - # no overlap - a = Interval(intersectionat(0.1), intersectionat(0.2)) - b = DisjointUnion(Interval(intersectionat(0.3), intersectionat(0.4)), Interval(intersectionat(0.5), intersectionat(0.6))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == a && res[2] == b[1] && res[3] == b[2] - ## du/du - # no overlap - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.1)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == a[1] && res[2] == b[1] && res[3] == a[2] && res[4] == b[2] - # one in a overlaps one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == a[2] && res[3] == b[2] - # one in a encompasses one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.5))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.6), intersectionat(0.7))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == a[1] && res[2] == a[2] && res[3] == b[2] - # one in a overlaps two in b - a = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.1), intersectionat(0.6)) && res[2] == a[2] - # one in a encompasses two in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.3), intersectionat(0.4))) - @test intervalunion(a, b) == intervalunion(b, a) == a - # each in a overlap one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.6))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.7))) - res = intervalunion(a, b) - @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.7)) - # each in a encompass one in b - a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) - b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) - @test intervalunion(a, b) == intervalunion(b, a) == a - ## empties - @test intervalunion(EmptyInterval(), EmptyInterval()) isa EmptyInterval - @test intervalunion(EmptyInterval(), intvl) == intvl - @test intervalunion(intvl, EmptyInterval()) == intvl - @test intervalunion(EmptyInterval(), du) == du - @test intervalunion(du, EmptyInterval()) == du - - # interval complement - int = intervalcomplement(EmptyInterval()) - @test lower(int) isa RayOrigin && upper(int) isa Infinity - - int = intervalcomplement(rayorigininterval(Infinity())) - @test int isa EmptyInterval - - int = intervalcomplement(rayorigininterval(pt1)) - @test lower(int) == pt1 && upper(int) isa Infinity - - int = intervalcomplement(positivehalfspace(pt1)) - @test lower(int) isa RayOrigin && upper(int) == pt1 - - int = intervalcomplement(Interval(pt1, pt2)) - @test lower(int[1]) isa RayOrigin && upper(int[1]) == pt1 && lower(int[2]) == pt2 && upper(int[2]) isa Infinity - - a = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) - res = intervalcomplement(a) - @test res[1] == rayorigininterval(intersectionat(0.1)) && res[2] == Interval(intersectionat(0.3), intersectionat(0.4)) && res[3] == positivehalfspace(intersectionat(0.7)) - - a = DisjointUnion(rayorigininterval(intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.7))) - res = intervalcomplement(a) - @test res[1] == Interval(intersectionat(0.2), intersectionat(0.4)) && res[2] == positivehalfspace(intersectionat(0.7)) - end # testset interval -end # testset General - -@otestset "SurfaceDefs" begin - - @testset "Knots" begin - # find span - knots = KnotVector{Int64}([0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 5]) - function apply(knots::KnotVector, curveorder, u, correctindex) - knotnum = findspan(knots, curveorder, u) - return knotnum == correctindex - end - @test apply(knots, 2, 2.5, 5) && apply(knots, 2, 0, 3) && apply(knots, 2, 4, 6) && apply(knots, 2, 5, 8) - - # insert knots - knots = [0, 0, 0, 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 7, 7] - insertions = knotstoinsert(knots, 3) - @test insertions == [(5, 2), (6, 2), (7, 1), (9, 1), (11, 1), (13, 2)] - end - - @testset "BSpline" begin - # bspline conversion - correctverts = [[0.0, 0.0, 0.0], [0.0, 0.49625, 0.0], [0.6625, 0.49625, 0.65625], [0.0, 0.0, 0.0], [0.6625, 0.49625, 0.65625], [0.6625, 0.0, 0.0], [0.6625, 0.0, 0.0], [0.6625, 0.49625, 0.65625], [1.33, 0.49625, 0.0], [0.6625, 0.0, 0.0], [1.33, 0.49625, 0.0], [1.33, 0.0, 0.0], [0.0, 0.49625, 0.0], [0.0, 1.0, 0.0], [0.6625, 1.0, 0.0], [0.0, 0.49625, 0.0], [0.6625, 1.0, 0.0], [0.6625, 0.49625, 0.65625], [0.6625, 0.49625, 0.65625], [0.6625, 1.0, 0.0], [1.33, 1.0, 0.0], [0.6625, 0.49625, 0.65625], [1.33, 1.0, 0.0], [1.33, 0.49625, 0.0]] - correcttris = [ - 0x00000001 0x00000002 0x00000003 - 0x00000004 0x00000005 0x00000006 - 0x00000007 0x00000008 0x00000009 - 0x0000000a 0x0000000b 0x0000000c - 0x0000000d 0x0000000e 0x0000000f - 0x00000010 0x00000011 0x00000012 - 0x00000013 0x00000014 0x00000015 - 0x00000016 0x00000017 0x00000018 - ] - surf = TestData.bsplinesurface() - points, triangles = makiemesh(makemesh(surf, 2)) - segments = tobeziersegments(surf) # TODO just testing that this evaluates for now - @test triangles == correcttris - @test all(isapprox.(points, correctverts, rtol = RTOLERANCE, atol = ATOLERANCE)) - end # testset BSpline - - @testset "PowerBasis" begin - # polynomial is 3x^2 + 2x + 1 - coeff = [1.0 2.0 3.0] - curve = PowerBasisCurve{OpticSim.Euclidean,Float64,1,2}(coeff) - @test !all(isapprox.(coeff, coefficients(curve, 1), rtol = RTOLERANCE, atol = ATOLERANCE)) # TODO not sure if this is correct/what this is testing? - correct = true - for x in -10.0:0.1:10 - exact = 3 * x^2 + 2 * x + 1 - calculated = point(curve, x)[1] - @test isapprox(exact, calculated, rtol = RTOLERANCE, atol = ATOLERANCE) - end - end # testset PowerBasis - - @testset "BezierSurface" begin - surf = TestData.beziersurface() - fdm = central_fdm(10, 1) - for u in 0:0.1:1, v in 0:0.1:1 - (du, dv) = partials(surf, u, v) - fu(u) = point(surf, u, v) - fv(v) = point(surf, u, v) - # compute accurate finite difference approximation to derivative - fdu, fdv = (fdm(fu, u), fdm(fv, v)) - @test isapprox(du, fdu, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dv, fdv, rtol = 1e-12, atol = 2 * eps(Float64)) - end - end # testset BezierSurface - - @testset "ZernikeSurface" begin - surf = TestData.zernikesurface1a() # with normradius - fdm = central_fdm(10, 1) - for ρ in 0.05:0.1:0.95, ϕ in 0:(π / 10):(2π) - (dρ, dϕ) = partials(surf, ρ, ϕ) - fρ(ρ) = point(surf, ρ, ϕ) - fϕ(ϕ) = point(surf, ρ, ϕ) - # compute accurate finite difference approximation to derivative - fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) - @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) - end - - @test !any(isnan.(normal(TestData.conicsurface(), 0.0, 0.0))) - end # testset ZernikeSurface - - @testset "QTypeSurface" begin - # test predefined values against papers - # Forbes, G. W. "Robust, efficient computational methods for axially symmetric optical aspheres." OpticSim express 18.19 (2010): 19700-19712. - # Forbes, G. W. "Characterizing the shape of freeform optics." OpticSim express 20.3 (2012): 2483-2499. - - f0_true = [2, sqrt(19 / 4), 4 * sqrt(10 / 19), 1 / 2 * sqrt(509 / 10), 6 * sqrt(259 / 509), 1 / 2 * sqrt(25607 / 259)] - for i in 1:length(f0_true) - @test isapprox(OpticSim.QType.f0(i - 1), f0_true[i], rtol = 2 * eps(Float64)) - end - g0_true = [-1 / 2, -5 / (2 * sqrt(19)), -17 / (2 * sqrt(190)), -91 / (2 * sqrt(5090)), -473 / (2 * sqrt(131831))] - for i in 1:length(g0_true) - @test isapprox(OpticSim.QType.g0(i - 1), g0_true[i], rtol = 2 * eps(Float64)) - end - h0_true = [-1 / 2, -6 / sqrt(19), -3 / 2 * sqrt(19 / 10), -20 * sqrt(10 / 509)] - for i in 1:length(h0_true) - @test isapprox(OpticSim.QType.h0(i - 1), h0_true[i], rtol = 2 * eps(Float64)) - end - - F_true = [1/4 1/2 27/32 5/4; 15/32 7/8 35/16 511/128; 17/72 35/36 35/16 23/6; 29/40 67/40 243/80 12287/2560] - N, M = size(F_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.F(m, n), F_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - G_true = [1/4 3/8 15/32 35/64; -1/24 -5/48 -7/32 -21/64; -7/40 -7/16 -117/160 -33/32] - N, M = size(G_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.G(m, n), G_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - f_true = [1/2 1/sqrt(2) 3 / 4*sqrt(3 / 2) sqrt(5)/2; 1 / 4*sqrt(7 / 2) 1 / 4*sqrt(19 / 2) 1 / 4*sqrt(185 / 6) 3 / 32*sqrt(427); 1 / 6*sqrt(115 / 14) 1 / 2*sqrt(145 / 38) 1 / 4*sqrt(12803 / 370) 1 / 2*sqrt(2785 / 183); 1 / 5*sqrt(3397 / 230) 1 / 4*sqrt(6841 / 290) 9 / 4*sqrt(14113 / 25606) 1 / 16*sqrt(1289057 / 1114)] - N, M = size(f_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.f(m, n), f_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - g_true = [1/2 3/(4 * sqrt(2)) 5/(4 * sqrt(6)) 7 * sqrt(5)/32; -1/(3 * sqrt(14)) -5/(6 * sqrt(38)) -7 / 4*sqrt(3 / 370) -1 / 2*sqrt(7 / 61); -21 / 10*sqrt(7 / 230) -7 / 4*sqrt(19 / 290) -117 / 4*sqrt(37 / 128030) -33 / 16*sqrt(183 / 2785)] - N, M = size(g_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.g(m, n), g_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - A_true = [2 3 5 7 9 11; -4/3 2/3 2 76/27 85/24 106/25; 9/5 26/15 2 117/50 203/75 108/35; 55/28 66/35 2 536/245 135/56 130/49; 161/81 122/63 2 515/243 143/63 161/66] - N, M = size(A_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.A(m, n), A_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - B_true = [-1 -2 -4 -6 -8 -10; -8/3 -4 -4 -40/9 -5 -28/5; -24/5 -4 -4 -21/5 -112/25 -24/5; -30/7 -4 -4 -144/35 -30/7 -220/49; -112/27 -4 -4 -110/27 -88/21 -13/3] - N, M = size(B_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.B(m, n), B_true[n + 1, m], rtol = 2 * eps(Float64)) - end - end - C_true = [NaN NaN NaN NaN NaN NaN; -11/3 -3 -5/3 -35/27 -9/8 -77/75; 0 5/9 7/15 21/50 88/225 13/35; 27/28 21/25 27/35 891/1225 39/56 33/49; 80/81 45/49 55/63 1430/1701 40/49 1105/1386] - N, M = size(C_true) - for m in 1:M - for n in 0:(N - 1) - @test isapprox(OpticSim.QType.C(m, n), C_true[n + 1, m], rtol = 2 * eps(Float64)) || (isnan(OpticSim.QType.C(m, n)) && isnan(C_true[n + 1, m])) - end - end - - # test deriv with finite differences - surf = TestData.qtypesurface1() - fdm = central_fdm(10, 1) - for ρ in 0.05:0.1:0.95, ϕ in 0:(π / 10):(2π) - (dρ, dϕ) = partials(surf, ρ, ϕ) - fρ(ρ) = point(surf, ρ, ϕ) - fϕ(ϕ) = point(surf, ρ, ϕ) - # compute accurate finite difference approximation to derivative - fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) - @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) - end - end # testset QTypeSurface - - @testset "GridSag" begin - surf = TestData.gridsagsurfacelinear() - fdm = central_fdm(10, 1) - # doesn't enforce C1 across patch boundaries meaning that finite differences won't match at all - # just test within a patch to make sure partials() is working - for ρ in 0.02:0.01:0.18, ϕ in 0:(π / 30):(2π) - (dρ, dϕ) = partials(surf, ρ, ϕ) - fρ(ρ) = point(surf, ρ, ϕ) - fϕ(ϕ) = point(surf, ρ, ϕ) - # compute accurate finite difference approximation to derivative - fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) - @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) - end - - surf = TestData.gridsagsurfacebicubic() - fdm = central_fdm(10, 1) - # doesn't enforce C2 across patch boundaries meaning that finite differences won't match exactly - # just test within a patch to make sure partials() is working - for ρ in 0.02:0.01:0.18, ϕ in 0:(π / 30):(2π) - (dρ, dϕ) = partials(surf, ρ, ϕ) - fρ(ρ) = point(surf, ρ, ϕ) - fϕ(ϕ) = point(surf, ρ, ϕ) - # compute accurate finite difference approximation to derivative - fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) - @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) - end - - surf = TestData.gridsagsurfacebicubiccheby() - fdm = central_fdm(10, 1) - # not valid at the very boundary of the surface as stuff gets weird outside |u| < 1 and |v| < 1 - for u in -0.3:0.01:0.3, v in -0.15:0.01:0.15 - (du, dv) = partials(surf, u, v) - fu(u) = point(surf, u, v) - fv(v) = point(surf, u, v) - # compute accurate finite difference approximation to derivative - fdu, fdv = (fdm(fu, u), fdm(fv, v)) - @test isapprox(du, fdu, rtol = 1e-12, atol = 2 * eps(Float64)) - @test isapprox(dv, fdv, rtol = 1e-12, atol = 2 * eps(Float64)) - end - end # testset GridSag - - @testset "ChebyshevSurface" begin - surf = TestData.chebyshevsurface1() # with normradius - fdm = central_fdm(10, 1) - # not valid at the very boundary of the surface as stuff gets weird outside |u| < 1 and |v| < 1 - for u in -0.99:0.1:0.99, v in -0.99:0.1:0.99 - (du, dv) = partials(surf, u, v) - fu(u) = point(surf, u, v) - fv(v) = point(surf, u, v) - # compute accurate finite difference approximation to derivative - fdu, fdv = (fdm(fu, u), fdm(fv, v)) - @test isapprox(du, fdu, rtol = 1e-11, atol = 2 * eps(Float64)) #changed rtol from 1e-12 to 1e-11. FiniteDifferences approximation to the derivative had larger than expected error. - @test isapprox(dv, fdv, rtol = 1e-11, atol = 2 * eps(Float64)) #changed rtol from 1e-12 to 1e-11. FiniteDifferences approximation to the derivative had larger than expected error. - end - end # testset ChebyshevSurface - -end # testset SurfaceDefs - -@otestset "Intersection" begin - function samplepoints(numsamples, lowu, highu, lowv, highv) - samples = Array{Tuple{Float64,Float64},1}(undef, 0) - - for i in 0:numsamples - u = lowu * i / numsamples + (1 - i / numsamples) * highu - for j in 0:numsamples - v = lowv * j / numsamples + (1 - j / numsamples) * highv - push!(samples, (u, v)) - end - end - return samples - end - - @testset "Cylinder" begin - # Random.seed!(SEED) - - # # random point intersections - # ur, vr = uvrange(Cylinder) - # for i in 1:10000 - # cyl = Cylinder(rand() * 3.0, rand() * 50.0) - # u, v = rand() * (ur[2] - ur[1]) + ur[1], rand() * (vr[2] - vr[1]) + vr[1] - # pointon = point(cyl, u, v) - # origin = SVector(4.0, 4.0, 4.0) - # r = Ray(origin, pointon .- origin) - # halfspace = surfaceintersection(cyl, r) - # @test halfspace !== nothing && !((lower(halfspace) isa RayOrigin) || (upper(halfspace) isa Infinity)) - # @test isapprox(pointon, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) || isapprox(pointon, point(upper(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) - # end - - # on xy - cyl = Cylinder(0.5) - r = Ray([1.0, 1.0, 0.0], [-1.0, -1.0, 0.0]) - intscts = surfaceintersection(cyl, r) - xy = sqrt(2) / 4.0 - pt1 = [xy, xy, 0.0] - pt2 = [-xy, -xy, 0.0] - cpt1 = point(lower(intscts)) - cpt2 = point(upper(intscts)) - @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) - - # starting inside - r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - intscts = surfaceintersection(cyl, r) - @test lower(intscts) isa RayOrigin && isapprox(pt1, point(upper(intscts)), rtol = RTOLERANCE, atol = ATOLERANCE) - - # inside infinite - r = Ray([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) - intscts = surfaceintersection(cyl, r) - @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity - - # on surface - r = Ray([0.5, 0.0, 0.0], [0.0, 0.0, 1.0]) - intscts = surfaceintersection(cyl, r) - @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity - - # on diagonal - r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) - intscts = surfaceintersection(cyl, r) - cpt1 = point(lower(intscts)) - cpt2 = point(upper(intscts)) - pt1 = [xy, xy, xy] - pt2 = [-xy, -xy, -xy] - @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) - - # missing - r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) - intscts = surfaceintersection(cyl, r) - @test intscts isa EmptyInterval - end # testset cylinder - - @testset "Sphere" begin - # Random.seed!(SEED) - - # # random point intersections - # ur, vr = uvrange(Sphere) - # for i in 1:10000 - # sph = Sphere(rand() * 3) - # u, v = rand() * (ur[2] - ur[1]) + ur[1], rand() * (vr[2] - vr[1]) + vr[1] - # pointon = point(sph, u, v) - # origin = SVector(4.0, 4.0, 4.0) - # r = Ray(origin, pointon .- origin) - # halfspace = surfaceintersection(sph, r) - # @test halfspace !== nothing && !((lower(halfspace) isa RayOrigin) || (upper(halfspace) isa Infinity)) - # @test isapprox(pointon, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) || isapprox(pointon, point(upper(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) - # end - - # on xy plane - sph = Sphere(0.5) - r = Ray([1.0, 1.0, 0.0], [-1.0, -1.0, 0.0]) - intscts = surfaceintersection(sph, r) - xy = sqrt(2) / 4.0 - pt1 = [xy, xy, 0.0] - pt2 = [-xy, -xy, 0.0] - cpt1 = point(lower(intscts)) - cpt2 = point(upper(intscts)) - @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) - - # starting inside - r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - intscts = surfaceintersection(sph, r) - @test lower(intscts) isa RayOrigin && isapprox(pt1, point(upper(intscts)), rtol = RTOLERANCE, atol = ATOLERANCE) - - # on diagonal - r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) - intscts = surfaceintersection(sph, r) - cpt1 = point(lower(intscts)) - cpt2 = point(upper(intscts)) - xyz = sqrt(3) / 6.0 - pt1 = [xyz, xyz, xyz] - pt2 = [-xyz, -xyz, -xyz] - @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) - - # missing - r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) - intscts = surfaceintersection(sph, r) - @test intscts isa EmptyInterval - - # tangent - r = Ray([0.5, 0.5, 0.0], [0.0, -1.0, 0.0]) - intscts = surfaceintersection(sph, r) - @test intscts isa EmptyInterval - end # testset sphere - - @testset "Spherical Cap" begin - @test_throws AssertionError SphericalCap(0.0, 1.0) - @test_throws AssertionError SphericalCap(1.0, 0.0) - - sph = SphericalCap(0.5, π / 3, SVector(1.0, 1.0, 0.0), SVector(1.0, 1.0, 1.0)) - - r = Ray([10.0, 10.0, 1.0], [-1.0, -1.0, 0.0]) - int = surfaceintersection(sph, r) - @test isapprox(point(lower(int)), [1.0, 1.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(int) isa Infinity - - r = Ray([0.8, 1.4, 1.2], [1.0, -1.0, 0.0]) - int = surfaceintersection(sph, r) - @test isapprox(point(lower(int)), [0.898231126982741, 1.301768873017259, 1.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [1.301768873017259, 0.8982311269827411, 1.2], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([2.0, 2.0, 1.5], [-1.0, -1.0, 0.0]) - @test surfaceintersection(sph, r) isa EmptyInterval - end # testset spherical cap - - @testset "Triangle" begin - Random.seed!(SEED) - - tri = Triangle(SVector{3}(2.0, 1.0, 1.0), SVector{3}(1.0, 2.0, 1.0), SVector{3}(1.0, 1.0, 2.0), SVector{2}(0.0, 0.0), SVector{2}(1.0, 0.0), SVector{2}(0.5, 0.5)) - - # random point intersections - for i in 1:1000 - barycentriccoords = rand(3) - barycentriccoords /= sum(barycentriccoords) - pointontri = point(tri, barycentriccoords...) - origin = SVector(3.0, 3.0, 3.0) - r = Ray(origin, pointontri .- origin) - halfspace = surfaceintersection(tri, r) - @test !(halfspace isa EmptyInterval) && upper(halfspace) isa Infinity - t = α(lower(halfspace)) - @test isapprox(point(r, t), point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pointontri, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) - end - - # front and back face intersections - tri = Triangle(SVector{3}(0.0, 0.0, 0.0), SVector{3}(1.0, 0.0, 0.0), SVector{3}(0.0, 1.0, 0.0), SVector{2}(0.0, 0.0), SVector{2}(1.0, 0.0), SVector{2}(0.0, 1.0)) - r1 = Ray([0.1, 0.1, 1.0], [0.0, 0.0, -1.0]) - r2 = Ray([0.1, 0.1, -1.0], [0.0, 0.0, 1.0]) - - intsct1 = halfspaceintersection(surfaceintersection(tri, r1)) - intsct2 = halfspaceintersection(surfaceintersection(tri, r2)) - - @test isapprox(point(intsct1), point(intsct2), rtol = RTOLERANCE, atol = ATOLERANCE) - - # ray should miss - r = Ray([1.0, 1.0, 1.0], [0.0, 0.0, -1.0]) - intsct = surfaceintersection(tri, r) - @test intsct isa EmptyInterval - end # testset triangle - - @testset "Plane" begin - rinplane = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 0.0]) - routside = Ray([0.0, 0.0, 2.0], [1.0, 1.0, 1.0]) - rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) - routintersects = Ray([0.0, 0.0, 2.0], [0.0, 0.0, -1.0]) - rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) - pln = Plane(0.0, 0.0, 1.0, 0.0, 0.0, 1.0) - - # parallel to plane and outside - @test surfaceintersection(pln, routside) isa EmptyInterval - - # NOTE for coplanar faces to work with visualization, rays in the plane must count as being 'inside' - # parallel and on plane - res = surfaceintersection(pln, rinplane) - @test lower(res) isa RayOrigin && upper(res) isa Infinity - - # inside but not hitting - res = surfaceintersection(pln, rinside) - @test lower(res) isa RayOrigin && upper(res) isa Infinity - - # starts outside and hits - res = surfaceintersection(pln, routintersects) - @test isapprox(point(lower(res)), [0.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits - res = surfaceintersection(pln, rinintersects) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) - end # testset plane - - @testset "Rectangle" begin - @test_throws AssertionError Rectangle(0.0, 1.0) - @test_throws AssertionError Rectangle(1.0, 0.0) - - rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) - rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) - routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) - routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) - rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) - rinbounds = Ray([0.5, 0.5, -1.0], [0.0, 0.0, 1.0]) - routbounds = Ray([0.5, 0.5, 1.0], [0.0, 0.0, -1.0]) - - rect = Rectangle(0.5, 0.5) - - # parallel to plane and outside - @test surfaceintersection(rect, routside) isa EmptyInterval - - # parallel and on plane - @test surfaceintersection(rect, rinplane) isa EmptyInterval - - # inside but not hitting - @test surfaceintersection(rect, rinside) isa EmptyInterval - - # starts outside and hits - res = surfaceintersection(rect, routintersects) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits - res = surfaceintersection(rect, rinintersects) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # starts inside and misses bounds - @test surfaceintersection(rect, rinmiss) isa EmptyInterval - - # starts outside and misses bounds - @test surfaceintersection(rect, routmiss) isa EmptyInterval - - # starts outside and hits bounds - res = surfaceintersection(rect, routbounds) - @test isapprox(point(lower(res)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits bounds - res = surfaceintersection(rect, rinbounds) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # test a rectangle with translation and rotation - rect2 = Rectangle(0.3, 0.5, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) - rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) - raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) - res = surfaceintersection(rect2, rayhit) - @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - @test surfaceintersection(rect2, raymiss) isa EmptyInterval - end # testset Rectangle - - @testset "Ellipse" begin - @test_throws AssertionError Ellipse(0.0, 1.0) - @test_nowarn Circle(0.5) - - rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) - rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) - routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) - routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) - rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) - rinbounds = Ray([0.5, 0.0, -1.0], [0.0, 0.0, 1.0]) - routbounds = Ray([0.5, 0.0, 1.0], [0.0, 0.0, -1.0]) - rasymhit = Ray([0.0, 0.9, 1.0], [0.0, 0.0, -1.0]) - rasymmiss = Ray([0.9, 0.0, 1.0], [0.0, 0.0, -1.0]) - - ell = Ellipse(0.5, 1.0) - - # parallel to plane and outside - @test surfaceintersection(ell, routside) isa EmptyInterval - - # parallel and on plane - @test surfaceintersection(ell, rinplane) isa EmptyInterval - - # inside but not hitting - @test surfaceintersection(ell, rinside) isa EmptyInterval - - # starts outside and hits - res = surfaceintersection(ell, routintersects) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits - res = surfaceintersection(ell, rinintersects) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # starts inside and misses bounds - @test surfaceintersection(ell, rinmiss) isa EmptyInterval - - # starts outside and misses bounds - @test surfaceintersection(ell, routmiss) isa EmptyInterval - - # starts outside and hits bounds - res = surfaceintersection(ell, routbounds) - @test isapprox(point(lower(res)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits bounds - res = surfaceintersection(ell, rinbounds) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # asymmetric hit - res = surfaceintersection(ell, rasymhit) - @test isapprox(point(lower(res)), [0.0, 0.9, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # asymmetric miss - @test surfaceintersection(ell, rasymmiss) isa EmptyInterval - - # test an ellipse with translation and rotation - ell2 = Ellipse(0.3, 0.5, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) - rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) - raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) - res = surfaceintersection(ell2, rayhit) - @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - @test surfaceintersection(ell2, raymiss) isa EmptyInterval - end # testset Ellipse - - @testset "Hexagon" begin - @test_nowarn Hexagon(0.5) - - rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) - rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) - routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) - routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) - rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) - rinbounds = Ray([sqrt(3) / 2, 0.0, -1.0], [0.0, 0.0, 1.0]) - routbounds = Ray([sqrt(3) / 2, 0.0, 1.0], [0.0, 0.0, -1.0]) - rasymhit = Ray([0.0, 1.0, 1.0], [0.0, 0.0, -1.0]) - rasymmiss = Ray([1.0, 0.0, 1.0], [0.0, 0.0, -1.0]) - - hex = Hexagon(1.0) - - # parallel to plane and outside - @test surfaceintersection(hex, routside) isa EmptyInterval - - # parallel and on plane - @test surfaceintersection(hex, rinplane) isa EmptyInterval - - # inside but not hitting - @test surfaceintersection(hex, rinside) isa EmptyInterval - - # starts outside and hits - res = surfaceintersection(hex, routintersects) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits - res = surfaceintersection(hex, rinintersects) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # starts inside and misses bounds - @test surfaceintersection(hex, rinmiss) isa EmptyInterval - - # starts outside and misses bounds - @test surfaceintersection(hex, routmiss) isa EmptyInterval - - # starts outside and hits bounds - res = surfaceintersection(hex, routbounds) - @test isapprox(point(lower(res)), [sqrt(3) / 2, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # starts inside and hits bounds - res = surfaceintersection(hex, rinbounds) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [sqrt(3) / 2, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # asymmetric hit - res = surfaceintersection(hex, rasymhit) - @test isapprox(point(lower(res)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # asymmetric miss - @test surfaceintersection(hex, rasymmiss) isa EmptyInterval - - # test an ellipse with translation and rotation - hex2 = Hexagon(0.4, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) - rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) - raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) - res = surfaceintersection(hex2, rayhit) - @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - @test surfaceintersection(hex2, raymiss) isa EmptyInterval - end # testset Hexagon - - @testset "Stops" begin - infiniterect = RectangularAperture(0.4, 0.8, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) - finiterect = RectangularAperture(0.4, 0.8, 1.0, 2.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) - # through hole - r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(infiniterect, r) isa EmptyInterval - @test surfaceintersection(finiterect, r) isa EmptyInterval - r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) - @test surfaceintersection(infiniterect, r) isa EmptyInterval - @test surfaceintersection(finiterect, r) isa EmptyInterval - # on edge - r = Ray([0.0, 1.0, 0.6], [0.0, -1.0, 0.0]) - res = surfaceintersection(infiniterect, r) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - res = surfaceintersection(finiterect, r) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - # through hole asym - r = Ray([0.7, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(infiniterect, r) isa EmptyInterval - @test surfaceintersection(finiterect, r) isa EmptyInterval - # on finite edge - r = Ray([1.0, -1.0, 2.0], [0.0, 1.0, 0.0]) - res = surfaceintersection(infiniterect, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) - res = surfaceintersection(finiterect, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) - # outside finite - r = Ray([2.0, 1.0, 3.0], [0.0, -1.0, 0.0]) - @test isapprox(point(surfaceintersection(infiniterect, r)), [2.0, 0.0, 3.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test surfaceintersection(finiterect, r) isa EmptyInterval - - infinitecirc = CircularAperture(0.4, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) - finitecirc = CircularAperture(0.4, 1.0, 2.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) - # through hole - r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(infinitecirc, r) isa EmptyInterval - @test surfaceintersection(finitecirc, r) isa EmptyInterval - r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) - @test surfaceintersection(infinitecirc, r) isa EmptyInterval - @test surfaceintersection(finitecirc, r) isa EmptyInterval - # on edge - r = Ray([0.0, 1.0, 0.6], [0.0, -1.0, 0.0]) - res = surfaceintersection(infinitecirc, r) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - res = surfaceintersection(finitecirc, r) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - # through hole 2 - r = Ray([0.3, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(infinitecirc, r) isa EmptyInterval - @test surfaceintersection(finitecirc, r) isa EmptyInterval - # on finite edge - r = Ray([1.0, -1.0, 2.0], [0.0, 1.0, 0.0]) - res = surfaceintersection(infinitecirc, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) - res = surfaceintersection(finitecirc, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) - # outside finite - r = Ray([2.0, 1.0, 3.0], [0.0, -1.0, 0.0]) - @test isapprox(point(surfaceintersection(infinitecirc, r)), [2.0, 0.0, 3.0], rtol = RTOLERANCE, atol = ATOLERANCE) - @test surfaceintersection(finitecirc, r) isa EmptyInterval - - annulus = Annulus(0.5, 1.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) - # through hole - r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(annulus, r) isa EmptyInterval - r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) - @test surfaceintersection(annulus, r) isa EmptyInterval - # on edge - r = Ray([0.0, 1.0, 0.5], [0.0, -1.0, 0.0]) - res = surfaceintersection(annulus, r) - @test isapprox(point(lower(res)), [0.0, 0.0, 0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - # through hole 2 - r = Ray([0.3, 1.0, 1.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(infiniterect, r) isa EmptyInterval - @test surfaceintersection(annulus, r) isa EmptyInterval - # on finite edge - r = Ray([1.0, -1.0, 1.0], [0.0, 1.0, 0.0]) - res = surfaceintersection(annulus, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) - # outside finite - r = Ray([0.9, 1.0, 1.9], [0.0, -1.0, 0.0]) - @test surfaceintersection(annulus, r) isa EmptyInterval - end # testset Stops - - @testset "Bezier" begin - surf = TestData.beziersurface() - accelsurf = AcceleratedParametricSurface(surf) - numsamples = 100 - - samples = samplepoints(numsamples, 0.05, 0.95, 0.05, 0.95) - missedintersections = 0 - - for uv in samples - pt = point(surf, uv[1], uv[2]) - origin = [0.5, 0.5, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(accelsurf, r) - if allintersections isa EmptyInterval - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - @test isapprox(point(halfspaceintersection(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test upper(intsct) isa Infinity - end - end - end - - if missedintersections > 0 - @warn "$missedintersections out of total $(length(samples)) bezier suface intersections were missed" - end - - # miss from outside - r = Ray([0.5, 0.5, 5.0], [0.0, 0.0, 1.0]) - @test surfaceintersection(accelsurf, r) isa EmptyInterval - r = Ray([5.0, 0.5, -5.0], [0.0, 0.0, -1.0]) - @test surfaceintersection(accelsurf, r) isa EmptyInterval - - # miss from inside - r = Ray([0.5, 0.5, -5.0], [0.0, 0.0, -1.0]) - res = surfaceintersection(accelsurf, r) - # TODO!! Fix bezier surface to create halfspace - # @test lower(res) isa RayOrigin && upper(res) isa Infinity - - # hit from inside - r = Ray([0.5, 0.5, 0.2], [0.0, 1.0, 0.0]) - res = surfaceintersection(accelsurf, r) - @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.8996900152986361, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit from outside - r = Ray([0.0, 0.5, 0.2], [1.0, 0.0, -1.0]) - res = surfaceintersection(accelsurf, r) - @test isapprox(point(lower(res)), [0.06398711204047353, 0.5, 0.13601288795952649], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity - - # two hits from outside - r = Ray([0.0, 0.5, 0.25], [1.0, 0.0, 0.0]) - res = surfaceintersection(accelsurf, r) - @test isapprox(point(lower(res)), [0.12607777053030267, 0.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res)), [0.8705887077060419, 0.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - - surf = TestData.wavybeziersurface() - accelsurf = AcceleratedParametricSurface(surf) - - # 3 hit starting outside - r = Ray([0.5, 0.0, 0.0], [0.0, 1.0, 0.0]) - res = surfaceintersection(accelsurf, r) - @test isa(res, DisjointUnion) && length(res) == 2 && isapprox(point(lower(res[1])), [0.5, 0.10013694786182059, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.5, 0.49625, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.8971357794109067, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[2]) isa Infinity) - - # 3 hit starting inside - r = Ray([0.5, 1.0, 0.0], [0.0, -1.0, 0.0]) - res = surfaceintersection(accelsurf, r) - @test isa(res, DisjointUnion) && length(res) == 2 && (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.5, 0.8971357794109067, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.49625, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.5, 0.10013694786182059, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - surf = TestData.verywavybeziersurface() - accelsurf = AcceleratedParametricSurface(surf, 20) - - # five hits starting outside - r = Ray([0.9, 0.0, -0.3], [0.0, 1.0, 0.7]) - res = surfaceintersection(accelsurf, r) - a = isapprox(point(lower(res[1])), [0.9, 0.03172286522032046, -0.2777939943457758], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.1733979947040411, -0.17862140370717122], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) - @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # five hits starting inside - r = Ray([0.9, 1.0, 0.4], [0.0, -1.0, -0.7]) - res = surfaceintersection(accelsurf, r) - a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.17339799470404108, -0.1786214037071712], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[3])), [0.9, 0.03172286522032046, -0.27779399434577573], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # 4 hits starting inside - r = Ray([0.1, 0.0, -0.3], [0.0, 1.0, 0.7]) - res = surfaceintersection(accelsurf, r) - a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.1, 0.2851860296285551, -0.10036977926001144], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.1, 0.5166793625025807, 0.06167555375180668], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.1, 0.7770862508789854, 0.24396037561528983], rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(point(lower(res[3])), [0.1, 0.98308919558696, 0.3881624369108719], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) - @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # 4 hits starting outside - r = Ray([0.9, 0.9, 0.4], [0.0, -0.9, -0.7]) - res = surfaceintersection(accelsurf, r) - a = isapprox(point(lower(res[1])), [0.9, 0.736072142615238, 0.2725005553674076], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.567439326091764, 0.141341698071372], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.16601081959179267, -0.1708804736508277], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.032434058775915924, -0.274773509840954], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(res, DisjointUnion) && length(res) == 2 && a && b - end # testset Bezier - - @testset "Zernike" begin - @test_nowarn ZernikeSurface(1.5) - @test_throws AssertionError ZernikeSurface(0.0) - @test_throws AssertionError ZernikeSurface(1.5, aspherics = [(1, 0.1)]) - - z1 = TestData.zernikesurface1() - az1 = AcceleratedParametricSurface(z1, 20) - numsamples = 100 - - # test that an on axis ray doesn't miss - @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) - - samples = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) - missedintersections = 0 - - for uv in samples - pt = point(z1, uv[1], uv[2]) - origin = [0.0, 0.0, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(az1, r) - if (allintersections isa EmptyInterval) - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - if lower(intsct) isa RayOrigin - missedintersections += 1 - else - # closest point should be on the surface, furthest should be on bounding prism - @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) - end - end - end - end - - if missedintersections > 0 - @warn "$missedintersections out of total $(length(samples)) zernike suface intersections were missed" - end - - # hit from inside - r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.12363711269619936, 0.24727422539239863, 0.11818556348099664], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit from outside - r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.07118311042701463, 0.1423662208540292, 0.144084447864927], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) - - # miss from inside - r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) - - # miss from outside - r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - - # two hits from outside - r = Ray([2.0, 0.0, 0.25], [-1.0, 0.0, 0.0]) - hit = surfaceintersection(az1, r) - a = isapprox(point(lower(hit[1])), [1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.3571758210851095, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(hit[2])), [-1.2665828947521165, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b - - # hit cyl from outside - r = Ray([0.0, 2.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.0, 1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit cyl from inside - r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - z2 = TestData.zernikesurface2() - az2 = AcceleratedParametricSurface(z2, 20) - - # two hits - r = Ray([2.0, -1.0, 0.2], [-1.0, 0.0, 0.0]) - intvl = surfaceintersection(az2, r) - @test isapprox(point(lower(intvl)), [0.238496383738369, -1.0, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [-0.8790518227874484, -1.0, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) - - # three hits - r = Ray([-0.7, 1.0, 0.2], [0.0, -1.0, 0.0]) - hit = surfaceintersection(az2, r) - a = (lower(hit[1]) isa RayOrigin) && isapprox(point(upper(hit[1])), [-0.7, 0.8732489598020176, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(hit[2])), [-0.7, -0.34532502965048606, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.7, -1.1615928003236047, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b - - # four hits - r = Ray([1.5, 0.1, 0.35], [-1.0, -0.5, 0.0]) - hit = surfaceintersection(az2, r) - a = isapprox(point(lower(hit[1])), [1.4371467631969683, 0.06857338159848421, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.1428338578611368, -0.07858307106943167, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(hit[2])), [0.12916355683491865, -0.5854182215825409, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.7290713614371003, -1.0145356807185504, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b - - # failure case, had a bug where rounding error would cause o2 to fail - o1 = leaf(AcceleratedParametricSurface(ZernikeSurface(1.4 * 1.15)), translation(0.0, 0.0, 3.0))() - o2 = leaf(AcceleratedParametricSurface(ZernikeSurface(1.4 * 1.15)), translation(2.0, 0.0, 3.0))() - r = Ray([-5.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - @test !(surfaceintersection(o1, r) isa EmptyInterval) - @test !(surfaceintersection(o2, r) isa EmptyInterval) - end # testset Zernike - - @testset "QType" begin - @test_nowarn QTypeSurface(1.5) - @test_throws AssertionError QTypeSurface(0.0) - - q1 = TestData.qtypesurface1() - aq1 = AcceleratedParametricSurface(q1, 20) - numsamples = 100 - - # test that an on axis ray doesn't miss - @test !(surfaceintersection(AcceleratedParametricSurface(q1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) - - samples = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) - missedintersections = 0 - - for uv in samples - pt = point(q1, uv[1], uv[2]) - origin = [0.0, 0.0, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(aq1, r) - if (allintersections isa EmptyInterval) - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - if lower(intsct) isa RayOrigin - missedintersections += 1 - else - # closest point should be on the surface, furthest should be on bounding prism - @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) - end - end - end - end - - if missedintersections > 0 - @warn "$missedintersections out of total $(length(samples)) qtype suface intersections were missed" - end - - # hit from inside - r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) - intvl = surfaceintersection(aq1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.09758258750208074, 0.19516517500416142, -0.01208706248959643], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit from outside - r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) - intvl = surfaceintersection(aq1, r) - @test isapprox(point(lower(intvl)), [0.10265857811957124, 0.20531715623914257, -0.013292890597856405], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) - - # miss from inside - r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) - intvl = surfaceintersection(aq1, r) - @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) - - # miss from outside - r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(aq1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) - @test surfaceintersection(aq1, r) isa EmptyInterval - r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(aq1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(aq1, r) isa EmptyInterval - end # testset QType - - @testset "BoundingBox" begin - function boundingval(a::BoundingBox{T}, axis::Int, plane::Bool) where {T<:Real} - if axis === 1 - return plane ? a.xmax : a.xmin - elseif axis === 2 - return plane ? a.ymax : a.ymin - elseif axis == 3 - return plane ? a.zmax : a.zmin - else - throw(ErrorException("Invalid axis: $axis")) - end - end - - Random.seed!(SEED) - - axis(x) = (x + 1) ÷ 2 - face(x) = mod(x, 2) - facevalue(a::BoundingBox, x) = boundingval(a, axis(x), Bool(face(x))) - boundingindices = ((2, 3), (1, 3), (1, 2)) - - function facepoint(a::BoundingBox, facenumber) - axisindex = axis(facenumber) - indices = boundingindices[axisindex] - b1min, b1max = boundingval(a, indices[1], false), boundingval(a, indices[1], true) - b2min, b2max = boundingval(a, indices[2], false), boundingval(a, indices[2], true) - - step = 0.00001 - pt1val = rand((b1min + step):step:(b1max - step)) - pt2val = rand((b2min + step):step:(b2max - step)) - - result = Array{Float64,1}(undef, 3) - result[indices[1]] = pt1val - result[indices[2]] = pt2val - result[axisindex] = facevalue(a, facenumber) - return result - end - - bbox = BoundingBox(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0) - - for i in 1:10000 - # randomly pick two planes - face1 = rand(1:6) - face2 = rand(1:6) - while face2 == face1 - face2 = rand(1:6) - end - # pick a point on each face - pt1 = facepoint(bbox, face1) - pt2 = facepoint(bbox, face2) - - direction = normalize(pt1 - pt2) - origin = pt2 - 3 * direction - - r = Ray(origin, direction) - - @test doesintersect(bbox, r) - end - - # should miss - @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [1.0, 0.0, 0.0])) - @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [0.0, 1.0, 0.0])) - @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [0.0, 0.0, 1.0])) - @test !doesintersect(bbox, Ray([-2.0, 0.0, 0.0], [-1.0, 0.0, 0.0])) - @test !doesintersect(bbox, Ray([0.0, -2.0, 0.0], [0.0, -1.0, 0.0])) - @test !doesintersect(bbox, Ray([0.0, 0.0, -2.0], [0.0, 0.0, -1.0])) - - bbox = BoundingBox(-0.5, 0.5, -0.75, 0.75, typemin(Float64), typemax(Float64)) - - # starting outside - r = Ray([-1.0, -1.1, 0.0], [1.0, 1.0, 0.0]) - intscts = surfaceintersection(bbox, r) - @test isapprox(point(lower(intscts)), [-0.5, -0.6, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intscts)), [0.5, 0.4, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # starting inside - r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) - intscts = surfaceintersection(bbox, r) - @test lower(intscts) isa RayOrigin && isapprox(point(upper(intscts)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # inside infinite - r = Ray([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) - intscts = surfaceintersection(bbox, r) - @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity - - # on surface - r = Ray([0.5, 0.0, 0.0], [0.0, 0.0, 1.0]) - intscts = surfaceintersection(bbox, r) - @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity - - # missing - r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) - intscts = surfaceintersection(bbox, r) - @test intscts isa EmptyInterval - - r = Ray([5.0, 5.0, 5.0], [0.0, -1.0, 0.0]) - intscts = surfaceintersection(bbox, r) - @test intscts isa EmptyInterval - end # testset BoundingBox - - @testset "CSG" begin - pln1 = Plane([0.0, 0.0, -1.0], [0.0, 0.0, -1.0]) - pln2 = Plane([0.0, 0.0, 1.0], [0.0, 0.0, 1.0]) - r = Ray([0.0, 0, 2.0], [0.0, 0.0, -1.0]) - gen1 = csgintersection(pln1, pln2) - gen2 = csgunion(pln1, pln2) - - csg = gen1(identitytransform()) - intsct = evalcsg(csg, r) - - intvlpt1 = lower(intsct) - intvlpt2 = upper(intsct) - - @test isapprox(point(intvlpt1), SVector{3}(0.0, 0.0, 1.0)) && isapprox(point(intvlpt2), SVector{3}(0.0, 0.0, -1.0)) - - csg = gen2(identitytransform()) - intsct = evalcsg(csg, r) - @test (lower(intsct) isa RayOrigin) && (upper(intsct) isa Infinity) - - # test simple rays for intersection, union and difference operations - # INTERSECTION - intersection_obj = csgintersection(leaf(Cylinder(0.5, 3.0), OpticSim.rotationd(90.0, 0.0, 0.0)), (leaf(Sphere(1.0))))() - r = Ray([0.7, 0.0, 0.0], [-1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test isapprox(point(lower(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([5.0, 0.0, 0.0], [-1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test isapprox(point(lower(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([0.7, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test (int isa EmptyInterval) - - r = Ray([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([0.0, 2.0, 0.0], [0.0, -1.0, 0.0]) - int = OpticSim.evalcsg(intersection_obj, r) - @test isapprox(point(lower(int)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [0.0, -1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # UNION - union_obj = csgunion(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))(OpticSim.translation(1.0, 0.0, 0.0)) - r = Ray([1.0, 0.0, 0.0], [0.0, 0.0, 1.0]) - int = OpticSim.evalcsg(union_obj, r) - @test (lower(int) isa RayOrigin) && (upper(int) isa Infinity) - - r = Ray([1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(union_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([1.6, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(union_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([1.6, 0.0, 0.0], [-1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(union_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(union_obj, r) - @test isapprox(point(lower(int)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([-1.0, 0.0, 4.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(union_obj, r) - @test isapprox(point(lower(int)), [0.5, 0.0, 4.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [1.5, 0.0, 4.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - # DIFFERENCE - difference_obj = csgdifference(leaf(Cylinder(0.5, 3.0)), leaf(Sphere(1.0), OpticSim.translation(0.75, 0.0, 0.2)))() - r = Ray([0.25, 0.0, 0.0], [-1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test isapprox(point(lower(int)), [-0.2297958971132712, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([0.25, 0.0, 0.0], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test int isa EmptyInterval - - r = Ray([0.25, 0.0, 0.0], [0.0, 0.0, 1.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test isapprox(point(lower(int)), [0.25, 0.0, 1.0660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(int) isa Infinity) - - r = Ray([0.25, 0.0, 1.5], [0.0, 0.0, -1.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test (lower(int[1]) isa RayOrigin) && isapprox(point(upper(int[1])), [0.25, 0.0, 1.0660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(int[2])), [0.25, 0.0, -0.6660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(int[2]) isa Infinity) - - r = Ray([0.25, 0.0, 1.5], [1.0, 0.0, 0.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.5, 0.0, 1.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([0.25, 0.0, 1.5], [0.0, 0.0, 1.0]) - int = OpticSim.evalcsg(difference_obj, r) - @test (lower(int) isa RayOrigin) && (upper(int) isa Infinity) - - # DisjointUnion result on CSG - surf = TestData.verywavybeziersurface() - accelsurf = leaf(AcceleratedParametricSurface(surf, 20))() - - # five hits starting outside - r = Ray([0.9, 0.0, -0.3], [0.0, 1.0, 0.7]) - res = surfaceintersection(accelsurf, r) - a = isapprox(point(lower(res[1])), [0.9, 0.03172286522032046, -0.2777939943457758], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.1733979947040411, -0.17862140370717122], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) - @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c - - # five hits starting inside - r = Ray([0.9, 1.0, 0.4], [0.0, -1.0, -0.7]) - res = surfaceintersection(accelsurf, r) - a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(point(lower(res[3])), [0.9, 0.17339799470404108, -0.1786214037071712], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[3])), [0.9, 0.03172286522032046, -0.27779399434577573], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c - end # testset CSG - - @testset "ThinGrating" begin - angle_from_ray(raydirection) = 90 + atand(raydirection[3], raydirection[2]) - true_diff(order, λ, period, θi) = asind((order * λ / period + sind(θi))) - period = 3.0 - int = TestData.transmissivethingrating(period, 2) - for k in 1:50 - for θi in [-5.0, 0.0, 5.0, 10.0] - for λ in [0.35, 0.55, 1.0] - ray = OpticalRay(SVector(0.0, 0.0, 2.0), SVector(0.0, sind(θi), -cosd(θi)), 1.0, λ) - raydir, _, _ = OpticSim.processintersection(int, SVector(0.0, 0.0, 0.0), SVector(0.0, 0.0, 1.0), ray, 20.0, 1.0, true, true) - # order is random so just check that it is correct for one of them - @test any([isapprox(x, angle_from_ray(raydir), rtol = RTOLERANCE, atol = ATOLERANCE) for x in [true_diff(m, λ, period, θi) for m in -2:2]]) - end - end - end - end # testset ThinGrating - - # TODO Hologram intersection tests - - @testset "Chebyshev" begin - @test_throws AssertionError ChebyshevSurface(0.0, 1.0, [(1, 2, 1.0)]) - @test_throws AssertionError ChebyshevSurface(1.0, 0.0, [(1, 2, 1.0)]) - - z1 = TestData.chebyshevsurface2() - az1 = AcceleratedParametricSurface(z1, 20) - numsamples = 100 - - # test that an on axis ray doesn't miss - @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) - - samples = samplepoints(numsamples, -0.95, 0.95, -0.95, 0.95) - missedintersections = 0 - - for uv in samples - pt = point(z1, uv[1], uv[2]) - origin = [0.0, 0.0, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(az1, r) - if (allintersections isa EmptyInterval) - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - if lower(intsct) isa RayOrigin - missedintersections += 1 - else - # closest point should be on the surface, furthest should be on bounding prism - @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) - end - end - end - end - - if missedintersections > 0 - @warn "$missedintersections out of total $(length(samples)) chebychev suface intersections were missed" - end - - z1 = TestData.chebyshevsurface1() - az1 = AcceleratedParametricSurface(z1, 20) - - # hit from inside - r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.07870079995991423, 0.15740159991982847, -0.10649600020042879], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit from outside - r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.13584702907541094, 0.2716940581508219, -0.1792351453770547], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [1.0, 2.0, -4.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - # miss from inside - r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) - - # miss from outside - r = Ray([0.2, 3.0, -0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 3.0, 0.5], [0.0, 0.0, -1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 3.0, 2.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - - # two hits from outside - r = Ray([0.0, -1.5, 0.25], [-1.0, 0.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [-1.030882920068228, -1.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [-1.786071230727613, -1.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - - r = Ray([1.5, -0.8, 0.25], [-1.0, 0.0, 0.0]) - hit = surfaceintersection(az1, r) - a = isapprox(point(lower(hit[1])), [0.21526832427065165, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [-0.25523748548950076, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(hit[2])), [-1.9273057166986296, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-2.0, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b - - # hit cyl from outside - r = Ray([0.0, 3.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.0, 2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit cyl from inside - r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - end # testset Chebyshev - - @testset "GridSag" begin - z1 = TestData.gridsagsurfacebicubic() - az1 = AcceleratedParametricSurface(z1, 20) - - numsamples = 100 - - # test that an on axis ray doesn't miss - @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) - - samples1 = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) - missedintersections = 0 - - for uv in samples1 - pt = point(z1, uv[1], uv[2]) - origin = [0.0, 0.0, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(az1, r) - if (allintersections isa EmptyInterval) - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - if lower(intsct) isa RayOrigin - missedintersections += 1 - else - # closest point should be on the surface, furthest should be on bounding prism - @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) - end - end - end - end - - z2 = TestData.gridsagsurfacebicubiccheby() - az2 = AcceleratedParametricSurface(z2, 20) - - samples2 = samplepoints(numsamples, -0.95, 0.95, -0.95, 0.95) - for uv in samples2 - pt = point(z2, uv[1], uv[2]) - origin = [0.0, 0.0, 5.0] - r = Ray(origin, pt .- origin) - allintersections = surfaceintersection(az2, r) - if (allintersections isa EmptyInterval) - # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. - # Have to use patch subdivision and convex hull polyhedron to do this. - missedintersections += 1 - else - if isa(allintersections, Interval) - allintersections = (allintersections,) - end - - for intsct in allintersections - if lower(intsct) isa RayOrigin - missedintersections += 1 - else - # closest point should be on the surface, furthest should be on bounding prism - @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) - end - end - end - end - - if missedintersections > 0 - @warn "$missedintersections out of total $(length(samples1) + length(samples2)) gridsag suface intersections were missed" - end - - # hit from inside - r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.13038780645120746, 0.26077561290241486, 0.15193903225603717], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit from outside - r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.046677740288643785, 0.0933554805772876, 0.26661129855678095], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) - - # miss from inside - r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) - - # miss from outside - r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) - @test surfaceintersection(az1, r) isa EmptyInterval - - # two hits from outside - r = Ray([2.0, 0.0, 0.25], [-1.0, 0.0, 0.0]) - hit = surfaceintersection(az1, r) - a = isapprox(point(lower(hit[1])), [1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.394132887629247, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(point(lower(hit[2])), [0.30184444700168467, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.6437764440680096, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) - @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b - - # hit cyl from outside - r = Ray([0.0, 2.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test isapprox(point(lower(intvl)), [0.0, 1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - - # hit cyl from inside - r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) - intvl = surfaceintersection(az1, r) - @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) - end # testset GridSag -end # testset intersection - -@otestset "Lenses" begin - test_n = 5000 - - @testset "Refraction" begin - Random.seed!(SEED) - ηᵢ = 1.4 - ηₜ = 1.2 - for i in 1:test_n - nₛ = randunit() - rᵢ = randunit() - rₜ = refractedray(ηᵢ, ηₜ, nₛ, rᵢ) - if !(rₜ === nothing) - sinθₜ = norm(cross(-nₛ, rₜ)) - sinθᵢ = norm(cross(nₛ, rᵢ)) - a = isapprox(ηₜ * sinθₜ, ηᵢ * sinθᵢ, rtol = RTOLERANCE, atol = ATOLERANCE) #verify snell's law for the the transmitted and incident ray - b = isapprox(1.0, norm(rₜ), rtol = RTOLERANCE, atol = ATOLERANCE) #verify transmitted ray is unit - perp = normalize(cross(nₛ, rᵢ)) - c = isapprox(0.0, rₜ ⋅ perp, rtol = RTOLERANCE, atol = ATOLERANCE) #verify incident and transmitted ray are in the same plane - @test a && b && c - end - end - end # testset refraction - - # snell - @testset "Snell" begin - Random.seed!(SEED) - for i in 1:test_n - r = randunit() - nₛ = randunit() - n1 = 1.0 - n2 = 1.4 - sθ1, sθ2 = snell(nₛ, r, n1, n2) - sθ3, sθ4 = snell(nₛ, r, n2, n1) - @test isapprox(sθ1 * n1, sθ2 * n2, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(sθ3 * n2, sθ4 * n1, rtol = RTOLERANCE, atol = ATOLERANCE) - end - end # testset snell - - @testset "Reflection" begin - Random.seed!(SEED) - # reflection - for i in 1:test_n - r = randunit() - nₛ = randunit() - if abs(r ⋅ nₛ) < 0.9999 # if r and n are exactly aligned then their sum will be zero, not a vector pointing in the direction of the normal - reflected = reflectedray(nₛ, r) - #ensure reflected and refracted rays are in the same plane - a = isapprox(norm(reflected ⋅ cross(r, nₛ)), 0.0, rtol = RTOLERANCE, atol = ATOLERANCE) - b = isapprox(norm(reflected), 1.0, rtol = RTOLERANCE, atol = ATOLERANCE) - c = isapprox(0.0, reflected ⋅ nₛ + r ⋅ nₛ, rtol = RTOLERANCE, atol = ATOLERANCE) - #ensure sum of reflected and origin ray are in the direction of the normal - d = isapprox(0.0, norm(nₛ - sign(reflected ⋅ nₛ) * (normalize(reflected - r))), rtol = RTOLERANCE, atol = ATOLERANCE) - @test a & b & c & d - end - end - end # testset reflection - - @testset "Paraxial" begin - # check that normally incident rays are focussed across the lens - l = ParaxialLensEllipse(100.0, 10.0, 10.0, [1.0, 1.0, 0.0], [3.0, 3.0, 3.0]) - r = OpticalRay([2.0, 2.0, 3.0], [1.0, 1.0, 0.0], 1.0, 0.55) - intsct = halfspaceintersection(surfaceintersection(l, r)) - ref, _, _ = OpticSim.processintersection(OpticSim.interface(intsct), OpticSim.point(intsct), OpticSim.normal(intsct), r, 20.0, 1.0, true, true) - @test isapprox(ref, normalize([1.0, 1.0, 0.0]), rtol = RTOLERANCE, atol = ATOLERANCE) - l = ParaxialLensRect(100.0, 10.0, 10.0, [1.0, 1.0, 0.0], [3.0, 3.0, 3.0]) - r = OpticalRay([2.3, 2.4, 3.1], [1.0, 1.0, 0.0], 1.0, 0.55) - intsct = halfspaceintersection(surfaceintersection(l, r)) - fp = [3.0, 3.0, 3.0] + 100 * normalize([1.0, 1.0, 0.0]) - ref, _, _ = OpticSim.processintersection(OpticSim.interface(intsct), OpticSim.point(intsct), OpticSim.normal(intsct), r, 20.0, 1.0, true, true) - @test isapprox(ref, normalize(fp - OpticSim.point(intsct)), rtol = RTOLERANCE, atol = ATOLERANCE) - end # testset Paraxial -end # testset lenses - -@otestset "Emitters" begin - # TODO!! once emitters are finalised -end # testset Emitters - -@otestset "Comparison" begin - @testset "Refraction" begin - λ = 0.550 - r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) - r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) - - # Test a number of rays under standard environmental conditions - a = TestData.planoplano() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [2.0, 2.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [5.0, 5.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 0.7192321011619232, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.234903851062, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [0.7200535362928893, -6.141047252697188, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.2448952769065, rtol = COMP_TOLERANCE) - - a = TestData.concaveplano() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [3.633723967570758, 3.633723967570758, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.0772722321026, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [9.153654757938119, 9.153654757938119, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.5695599263722, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, -3.401152468994745, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.1609946836496, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [-3.457199906282556, -10.32836827735406, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.5393056652617, rtol = COMP_TOLERANCE) - - a = TestData.doubleconcave() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [5.235579681684886, 5.235579681684886, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.2624909340242, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [13.42351905137007, 13.42351905137007, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.8131310483928, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, -6.904467939352202, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.3286918571586, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [-7.089764102262856, -14.61837033417989, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.4286280772859, rtol = COMP_TOLERANCE) - - a = TestData.convexplano() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [0.8867891519368289, 0.8867891519368289, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9698941263224, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [2.212067233969831, 2.212067233969831, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.8895474037682, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 3.489759589087602, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.4310036252394, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [3.491858246934857, -3.331802834115089, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.344370622227, rtol = COMP_TOLERANCE) - - a = TestData.doubleconvex() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-0.06191521590711035, -0.06191521590711035, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.987421926598, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-0.2491105067897657, -0.2491105067897657, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.0110871422915, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 5.639876913179362, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.6758349889372, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [5.705452331562673, -0.7679713587894854, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.6175821043825, rtol = COMP_TOLERANCE) - end # testset Refraction - - @testset "Temperature/Pressure" begin - λ = 0.550 - r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) - r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) - - # Test a component whose material does have a ΔT component - a = TestData.doubleconvex(temperature = 40 * u"°C") - @test isapprox(point(intersection(trace(a, r1, test = true))), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(point(intersection(trace(a, r2, test = true))), [-0.06214282132053373, -0.06214282132053373, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(point(intersection(trace(a, r3, test = true))), [-0.2497005807710933, -0.2497005807710933, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(point(intersection(trace(a, r4, test = true))), [0.0, 5.640416741927821, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(point(intersection(trace(a, r5, test = true))), [5.706006107804734, -0.7673622008537766, -67.8], rtol = COMP_TOLERANCE) - - # note that this material doesn't have a ΔT component - λ2 = 0.533 - r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) - r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) - r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) - r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ2) - r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ2) - end # testset Temperature/Pressure - - @testset "Reflection" begin - λ = 0.550 - r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) - r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) - - a = TestData.planoconcaverefl() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, 47.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 79.1704477524159, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-6.19723306038804, -6.19723306038804, 47.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 80.0979229417755, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-17.7546255175372, -17.7546255175372, 47.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 86.2063094599075, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 19.5349836031196, 47.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 83.5364646376395, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [21.2477706541112, 17.3463704045055, 47.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 87.4033932087711, rtol = COMP_TOLERANCE) - end # testset Reflection - - @testset "Complex Lenses" begin - λ = 0.550 - r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) - r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) - - a = TestData.conicsystemZ() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-1.80279270185495, -1.80279270185495, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.1000975813922, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-2.8229241607807, -2.8229241607807, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.282094499601, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 9.01421545841289, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.2211078071893, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [8.13946939328266, 2.23006981338816, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.1025133542546, rtol = COMP_TOLERANCE) - - a = TestData.asphericsystem() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [0.0735282671574837, 0.0735282671574837, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.0161411455851, rtol = COMP_TOLERANCE) - track = Vector{OpticSim.LensTrace{Float64,3}}(undef, 0) - res = trace(a, r3, test = true, trackrays = track) - @test (res === nothing) # TIR - @test isapprox(point(track[end]), [-5.0, -5.0, 0.0], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(track[end]), 46.5556716286238, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 11.9748998399885, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 76.0760286320348, rtol = COMP_TOLERANCE) - track = Vector{OpticSim.LensTrace{Float64,3}}(undef, 0) - res = trace(a, r5, test = true, trackrays = track) - @test (res === nothing) # TIR - @test isapprox(point(track[end]), [5.49905367197174, 5.66882664623822, 0.0], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(track[end]), 47.6825931025333, rtol = COMP_TOLERANCE) - - a = TestData.zernikesystem() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, -8.9787010034042, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.5977029819277, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-1.58696749235066, -9.71313213852721, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.7603266537023, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [0.790348081563859, -0.762155123619682, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.1933359669848, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true, trackrays = track) - @test isapprox(point(res), [0.0, 10.0037899692172, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 76.8041236301678, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [35.5152289731456, 28.3819941055557, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 94.3059947823954, rtol = COMP_TOLERANCE) - - a = TestData.conicsystemQ() - res = trace(a, r1, test = true) - @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-1.80279270185495, -1.80279270185495, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.1000975813922, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-2.8229241607807, -2.8229241607807, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.282094499601, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [0.0, 9.01421545841289, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.2211078071893, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [8.13946939328266, 2.23006981338816, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.1025133542546, rtol = COMP_TOLERANCE) - - # No NSC qtype so have less precise SC values and no angled rays - a = TestData.qtypesystem() - res = trace(a, r1, test = true) - @test isapprox(point(res), [-4.3551074306, -0.318112811010, -67.8], rtol = 1e-12) - # TODO these are the values that I get for comparison - I'm pretty certain that they are just wrong... - # res = trace(a, r2, test = true) - # @test isapprox(point(res), [-0.091773202667, 3.9362695851, -67.8], rtol = 1e-12) - # res = trace(a, r3, test = true) - # @test isapprox(point(res), [-7.0993098547, -2.6848726242, -67.8], rtol = 1e-12) - - # No NSC chebyshev so have less precise SC values and no angled rays - a = TestData.chebyshevsystem() - res = trace(a, r1, test = true) - @test isapprox(point(res), [-1.07963031980, 0.53981515992, -67.8], rtol = 1e-12) - res = trace(a, r2, test = true) - @test isapprox(point(res), [0.00851939011, 1.25870229260, -67.8], rtol = 1e-12) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-1.20441411240, -0.07554605390, -67.8], rtol = 1e-12) - - a = TestData.gridsagsystem() - res = trace(a, r1, test = true) - @test isapprox(point(res), [21.0407756733608, 21.724638830759, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 82.0777022031378, rtol = COMP_TOLERANCE) - res = trace(a, r2, test = true) - @test isapprox(point(res), [-0.489183765274452, 0.405160352533666, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 75.536902213118, rtol = COMP_TOLERANCE) - res = trace(a, r3, test = true) - @test isapprox(point(res), [-12.0770793886528, -8.7705340321259, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 77.5651917266687, rtol = COMP_TOLERANCE) - res = trace(a, r4, test = true) - @test isapprox(point(res), [-1.20535362019229, 6.07973526939659, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.7349148204077, rtol = COMP_TOLERANCE) - res = trace(a, r5, test = true) - @test isapprox(point(res), [6.5112407340601, -0.22055440245024, -67.8], rtol = COMP_TOLERANCE) - @test isapprox(pathlength(res), 74.6955888563913, rtol = COMP_TOLERANCE) - end #testset complex lenses - - # @testset "Power" begin - # λ = 0.550 - # r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - # r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - # r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) - # a = TestData.doubleconvex() - # res = trace(a, r1, test = true) - # @test isapprox(power(res), 0.915508396) - # res = trace(a, r2, test = true) - # @test isapprox(power(res), 0.915526077) - # res = trace(a, r3, test = true) - # @test isapprox(power(res), 0.915577586) - # end -end # testset Comparison - -@otestset "Visualization" begin - # test that this all at least runs - surf1 = AcceleratedParametricSurface(TestData.beziersurface(), 15) - surf2 = AcceleratedParametricSurface(TestData.upsidedownbeziersurface(), 15) - m = csgintersection(leaf(surf1, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf1, RigidBodyTransform{Float64}(0.0, Float64(π), 0.0, 0.5, -0.5, 0.0))))() - @test_nowarn Vis.draw(m) - m = csgintersection(leaf(surf1, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf2, translation(-0.5, -0.5, 0.0))))() - @test_nowarn Vis.draw(m) - m = csgunion(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() - @test_nowarn Vis.draw(m) - m = csgintersection(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() - @test_nowarn Vis.draw(m) - m = csgdifference(leaf(Cylinder(0.5, 3.0)), leaf(Sphere(1.0)))() - @test_nowarn Vis.draw(m) -end # testset Visualization - -@otestset "Allocations" begin - # ensure that there are 0 allocations for all the benchmarks - for b in Benchmarks.all_benchmarks() - @test Benchmarks.runbenchmark(b, samples = 1, evals = 1).allocs == 0 - end -end # testset Allocations +include("testsets/JuliaLang.jl") +# include("testsets/BVH.jl") +include("testsets/TestData.jl") +# include("testsets/Examples.jl") # slow +include("testsets/General.jl") +include("testsets/SurfaceDefs.jl") +include("testsets/Intersection.jl") +include("testsets/Lenses.jl") +include("testsets/Emitters.jl") # TODO +include("testsets/Comparison.jl") +include("testsets/Visualization.jl") + +include("Benchmarks.jl") +include("testsets/Allocations.jl") diff --git a/test/testsets/Allocations.jl b/test/testsets/Allocations.jl new file mode 100644 index 000000000..2cdb2b671 --- /dev/null +++ b/test/testsets/Allocations.jl @@ -0,0 +1,6 @@ +@otestset "Allocations" begin + # ensure that there are 0 allocations for all the benchmarks + for b in Benchmarks.all_benchmarks() + @test Benchmarks.runbenchmark(b, samples = 1, evals = 1).allocs == 0 + end +end # testset Allocations diff --git a/test/testsets/BVH.jl b/test/testsets/BVH.jl new file mode 100644 index 000000000..f063df152 --- /dev/null +++ b/test/testsets/BVH.jl @@ -0,0 +1,35 @@ +@otestset "BVH" begin + @testset "partition!" begin + split = 0.5 + + for i in 1:100000 + a = rand(5) + b = copy(a) + badresult::Bool = false + + lower, upper = partition!(a, (x) -> x, split) + + if lower !== nothing + for i in lower + if i >= split + badresult = true + break + end + end + end + + if upper !== nothing + for i in upper + if i <= split + badresult = true + end + end + end + + if badresult + throw(ErrorException("array didn't partition: $(b)")) + end + + end + end +end # testset BVH diff --git a/test/testsets/Comparison.jl b/test/testsets/Comparison.jl new file mode 100644 index 000000000..7ae89eebb --- /dev/null +++ b/test/testsets/Comparison.jl @@ -0,0 +1,278 @@ +@otestset "Comparison" begin + @testset "Refraction" begin + λ = 0.550 + r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) + r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) + + # Test a number of rays under standard environmental conditions + a = TestData.planoplano() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [2.0, 2.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [5.0, 5.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 0.7192321011619232, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.234903851062, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [0.7200535362928893, -6.141047252697188, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.2448952769065, rtol = COMP_TOLERANCE) + + a = TestData.concaveplano() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [3.633723967570758, 3.633723967570758, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.0772722321026, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [9.153654757938119, 9.153654757938119, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.5695599263722, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, -3.401152468994745, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.1609946836496, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [-3.457199906282556, -10.32836827735406, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.5393056652617, rtol = COMP_TOLERANCE) + + a = TestData.doubleconcave() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [5.235579681684886, 5.235579681684886, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.2624909340242, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [13.42351905137007, 13.42351905137007, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.8131310483928, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, -6.904467939352202, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.3286918571586, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [-7.089764102262856, -14.61837033417989, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.4286280772859, rtol = COMP_TOLERANCE) + + a = TestData.convexplano() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [0.8867891519368289, 0.8867891519368289, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9698941263224, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [2.212067233969831, 2.212067233969831, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.8895474037682, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 3.489759589087602, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.4310036252394, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [3.491858246934857, -3.331802834115089, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.344370622227, rtol = COMP_TOLERANCE) + + a = TestData.doubleconvex() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-0.06191521590711035, -0.06191521590711035, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.987421926598, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-0.2491105067897657, -0.2491105067897657, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.0110871422915, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 5.639876913179362, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.6758349889372, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [5.705452331562673, -0.7679713587894854, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.6175821043825, rtol = COMP_TOLERANCE) + end # testset Refraction + + @testset "Temperature/Pressure" begin + λ = 0.550 + r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) + r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) + + # Test a component whose material does have a ΔT component + a = TestData.doubleconvex(temperature = 40 * u"°C") + @test isapprox(point(intersection(trace(a, r1, test = true))), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(point(intersection(trace(a, r2, test = true))), [-0.06214282132053373, -0.06214282132053373, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(point(intersection(trace(a, r3, test = true))), [-0.2497005807710933, -0.2497005807710933, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(point(intersection(trace(a, r4, test = true))), [0.0, 5.640416741927821, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(point(intersection(trace(a, r5, test = true))), [5.706006107804734, -0.7673622008537766, -67.8], rtol = COMP_TOLERANCE) + + # note that this material doesn't have a ΔT component + λ2 = 0.533 + r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) + r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) + r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ2) + r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ2) + r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ2) + end # testset Temperature/Pressure + + @testset "Reflection" begin + λ = 0.550 + r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) + r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) + + a = TestData.planoconcaverefl() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, 47.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 79.1704477524159, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-6.19723306038804, -6.19723306038804, 47.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 80.0979229417755, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-17.7546255175372, -17.7546255175372, 47.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 86.2063094599075, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 19.5349836031196, 47.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 83.5364646376395, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [21.2477706541112, 17.3463704045055, 47.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 87.4033932087711, rtol = COMP_TOLERANCE) + end # testset Reflection + + @testset "Complex Lenses" begin + λ = 0.550 + r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + r4 = OpticalRay([0.0, -5.0, 1.0], [0.0, 0.08715574274765818, -0.9961946980917454], 1.0, λ) + r5 = OpticalRay([-5.0, -5.0, 1.0], [0.08715574274765818, -0.01738599476176408, -0.9960429728140486], 1.0, λ) + + a = TestData.conicsystemZ() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-1.80279270185495, -1.80279270185495, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.1000975813922, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-2.8229241607807, -2.8229241607807, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.282094499601, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 9.01421545841289, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.2211078071893, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [8.13946939328266, 2.23006981338816, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.1025133542546, rtol = COMP_TOLERANCE) + + a = TestData.asphericsystem() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [0.0735282671574837, 0.0735282671574837, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.0161411455851, rtol = COMP_TOLERANCE) + track = Vector{OpticSim.LensTrace{Float64,3}}(undef, 0) + res = trace(a, r3, test = true, trackrays = track) + @test (res === nothing) # TIR + @test isapprox(point(track[end]), [-5.0, -5.0, 0.0], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(track[end]), 46.5556716286238, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 11.9748998399885, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 76.0760286320348, rtol = COMP_TOLERANCE) + track = Vector{OpticSim.LensTrace{Float64,3}}(undef, 0) + res = trace(a, r5, test = true, trackrays = track) + @test (res === nothing) # TIR + @test isapprox(point(track[end]), [5.49905367197174, 5.66882664623822, 0.0], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(track[end]), 47.6825931025333, rtol = COMP_TOLERANCE) + + a = TestData.zernikesystem() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, -8.9787010034042, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.5977029819277, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-1.58696749235066, -9.71313213852721, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.7603266537023, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [0.790348081563859, -0.762155123619682, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.1933359669848, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true, trackrays = track) + @test isapprox(point(res), [0.0, 10.0037899692172, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 76.8041236301678, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [35.5152289731456, 28.3819941055557, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 94.3059947823954, rtol = COMP_TOLERANCE) + + a = TestData.conicsystemQ() + res = trace(a, r1, test = true) + @test isapprox(point(res), [0.0, 0.0, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 73.9852238762079, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-1.80279270185495, -1.80279270185495, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.1000975813922, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-2.8229241607807, -2.8229241607807, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.282094499601, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [0.0, 9.01421545841289, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.2211078071893, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [8.13946939328266, 2.23006981338816, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.1025133542546, rtol = COMP_TOLERANCE) + + # No NSC qtype so have less precise SC values and no angled rays + a = TestData.qtypesystem() + res = trace(a, r1, test = true) + @test isapprox(point(res), [-4.3551074306, -0.318112811010, -67.8], rtol = 1e-12) + # TODO these are the values that I get for comparison - I'm pretty certain that they are just wrong... + # res = trace(a, r2, test = true) + # @test isapprox(point(res), [-0.091773202667, 3.9362695851, -67.8], rtol = 1e-12) + # res = trace(a, r3, test = true) + # @test isapprox(point(res), [-7.0993098547, -2.6848726242, -67.8], rtol = 1e-12) + + # No NSC chebyshev so have less precise SC values and no angled rays + a = TestData.chebyshevsystem() + res = trace(a, r1, test = true) + @test isapprox(point(res), [-1.07963031980, 0.53981515992, -67.8], rtol = 1e-12) + res = trace(a, r2, test = true) + @test isapprox(point(res), [0.00851939011, 1.25870229260, -67.8], rtol = 1e-12) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-1.20441411240, -0.07554605390, -67.8], rtol = 1e-12) + + a = TestData.gridsagsystem() + res = trace(a, r1, test = true) + @test isapprox(point(res), [21.0407756733608, 21.724638830759, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 82.0777022031378, rtol = COMP_TOLERANCE) + res = trace(a, r2, test = true) + @test isapprox(point(res), [-0.489183765274452, 0.405160352533666, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 75.536902213118, rtol = COMP_TOLERANCE) + res = trace(a, r3, test = true) + @test isapprox(point(res), [-12.0770793886528, -8.7705340321259, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 77.5651917266687, rtol = COMP_TOLERANCE) + res = trace(a, r4, test = true) + @test isapprox(point(res), [-1.20535362019229, 6.07973526939659, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.7349148204077, rtol = COMP_TOLERANCE) + res = trace(a, r5, test = true) + @test isapprox(point(res), [6.5112407340601, -0.22055440245024, -67.8], rtol = COMP_TOLERANCE) + @test isapprox(pathlength(res), 74.6955888563913, rtol = COMP_TOLERANCE) + end #testset complex lenses + + # @testset "Power" begin + # λ = 0.550 + # r1 = OpticalRay([0.0, 0.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + # r2 = OpticalRay([2.0, 2.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + # r3 = OpticalRay([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], 1.0, λ) + # a = TestData.doubleconvex() + # res = trace(a, r1, test = true) + # @test isapprox(power(res), 0.915508396) + # res = trace(a, r2, test = true) + # @test isapprox(power(res), 0.915526077) + # res = trace(a, r3, test = true) + # @test isapprox(power(res), 0.915577586) + # end +end # testset Comparison diff --git a/test/testsets/Emitters.jl b/test/testsets/Emitters.jl new file mode 100644 index 000000000..312a2c21e --- /dev/null +++ b/test/testsets/Emitters.jl @@ -0,0 +1,3 @@ +@otestset "Emitters" begin + # TODO!! once emitters are finalised +end # testset Emitters diff --git a/test/testsets/Examples.jl b/test/testsets/Examples.jl new file mode 100644 index 000000000..ac49c0669 --- /dev/null +++ b/test/testsets/Examples.jl @@ -0,0 +1,4 @@ +# TODO may want to test this but it's very slow on azure +@otestset "Examples" begin + @test_all_no_arg_functions Examples +end # testset Examples diff --git a/test/testsets/General.jl b/test/testsets/General.jl new file mode 100644 index 000000000..d14a8fa4e --- /dev/null +++ b/test/testsets/General.jl @@ -0,0 +1,239 @@ +@otestset "General" begin + @testset "QuadraticRoots" begin + Random.seed!(SEED) + similarroots(r1, r2, x1, x2) = isapprox([r1, r2], [x1, x2], rtol = 1e-9) || isapprox([r2, r1], [x1, x2], rtol = 1e-9) + + for i in 1:10000 + r1, r2, scale = rand(3) .- 0.5 + a, b, c = scale .* (1, r1 + r2, r1 * r2) + x1, x2 = quadraticroots(a, b, c) + @test similarroots(-r1, -r2, x1, x2) + end + + a, b, c = 1, 0, -1 + x1, x2 = quadraticroots(a, b, c) + @test similarroots(1, -1, x1, x2) + + a, b, c = 1, 2, 1 # (x+1)(x+1)= x^2 + 2x + 1, double root at one + x1, x2 = quadraticroots(a, b, c) + @test similarroots(-1, -1, x1, x2) + end # testset QuadraticRoots + + @testset "RigidBodyTransform" begin + Random.seed!(SEED) + @test isapprox(rotmatd(180, 0, 0), [1.0 0.0 0.0; 0.0 -1.0 0.0; 0.0 0.0 -1.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(rotmatd(0.0, 180.0, 0.0), [-1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 -1.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(rotmatd(0, 0, 180), [-1.0 0.0 0.0; 0.0 -1.0 0.0; 0.0 0.0 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(rotmatd(0, 90, 0), [0.0 0.0 1.0; 0.0 1.0 0.0; -1.0 0.0 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(rotmatd(45, -45, 45), [0.5 -0.8535533905932737 0.1464466094067261; 0.5 0.14644660940672644 -0.8535533905932737; 0.7071067811865475 0.5 0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + x, y, z = rand(3) + @test isapprox(rotmatd(x * 180 / π, y * 180 / π, z * 180 / π), rotmat(x, y, z), rtol = RTOLERANCE, atol = ATOLERANCE) + + @test isapprox(RigidBodyTransform(rotmatd(0, 90, 0), SVector(0.0, 0.0, 1.0)) * SVector(1.0, 0.0, 0.0), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + ta = RigidBodyTransform(rotmatd(0, 90, 0), SVector(0.0, 0.0, 1.0)) + tb = RigidBodyTransform(rotmatd(90, 0, 0), SVector(1.0, 0.0, 0.0)) + @test isapprox(collect(ta * tb), collect(RigidBodyTransform(rotmatd(90, 90, 0), SVector(0.0, 0.0, 0.0))), rtol = RTOLERANCE, atol = ATOLERANCE) + + @test isapprox(collect(ta * inv(ta)), collect(identitytransform()), rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(collect(tb * inv(tb)), collect(identitytransform()), rtol = RTOLERANCE, atol = ATOLERANCE) + @test isapprox(collect(inv(ta)), collect(RigidBodyTransform(rotmatd(0, -90, 0), SVector(1.0, 0.0, 0.0))), rtol = RTOLERANCE, atol = ATOLERANCE) + end # testset RigidBodyTransform + + @testset "Interval" begin + pt1 = Intersection(0.3, [4.0, 5.0, 6.0], normalize(rand(3)), 0.4, 0.5, NullInterface()) + pt2 = Intersection(0.5, [1.0, 2.0, 3.0], normalize(rand(3)), 0.2, 0.3, NullInterface()) + @test pt1 < pt2 && pt1 <= pt2 && pt2 > pt1 && pt2 >= pt1 && pt1 != pt2 && pt1 <= pt1 + intvl2 = positivehalfspace(pt1) + @test α(halfspaceintersection(intvl2)) == α(pt1) + + ### interval intersection + intersectionat = TestData.intersectionat + ## interval/interval + # one in another + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.0), intersectionat(0.3)) + @test intervalintersection(a, b) == intervalintersection(b, a) == a + # overlap + a = Interval(intersectionat(0.1), intersectionat(0.3)) + b = Interval(intersectionat(0.2), intersectionat(0.4)) + @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.2), intersectionat(0.3)) + # no overlap + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.3), intersectionat(0.4)) + @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval + # start == end + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.2), intersectionat(0.3)) + @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval + ## interval/du + # encompass one + a = Interval(intersectionat(0.0), intersectionat(0.3)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalintersection(a, b) == intervalintersection(b, a) == b[1] + # encompass two + a = Interval(intersectionat(0.0), intersectionat(0.8)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalintersection(a, b) == intervalintersection(b, a) == b + # overlap one + a = Interval(intersectionat(0.0), intersectionat(0.2)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.1), intersectionat(0.2)) + # overlap two + a = Interval(intersectionat(0.2), intersectionat(0.5)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) + res = intervalintersection(a, b) + @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.2), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.5)) + # no overlap + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = DisjointUnion(Interval(intersectionat(0.3), intersectionat(0.4)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval + ## du/du + # no overlap + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.1)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) + @test intervalintersection(a, b) == intervalintersection(b, a) isa EmptyInterval + # one in a overlaps one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) + @test intervalintersection(a, b) == intervalintersection(b, a) == Interval(intersectionat(0.1), intersectionat(0.2)) + # one in a encompasses one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.6), intersectionat(0.7))) + @test intervalintersection(a, b) == intervalintersection(b, a) == b[1] + # one in a overlaps two in b + a = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) + res = intervalintersection(a, b) + @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.2), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.5)) + # one in a encompasses two in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.3), intersectionat(0.4))) + @test intervalintersection(a, b) == intervalintersection(b, a) == b + # each in a overlap one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.6))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.7))) + res = intervalintersection(a, b) + @test res == intervalintersection(b, a) && res[1] == Interval(intersectionat(0.1), intersectionat(0.2)) && res[2] == Interval(intersectionat(0.5), intersectionat(0.6)) + # each in a encompass one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalintersection(a, b) == intervalintersection(b, a) == b + ## empties + intvl = positivehalfspace(pt1) + du = intervalcomplement(Interval(pt1, pt2)) + @test intervalintersection(EmptyInterval(), EmptyInterval()) isa EmptyInterval + @test intervalintersection(EmptyInterval(), intvl) isa EmptyInterval + @test intervalintersection(intvl, EmptyInterval()) isa EmptyInterval + @test intervalintersection(EmptyInterval(), du) isa EmptyInterval + @test intervalintersection(du, EmptyInterval()) isa EmptyInterval + + ### interval union + ## interval/interval + # one in another + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.0), intersectionat(0.3)) + @test intervalunion(a, b) == intervalunion(b, a) == b + # overlap + a = Interval(intersectionat(0.1), intersectionat(0.3)) + b = Interval(intersectionat(0.2), intersectionat(0.4)) + @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.4)) + # no overlap + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.3), intersectionat(0.4)) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == a && res[2] == b + # start == end + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = Interval(intersectionat(0.2), intersectionat(0.3)) + @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.3)) + ## interval/du + # encompass one + a = Interval(intersectionat(0.0), intersectionat(0.3)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == a && res[2] == b[2] + # encompass two + a = Interval(intersectionat(0.0), intersectionat(0.8)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalunion(a, b) == intervalunion(b, a) == a + # overlap one + a = Interval(intersectionat(0.0), intersectionat(0.2)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.6))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == b[2] + # overlap two + a = Interval(intersectionat(0.2), intersectionat(0.5)) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) + @test intervalunion(a, b) == intervalunion(b, a) == Interval(intersectionat(0.1), intersectionat(0.6)) + # no overlap + a = Interval(intersectionat(0.1), intersectionat(0.2)) + b = DisjointUnion(Interval(intersectionat(0.3), intersectionat(0.4)), Interval(intersectionat(0.5), intersectionat(0.6))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == a && res[2] == b[1] && res[3] == b[2] + ## du/du + # no overlap + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.1)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == a[1] && res[2] == b[1] && res[3] == a[2] && res[4] == b[2] + # one in a overlaps one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.6), intersectionat(0.7))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == a[2] && res[3] == b[2] + # one in a encompasses one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.5))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.6), intersectionat(0.7))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == a[1] && res[2] == a[2] && res[3] == b[2] + # one in a overlaps two in b + a = DisjointUnion(Interval(intersectionat(0.2), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.6))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.1), intersectionat(0.6)) && res[2] == a[2] + # one in a encompasses two in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.5)), Interval(intersectionat(0.7), intersectionat(0.8))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.3), intersectionat(0.4))) + @test intervalunion(a, b) == intervalunion(b, a) == a + # each in a overlap one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.6))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.5), intersectionat(0.7))) + res = intervalunion(a, b) + @test res == intervalunion(b, a) && res[1] == Interval(intersectionat(0.0), intersectionat(0.3)) && res[2] == Interval(intersectionat(0.4), intersectionat(0.7)) + # each in a encompass one in b + a = DisjointUnion(Interval(intersectionat(0.0), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) + b = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.2)), Interval(intersectionat(0.5), intersectionat(0.6))) + @test intervalunion(a, b) == intervalunion(b, a) == a + ## empties + @test intervalunion(EmptyInterval(), EmptyInterval()) isa EmptyInterval + @test intervalunion(EmptyInterval(), intvl) == intvl + @test intervalunion(intvl, EmptyInterval()) == intvl + @test intervalunion(EmptyInterval(), du) == du + @test intervalunion(du, EmptyInterval()) == du + + # interval complement + int = intervalcomplement(EmptyInterval()) + @test lower(int) isa RayOrigin && upper(int) isa Infinity + + int = intervalcomplement(rayorigininterval(Infinity())) + @test int isa EmptyInterval + + int = intervalcomplement(rayorigininterval(pt1)) + @test lower(int) == pt1 && upper(int) isa Infinity + + int = intervalcomplement(positivehalfspace(pt1)) + @test lower(int) isa RayOrigin && upper(int) == pt1 + + int = intervalcomplement(Interval(pt1, pt2)) + @test lower(int[1]) isa RayOrigin && upper(int[1]) == pt1 && lower(int[2]) == pt2 && upper(int[2]) isa Infinity + + a = DisjointUnion(Interval(intersectionat(0.1), intersectionat(0.3)), Interval(intersectionat(0.4), intersectionat(0.7))) + res = intervalcomplement(a) + @test res[1] == rayorigininterval(intersectionat(0.1)) && res[2] == Interval(intersectionat(0.3), intersectionat(0.4)) && res[3] == positivehalfspace(intersectionat(0.7)) + + a = DisjointUnion(rayorigininterval(intersectionat(0.2)), Interval(intersectionat(0.4), intersectionat(0.7))) + res = intervalcomplement(a) + @test res[1] == Interval(intersectionat(0.2), intersectionat(0.4)) && res[2] == positivehalfspace(intersectionat(0.7)) + end # testset interval +end # testset General diff --git a/test/testsets/Intersection.jl b/test/testsets/Intersection.jl new file mode 100644 index 000000000..e3811bddd --- /dev/null +++ b/test/testsets/Intersection.jl @@ -0,0 +1,1215 @@ +@otestset "Intersection" begin + function samplepoints(numsamples, lowu, highu, lowv, highv) + samples = Array{Tuple{Float64,Float64},1}(undef, 0) + + for i in 0:numsamples + u = lowu * i / numsamples + (1 - i / numsamples) * highu + for j in 0:numsamples + v = lowv * j / numsamples + (1 - j / numsamples) * highv + push!(samples, (u, v)) + end + end + return samples + end + + @testset "Cylinder" begin + # Random.seed!(SEED) + + # # random point intersections + # ur, vr = uvrange(Cylinder) + # for i in 1:10000 + # cyl = Cylinder(rand() * 3.0, rand() * 50.0) + # u, v = rand() * (ur[2] - ur[1]) + ur[1], rand() * (vr[2] - vr[1]) + vr[1] + # pointon = point(cyl, u, v) + # origin = SVector(4.0, 4.0, 4.0) + # r = Ray(origin, pointon .- origin) + # halfspace = surfaceintersection(cyl, r) + # @test halfspace !== nothing && !((lower(halfspace) isa RayOrigin) || (upper(halfspace) isa Infinity)) + # @test isapprox(pointon, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) || isapprox(pointon, point(upper(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) + # end + + # on xy + cyl = Cylinder(0.5) + r = Ray([1.0, 1.0, 0.0], [-1.0, -1.0, 0.0]) + intscts = surfaceintersection(cyl, r) + xy = sqrt(2) / 4.0 + pt1 = [xy, xy, 0.0] + pt2 = [-xy, -xy, 0.0] + cpt1 = point(lower(intscts)) + cpt2 = point(upper(intscts)) + @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) + + # starting inside + r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + intscts = surfaceintersection(cyl, r) + @test lower(intscts) isa RayOrigin && isapprox(pt1, point(upper(intscts)), rtol = RTOLERANCE, atol = ATOLERANCE) + + # inside infinite + r = Ray([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) + intscts = surfaceintersection(cyl, r) + @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity + + # on surface + r = Ray([0.5, 0.0, 0.0], [0.0, 0.0, 1.0]) + intscts = surfaceintersection(cyl, r) + @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity + + # on diagonal + r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) + intscts = surfaceintersection(cyl, r) + cpt1 = point(lower(intscts)) + cpt2 = point(upper(intscts)) + pt1 = [xy, xy, xy] + pt2 = [-xy, -xy, -xy] + @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) + + # missing + r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) + intscts = surfaceintersection(cyl, r) + @test intscts isa EmptyInterval + end # testset cylinder + + @testset "Sphere" begin + # Random.seed!(SEED) + + # # random point intersections + # ur, vr = uvrange(Sphere) + # for i in 1:10000 + # sph = Sphere(rand() * 3) + # u, v = rand() * (ur[2] - ur[1]) + ur[1], rand() * (vr[2] - vr[1]) + vr[1] + # pointon = point(sph, u, v) + # origin = SVector(4.0, 4.0, 4.0) + # r = Ray(origin, pointon .- origin) + # halfspace = surfaceintersection(sph, r) + # @test halfspace !== nothing && !((lower(halfspace) isa RayOrigin) || (upper(halfspace) isa Infinity)) + # @test isapprox(pointon, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) || isapprox(pointon, point(upper(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) + # end + + # on xy plane + sph = Sphere(0.5) + r = Ray([1.0, 1.0, 0.0], [-1.0, -1.0, 0.0]) + intscts = surfaceintersection(sph, r) + xy = sqrt(2) / 4.0 + pt1 = [xy, xy, 0.0] + pt2 = [-xy, -xy, 0.0] + cpt1 = point(lower(intscts)) + cpt2 = point(upper(intscts)) + @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) + + # starting inside + r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + intscts = surfaceintersection(sph, r) + @test lower(intscts) isa RayOrigin && isapprox(pt1, point(upper(intscts)), rtol = RTOLERANCE, atol = ATOLERANCE) + + # on diagonal + r = Ray([1.0, 1.0, 1.0], [-1.0, -1.0, -1.0]) + intscts = surfaceintersection(sph, r) + cpt1 = point(lower(intscts)) + cpt2 = point(upper(intscts)) + xyz = sqrt(3) / 6.0 + pt1 = [xyz, xyz, xyz] + pt2 = [-xyz, -xyz, -xyz] + @test isapprox(pt1, cpt1, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pt2, cpt2, rtol = RTOLERANCE, atol = ATOLERANCE) + + # missing + r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) + intscts = surfaceintersection(sph, r) + @test intscts isa EmptyInterval + + # tangent + r = Ray([0.5, 0.5, 0.0], [0.0, -1.0, 0.0]) + intscts = surfaceintersection(sph, r) + @test intscts isa EmptyInterval + end # testset sphere + + @testset "Spherical Cap" begin + @test_throws AssertionError SphericalCap(0.0, 1.0) + @test_throws AssertionError SphericalCap(1.0, 0.0) + + sph = SphericalCap(0.5, π / 3, SVector(1.0, 1.0, 0.0), SVector(1.0, 1.0, 1.0)) + + r = Ray([10.0, 10.0, 1.0], [-1.0, -1.0, 0.0]) + int = surfaceintersection(sph, r) + @test isapprox(point(lower(int)), [1.0, 1.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(int) isa Infinity + + r = Ray([0.8, 1.4, 1.2], [1.0, -1.0, 0.0]) + int = surfaceintersection(sph, r) + @test isapprox(point(lower(int)), [0.898231126982741, 1.301768873017259, 1.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [1.301768873017259, 0.8982311269827411, 1.2], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([2.0, 2.0, 1.5], [-1.0, -1.0, 0.0]) + @test surfaceintersection(sph, r) isa EmptyInterval + end # testset spherical cap + + @testset "Triangle" begin + Random.seed!(SEED) + + tri = Triangle(SVector{3}(2.0, 1.0, 1.0), SVector{3}(1.0, 2.0, 1.0), SVector{3}(1.0, 1.0, 2.0), SVector{2}(0.0, 0.0), SVector{2}(1.0, 0.0), SVector{2}(0.5, 0.5)) + + # random point intersections + for i in 1:1000 + barycentriccoords = rand(3) + barycentriccoords /= sum(barycentriccoords) + pointontri = point(tri, barycentriccoords...) + origin = SVector(3.0, 3.0, 3.0) + r = Ray(origin, pointontri .- origin) + halfspace = surfaceintersection(tri, r) + @test !(halfspace isa EmptyInterval) && upper(halfspace) isa Infinity + t = α(lower(halfspace)) + @test isapprox(point(r, t), point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(pointontri, point(lower(halfspace)), rtol = RTOLERANCE, atol = ATOLERANCE) + end + + # front and back face intersections + tri = Triangle(SVector{3}(0.0, 0.0, 0.0), SVector{3}(1.0, 0.0, 0.0), SVector{3}(0.0, 1.0, 0.0), SVector{2}(0.0, 0.0), SVector{2}(1.0, 0.0), SVector{2}(0.0, 1.0)) + r1 = Ray([0.1, 0.1, 1.0], [0.0, 0.0, -1.0]) + r2 = Ray([0.1, 0.1, -1.0], [0.0, 0.0, 1.0]) + + intsct1 = halfspaceintersection(surfaceintersection(tri, r1)) + intsct2 = halfspaceintersection(surfaceintersection(tri, r2)) + + @test isapprox(point(intsct1), point(intsct2), rtol = RTOLERANCE, atol = ATOLERANCE) + + # ray should miss + r = Ray([1.0, 1.0, 1.0], [0.0, 0.0, -1.0]) + intsct = surfaceintersection(tri, r) + @test intsct isa EmptyInterval + end # testset triangle + + @testset "Plane" begin + rinplane = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 0.0]) + routside = Ray([0.0, 0.0, 2.0], [1.0, 1.0, 1.0]) + rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) + routintersects = Ray([0.0, 0.0, 2.0], [0.0, 0.0, -1.0]) + rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) + pln = Plane(0.0, 0.0, 1.0, 0.0, 0.0, 1.0) + + # parallel to plane and outside + @test surfaceintersection(pln, routside) isa EmptyInterval + + # NOTE for coplanar faces to work with visualization, rays in the plane must count as being 'inside' + # parallel and on plane + res = surfaceintersection(pln, rinplane) + @test lower(res) isa RayOrigin && upper(res) isa Infinity + + # inside but not hitting + res = surfaceintersection(pln, rinside) + @test lower(res) isa RayOrigin && upper(res) isa Infinity + + # starts outside and hits + res = surfaceintersection(pln, routintersects) + @test isapprox(point(lower(res)), [0.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits + res = surfaceintersection(pln, rinintersects) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) + end # testset plane + + @testset "Rectangle" begin + @test_throws AssertionError Rectangle(0.0, 1.0) + @test_throws AssertionError Rectangle(1.0, 0.0) + + rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) + rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) + routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) + rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) + routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) + rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) + rinbounds = Ray([0.5, 0.5, -1.0], [0.0, 0.0, 1.0]) + routbounds = Ray([0.5, 0.5, 1.0], [0.0, 0.0, -1.0]) + + rect = Rectangle(0.5, 0.5) + + # parallel to plane and outside + @test surfaceintersection(rect, routside) isa EmptyInterval + + # parallel and on plane + @test surfaceintersection(rect, rinplane) isa EmptyInterval + + # inside but not hitting + @test surfaceintersection(rect, rinside) isa EmptyInterval + + # starts outside and hits + res = surfaceintersection(rect, routintersects) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits + res = surfaceintersection(rect, rinintersects) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # starts inside and misses bounds + @test surfaceintersection(rect, rinmiss) isa EmptyInterval + + # starts outside and misses bounds + @test surfaceintersection(rect, routmiss) isa EmptyInterval + + # starts outside and hits bounds + res = surfaceintersection(rect, routbounds) + @test isapprox(point(lower(res)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits bounds + res = surfaceintersection(rect, rinbounds) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # test a rectangle with translation and rotation + rect2 = Rectangle(0.3, 0.5, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) + rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) + raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) + res = surfaceintersection(rect2, rayhit) + @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + @test surfaceintersection(rect2, raymiss) isa EmptyInterval + end # testset Rectangle + + @testset "Ellipse" begin + @test_throws AssertionError Ellipse(0.0, 1.0) + @test_nowarn Circle(0.5) + + rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) + rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) + routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) + rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) + routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) + rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) + rinbounds = Ray([0.5, 0.0, -1.0], [0.0, 0.0, 1.0]) + routbounds = Ray([0.5, 0.0, 1.0], [0.0, 0.0, -1.0]) + rasymhit = Ray([0.0, 0.9, 1.0], [0.0, 0.0, -1.0]) + rasymmiss = Ray([0.9, 0.0, 1.0], [0.0, 0.0, -1.0]) + + ell = Ellipse(0.5, 1.0) + + # parallel to plane and outside + @test surfaceintersection(ell, routside) isa EmptyInterval + + # parallel and on plane + @test surfaceintersection(ell, rinplane) isa EmptyInterval + + # inside but not hitting + @test surfaceintersection(ell, rinside) isa EmptyInterval + + # starts outside and hits + res = surfaceintersection(ell, routintersects) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits + res = surfaceintersection(ell, rinintersects) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # starts inside and misses bounds + @test surfaceintersection(ell, rinmiss) isa EmptyInterval + + # starts outside and misses bounds + @test surfaceintersection(ell, routmiss) isa EmptyInterval + + # starts outside and hits bounds + res = surfaceintersection(ell, routbounds) + @test isapprox(point(lower(res)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits bounds + res = surfaceintersection(ell, rinbounds) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # asymmetric hit + res = surfaceintersection(ell, rasymhit) + @test isapprox(point(lower(res)), [0.0, 0.9, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # asymmetric miss + @test surfaceintersection(ell, rasymmiss) isa EmptyInterval + + # test an ellipse with translation and rotation + ell2 = Ellipse(0.3, 0.5, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) + rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) + raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) + res = surfaceintersection(ell2, rayhit) + @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + @test surfaceintersection(ell2, raymiss) isa EmptyInterval + end # testset Ellipse + + @testset "Hexagon" begin + @test_nowarn Hexagon(0.5) + + rinplane = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + routside = Ray([0.0, 0.0, 1.0], [1.0, 1.0, 1.0]) + rinside = Ray([0.0, 0.0, -1.0], [1.0, 1.0, -1.0]) + routintersects = Ray([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]) + rinintersects = Ray([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]) + routmiss = Ray([0.0, 2.0, 1.0], [0.0, 0.0, -1.0]) + rinmiss = Ray([0.0, 2.0, -1.0], [0.0, 0.0, 1.0]) + rinbounds = Ray([sqrt(3) / 2, 0.0, -1.0], [0.0, 0.0, 1.0]) + routbounds = Ray([sqrt(3) / 2, 0.0, 1.0], [0.0, 0.0, -1.0]) + rasymhit = Ray([0.0, 1.0, 1.0], [0.0, 0.0, -1.0]) + rasymmiss = Ray([1.0, 0.0, 1.0], [0.0, 0.0, -1.0]) + + hex = Hexagon(1.0) + + # parallel to plane and outside + @test surfaceintersection(hex, routside) isa EmptyInterval + + # parallel and on plane + @test surfaceintersection(hex, rinplane) isa EmptyInterval + + # inside but not hitting + @test surfaceintersection(hex, rinside) isa EmptyInterval + + # starts outside and hits + res = surfaceintersection(hex, routintersects) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits + res = surfaceintersection(hex, rinintersects) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # starts inside and misses bounds + @test surfaceintersection(hex, rinmiss) isa EmptyInterval + + # starts outside and misses bounds + @test surfaceintersection(hex, routmiss) isa EmptyInterval + + # starts outside and hits bounds + res = surfaceintersection(hex, routbounds) + @test isapprox(point(lower(res)), [sqrt(3) / 2, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # starts inside and hits bounds + res = surfaceintersection(hex, rinbounds) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [sqrt(3) / 2, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # asymmetric hit + res = surfaceintersection(hex, rasymhit) + @test isapprox(point(lower(res)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # asymmetric miss + @test surfaceintersection(hex, rasymmiss) isa EmptyInterval + + # test an ellipse with translation and rotation + hex2 = Hexagon(0.4, SVector(3.0, 1.0, 4.0), SVector(0.2, 0.3, 0.4)) + rayhit = Ray([0.6, 0.6, 0.6], [-0.3, -0.1, -0.3]) + raymiss = Ray([0.7, 0.4, 0.4], [-0.3, -0.1, -0.3]) + res = surfaceintersection(hex2, rayhit) + @test isapprox(point(lower(res)), [0.2863636363636363, 0.4954545454545454, 0.2863636363636363], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + @test surfaceintersection(hex2, raymiss) isa EmptyInterval + end # testset Hexagon + + @testset "Stops" begin + infiniterect = RectangularAperture(0.4, 0.8, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) + finiterect = RectangularAperture(0.4, 0.8, 1.0, 2.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) + # through hole + r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(infiniterect, r) isa EmptyInterval + @test surfaceintersection(finiterect, r) isa EmptyInterval + r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) + @test surfaceintersection(infiniterect, r) isa EmptyInterval + @test surfaceintersection(finiterect, r) isa EmptyInterval + # on edge + r = Ray([0.0, 1.0, 0.6], [0.0, -1.0, 0.0]) + res = surfaceintersection(infiniterect, r) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + res = surfaceintersection(finiterect, r) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + # through hole asym + r = Ray([0.7, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(infiniterect, r) isa EmptyInterval + @test surfaceintersection(finiterect, r) isa EmptyInterval + # on finite edge + r = Ray([1.0, -1.0, 2.0], [0.0, 1.0, 0.0]) + res = surfaceintersection(infiniterect, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) + res = surfaceintersection(finiterect, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) + # outside finite + r = Ray([2.0, 1.0, 3.0], [0.0, -1.0, 0.0]) + @test isapprox(point(surfaceintersection(infiniterect, r)), [2.0, 0.0, 3.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test surfaceintersection(finiterect, r) isa EmptyInterval + + infinitecirc = CircularAperture(0.4, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) + finitecirc = CircularAperture(0.4, 1.0, 2.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) + # through hole + r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(infinitecirc, r) isa EmptyInterval + @test surfaceintersection(finitecirc, r) isa EmptyInterval + r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) + @test surfaceintersection(infinitecirc, r) isa EmptyInterval + @test surfaceintersection(finitecirc, r) isa EmptyInterval + # on edge + r = Ray([0.0, 1.0, 0.6], [0.0, -1.0, 0.0]) + res = surfaceintersection(infinitecirc, r) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + res = surfaceintersection(finitecirc, r) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.6], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + # through hole 2 + r = Ray([0.3, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(infinitecirc, r) isa EmptyInterval + @test surfaceintersection(finitecirc, r) isa EmptyInterval + # on finite edge + r = Ray([1.0, -1.0, 2.0], [0.0, 1.0, 0.0]) + res = surfaceintersection(infinitecirc, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) + res = surfaceintersection(finitecirc, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 2.0], rtol = RTOLERANCE, atol = ATOLERANCE) + # outside finite + r = Ray([2.0, 1.0, 3.0], [0.0, -1.0, 0.0]) + @test isapprox(point(surfaceintersection(infinitecirc, r)), [2.0, 0.0, 3.0], rtol = RTOLERANCE, atol = ATOLERANCE) + @test surfaceintersection(finitecirc, r) isa EmptyInterval + + annulus = Annulus(0.5, 1.0, SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0)) + # through hole + r = Ray([0.0, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(annulus, r) isa EmptyInterval + r = Ray([0.0, -1.0, 1.0], [0.0, 1.0, 0.0]) + @test surfaceintersection(annulus, r) isa EmptyInterval + # on edge + r = Ray([0.0, 1.0, 0.5], [0.0, -1.0, 0.0]) + res = surfaceintersection(annulus, r) + @test isapprox(point(lower(res)), [0.0, 0.0, 0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + # through hole 2 + r = Ray([0.3, 1.0, 1.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(infiniterect, r) isa EmptyInterval + @test surfaceintersection(annulus, r) isa EmptyInterval + # on finite edge + r = Ray([1.0, -1.0, 1.0], [0.0, 1.0, 0.0]) + res = surfaceintersection(annulus, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [1.0, 0.0, 1.0], rtol = RTOLERANCE, atol = ATOLERANCE) + # outside finite + r = Ray([0.9, 1.0, 1.9], [0.0, -1.0, 0.0]) + @test surfaceintersection(annulus, r) isa EmptyInterval + end # testset Stops + + @testset "Bezier" begin + surf = TestData.beziersurface() + accelsurf = AcceleratedParametricSurface(surf) + numsamples = 100 + + samples = samplepoints(numsamples, 0.05, 0.95, 0.05, 0.95) + missedintersections = 0 + + for uv in samples + pt = point(surf, uv[1], uv[2]) + origin = [0.5, 0.5, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(accelsurf, r) + if allintersections isa EmptyInterval + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + @test isapprox(point(halfspaceintersection(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test upper(intsct) isa Infinity + end + end + end + + if missedintersections > 0 + @warn "$missedintersections out of total $(length(samples)) bezier suface intersections were missed" + end + + # miss from outside + r = Ray([0.5, 0.5, 5.0], [0.0, 0.0, 1.0]) + @test surfaceintersection(accelsurf, r) isa EmptyInterval + r = Ray([5.0, 0.5, -5.0], [0.0, 0.0, -1.0]) + @test surfaceintersection(accelsurf, r) isa EmptyInterval + + # miss from inside + r = Ray([0.5, 0.5, -5.0], [0.0, 0.0, -1.0]) + res = surfaceintersection(accelsurf, r) + # TODO!! Fix bezier surface to create halfspace + # @test lower(res) isa RayOrigin && upper(res) isa Infinity + + # hit from inside + r = Ray([0.5, 0.5, 0.2], [0.0, 1.0, 0.0]) + res = surfaceintersection(accelsurf, r) + @test lower(res) isa RayOrigin && isapprox(point(upper(res)), [0.5, 0.8996900152986361, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit from outside + r = Ray([0.0, 0.5, 0.2], [1.0, 0.0, -1.0]) + res = surfaceintersection(accelsurf, r) + @test isapprox(point(lower(res)), [0.06398711204047353, 0.5, 0.13601288795952649], rtol = RTOLERANCE, atol = ATOLERANCE) && upper(res) isa Infinity + + # two hits from outside + r = Ray([0.0, 0.5, 0.25], [1.0, 0.0, 0.0]) + res = surfaceintersection(accelsurf, r) + @test isapprox(point(lower(res)), [0.12607777053030267, 0.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res)), [0.8705887077060419, 0.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + + surf = TestData.wavybeziersurface() + accelsurf = AcceleratedParametricSurface(surf) + + # 3 hit starting outside + r = Ray([0.5, 0.0, 0.0], [0.0, 1.0, 0.0]) + res = surfaceintersection(accelsurf, r) + @test isa(res, DisjointUnion) && length(res) == 2 && isapprox(point(lower(res[1])), [0.5, 0.10013694786182059, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.5, 0.49625, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.8971357794109067, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[2]) isa Infinity) + + # 3 hit starting inside + r = Ray([0.5, 1.0, 0.0], [0.0, -1.0, 0.0]) + res = surfaceintersection(accelsurf, r) + @test isa(res, DisjointUnion) && length(res) == 2 && (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.5, 0.8971357794109067, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(res[2])), [0.5, 0.49625, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.5, 0.10013694786182059, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + surf = TestData.verywavybeziersurface() + accelsurf = AcceleratedParametricSurface(surf, 20) + + # five hits starting outside + r = Ray([0.9, 0.0, -0.3], [0.0, 1.0, 0.7]) + res = surfaceintersection(accelsurf, r) + a = isapprox(point(lower(res[1])), [0.9, 0.03172286522032046, -0.2777939943457758], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.1733979947040411, -0.17862140370717122], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(point(lower(res[3])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) + @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c + + # five hits starting inside + r = Ray([0.9, 1.0, 0.4], [0.0, -1.0, -0.7]) + res = surfaceintersection(accelsurf, r) + a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(point(lower(res[3])), [0.9, 0.17339799470404108, -0.1786214037071712], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[3])), [0.9, 0.03172286522032046, -0.27779399434577573], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c + + # 4 hits starting inside + r = Ray([0.1, 0.0, -0.3], [0.0, 1.0, 0.7]) + res = surfaceintersection(accelsurf, r) + a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.1, 0.2851860296285551, -0.10036977926001144], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.1, 0.5166793625025807, 0.06167555375180668], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.1, 0.7770862508789854, 0.24396037561528983], rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(point(lower(res[3])), [0.1, 0.98308919558696, 0.3881624369108719], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) + @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c + + # 4 hits starting outside + r = Ray([0.9, 0.9, 0.4], [0.0, -0.9, -0.7]) + res = surfaceintersection(accelsurf, r) + a = isapprox(point(lower(res[1])), [0.9, 0.736072142615238, 0.2725005553674076], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.567439326091764, 0.141341698071372], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.9, 0.16601081959179267, -0.1708804736508277], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.032434058775915924, -0.274773509840954], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(res, DisjointUnion) && length(res) == 2 && a && b + end # testset Bezier + + @testset "Zernike" begin + @test_nowarn ZernikeSurface(1.5) + @test_throws AssertionError ZernikeSurface(0.0) + @test_throws AssertionError ZernikeSurface(1.5, aspherics = [(1, 0.1)]) + + z1 = TestData.zernikesurface1() + az1 = AcceleratedParametricSurface(z1, 20) + numsamples = 100 + + # test that an on axis ray doesn't miss + @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) + + samples = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) + missedintersections = 0 + + for uv in samples + pt = point(z1, uv[1], uv[2]) + origin = [0.0, 0.0, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(az1, r) + if (allintersections isa EmptyInterval) + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + if lower(intsct) isa RayOrigin + missedintersections += 1 + else + # closest point should be on the surface, furthest should be on bounding prism + @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) + end + end + end + end + + if missedintersections > 0 + @warn "$missedintersections out of total $(length(samples)) zernike suface intersections were missed" + end + + # hit from inside + r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.12363711269619936, 0.24727422539239863, 0.11818556348099664], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit from outside + r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.07118311042701463, 0.1423662208540292, 0.144084447864927], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) + + # miss from inside + r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) + + # miss from outside + r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + + # two hits from outside + r = Ray([2.0, 0.0, 0.25], [-1.0, 0.0, 0.0]) + hit = surfaceintersection(az1, r) + a = isapprox(point(lower(hit[1])), [1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.3571758210851095, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(hit[2])), [-1.2665828947521165, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b + + # hit cyl from outside + r = Ray([0.0, 2.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.0, 1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit cyl from inside + r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + z2 = TestData.zernikesurface2() + az2 = AcceleratedParametricSurface(z2, 20) + + # two hits + r = Ray([2.0, -1.0, 0.2], [-1.0, 0.0, 0.0]) + intvl = surfaceintersection(az2, r) + @test isapprox(point(lower(intvl)), [0.238496383738369, -1.0, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [-0.8790518227874484, -1.0, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) + + # three hits + r = Ray([-0.7, 1.0, 0.2], [0.0, -1.0, 0.0]) + hit = surfaceintersection(az2, r) + a = (lower(hit[1]) isa RayOrigin) && isapprox(point(upper(hit[1])), [-0.7, 0.8732489598020176, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(hit[2])), [-0.7, -0.34532502965048606, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.7, -1.1615928003236047, 0.2], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b + + # four hits + r = Ray([1.5, 0.1, 0.35], [-1.0, -0.5, 0.0]) + hit = surfaceintersection(az2, r) + a = isapprox(point(lower(hit[1])), [1.4371467631969683, 0.06857338159848421, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.1428338578611368, -0.07858307106943167, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(hit[2])), [0.12916355683491865, -0.5854182215825409, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.7290713614371003, -1.0145356807185504, 0.35], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b + + # failure case, had a bug where rounding error would cause o2 to fail + o1 = leaf(AcceleratedParametricSurface(ZernikeSurface(1.4 * 1.15)), translation(0.0, 0.0, 3.0))() + o2 = leaf(AcceleratedParametricSurface(ZernikeSurface(1.4 * 1.15)), translation(2.0, 0.0, 3.0))() + r = Ray([-5.0, 0.0, 0.0], [1.0, 0.0, 0.0]) + @test !(surfaceintersection(o1, r) isa EmptyInterval) + @test !(surfaceintersection(o2, r) isa EmptyInterval) + end # testset Zernike + + @testset "QType" begin + @test_nowarn QTypeSurface(1.5) + @test_throws AssertionError QTypeSurface(0.0) + + q1 = TestData.qtypesurface1() + aq1 = AcceleratedParametricSurface(q1, 20) + numsamples = 100 + + # test that an on axis ray doesn't miss + @test !(surfaceintersection(AcceleratedParametricSurface(q1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) + + samples = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) + missedintersections = 0 + + for uv in samples + pt = point(q1, uv[1], uv[2]) + origin = [0.0, 0.0, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(aq1, r) + if (allintersections isa EmptyInterval) + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + if lower(intsct) isa RayOrigin + missedintersections += 1 + else + # closest point should be on the surface, furthest should be on bounding prism + @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) + end + end + end + end + + if missedintersections > 0 + @warn "$missedintersections out of total $(length(samples)) qtype suface intersections were missed" + end + + # hit from inside + r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) + intvl = surfaceintersection(aq1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.09758258750208074, 0.19516517500416142, -0.01208706248959643], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit from outside + r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) + intvl = surfaceintersection(aq1, r) + @test isapprox(point(lower(intvl)), [0.10265857811957124, 0.20531715623914257, -0.013292890597856405], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) + + # miss from inside + r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) + intvl = surfaceintersection(aq1, r) + @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) + + # miss from outside + r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(aq1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) + @test surfaceintersection(aq1, r) isa EmptyInterval + r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(aq1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(aq1, r) isa EmptyInterval + end # testset QType + + @testset "BoundingBox" begin + function boundingval(a::BoundingBox{T}, axis::Int, plane::Bool) where {T<:Real} + if axis === 1 + return plane ? a.xmax : a.xmin + elseif axis === 2 + return plane ? a.ymax : a.ymin + elseif axis == 3 + return plane ? a.zmax : a.zmin + else + throw(ErrorException("Invalid axis: $axis")) + end + end + + Random.seed!(SEED) + + axis(x) = (x + 1) ÷ 2 + face(x) = mod(x, 2) + facevalue(a::BoundingBox, x) = boundingval(a, axis(x), Bool(face(x))) + boundingindices = ((2, 3), (1, 3), (1, 2)) + + function facepoint(a::BoundingBox, facenumber) + axisindex = axis(facenumber) + indices = boundingindices[axisindex] + b1min, b1max = boundingval(a, indices[1], false), boundingval(a, indices[1], true) + b2min, b2max = boundingval(a, indices[2], false), boundingval(a, indices[2], true) + + step = 0.00001 + pt1val = rand((b1min + step):step:(b1max - step)) + pt2val = rand((b2min + step):step:(b2max - step)) + + result = Array{Float64,1}(undef, 3) + result[indices[1]] = pt1val + result[indices[2]] = pt2val + result[axisindex] = facevalue(a, facenumber) + return result + end + + bbox = BoundingBox(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0) + + for i in 1:10000 + # randomly pick two planes + face1 = rand(1:6) + face2 = rand(1:6) + while face2 == face1 + face2 = rand(1:6) + end + # pick a point on each face + pt1 = facepoint(bbox, face1) + pt2 = facepoint(bbox, face2) + + direction = normalize(pt1 - pt2) + origin = pt2 - 3 * direction + + r = Ray(origin, direction) + + @test doesintersect(bbox, r) + end + + # should miss + @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [1.0, 0.0, 0.0])) + @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [0.0, 1.0, 0.0])) + @test !doesintersect(bbox, Ray([-2.0, -2.0, 0.0], [0.0, 0.0, 1.0])) + @test !doesintersect(bbox, Ray([-2.0, 0.0, 0.0], [-1.0, 0.0, 0.0])) + @test !doesintersect(bbox, Ray([0.0, -2.0, 0.0], [0.0, -1.0, 0.0])) + @test !doesintersect(bbox, Ray([0.0, 0.0, -2.0], [0.0, 0.0, -1.0])) + + bbox = BoundingBox(-0.5, 0.5, -0.75, 0.75, typemin(Float64), typemax(Float64)) + + # starting outside + r = Ray([-1.0, -1.1, 0.0], [1.0, 1.0, 0.0]) + intscts = surfaceintersection(bbox, r) + @test isapprox(point(lower(intscts)), [-0.5, -0.6, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intscts)), [0.5, 0.4, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # starting inside + r = Ray([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) + intscts = surfaceintersection(bbox, r) + @test lower(intscts) isa RayOrigin && isapprox(point(upper(intscts)), [0.5, 0.5, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # inside infinite + r = Ray([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) + intscts = surfaceintersection(bbox, r) + @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity + + # on surface + r = Ray([0.5, 0.0, 0.0], [0.0, 0.0, 1.0]) + intscts = surfaceintersection(bbox, r) + @test lower(intscts) isa RayOrigin && upper(intscts) isa Infinity + + # missing + r = Ray([5.0, 5.0, 5.0], [0.0, 0.0, -1.0]) + intscts = surfaceintersection(bbox, r) + @test intscts isa EmptyInterval + + r = Ray([5.0, 5.0, 5.0], [0.0, -1.0, 0.0]) + intscts = surfaceintersection(bbox, r) + @test intscts isa EmptyInterval + end # testset BoundingBox + + @testset "CSG" begin + pln1 = Plane([0.0, 0.0, -1.0], [0.0, 0.0, -1.0]) + pln2 = Plane([0.0, 0.0, 1.0], [0.0, 0.0, 1.0]) + r = Ray([0.0, 0, 2.0], [0.0, 0.0, -1.0]) + gen1 = csgintersection(pln1, pln2) + gen2 = csgunion(pln1, pln2) + + csg = gen1(identitytransform()) + intsct = evalcsg(csg, r) + + intvlpt1 = lower(intsct) + intvlpt2 = upper(intsct) + + @test isapprox(point(intvlpt1), SVector{3}(0.0, 0.0, 1.0)) && isapprox(point(intvlpt2), SVector{3}(0.0, 0.0, -1.0)) + + csg = gen2(identitytransform()) + intsct = evalcsg(csg, r) + @test (lower(intsct) isa RayOrigin) && (upper(intsct) isa Infinity) + + # test simple rays for intersection, union and difference operations + # INTERSECTION + intersection_obj = csgintersection(leaf(Cylinder(0.5, 3.0), OpticSim.rotationd(90.0, 0.0, 0.0)), (leaf(Sphere(1.0))))() + r = Ray([0.7, 0.0, 0.0], [-1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test isapprox(point(lower(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([5.0, 0.0, 0.0], [-1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test isapprox(point(lower(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([0.7, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test (int isa EmptyInterval) + + r = Ray([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([0.0, 0.0, 0.0], [0.0, 1.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([0.0, 2.0, 0.0], [0.0, -1.0, 0.0]) + int = OpticSim.evalcsg(intersection_obj, r) + @test isapprox(point(lower(int)), [0.0, 1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [0.0, -1.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # UNION + union_obj = csgunion(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))(OpticSim.translation(1.0, 0.0, 0.0)) + r = Ray([1.0, 0.0, 0.0], [0.0, 0.0, 1.0]) + int = OpticSim.evalcsg(union_obj, r) + @test (lower(int) isa RayOrigin) && (upper(int) isa Infinity) + + r = Ray([1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(union_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([1.6, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(union_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([1.6, 0.0, 0.0], [-1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(union_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(union_obj, r) + @test isapprox(point(lower(int)), [0.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [2.0, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([-1.0, 0.0, 4.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(union_obj, r) + @test isapprox(point(lower(int)), [0.5, 0.0, 4.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [1.5, 0.0, 4.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + # DIFFERENCE + difference_obj = csgdifference(leaf(Cylinder(0.5, 3.0)), leaf(Sphere(1.0), OpticSim.translation(0.75, 0.0, 0.2)))() + r = Ray([0.25, 0.0, 0.0], [-1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test isapprox(point(lower(int)), [-0.2297958971132712, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(int)), [-0.5, 0.0, 0.0], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([0.25, 0.0, 0.0], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test int isa EmptyInterval + + r = Ray([0.25, 0.0, 0.0], [0.0, 0.0, 1.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test isapprox(point(lower(int)), [0.25, 0.0, 1.0660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(int) isa Infinity) + + r = Ray([0.25, 0.0, 1.5], [0.0, 0.0, -1.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test (lower(int[1]) isa RayOrigin) && isapprox(point(upper(int[1])), [0.25, 0.0, 1.0660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(lower(int[2])), [0.25, 0.0, -0.6660254037844386], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(int[2]) isa Infinity) + + r = Ray([0.25, 0.0, 1.5], [1.0, 0.0, 0.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test (lower(int) isa RayOrigin) && isapprox(point(upper(int)), [0.5, 0.0, 1.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([0.25, 0.0, 1.5], [0.0, 0.0, 1.0]) + int = OpticSim.evalcsg(difference_obj, r) + @test (lower(int) isa RayOrigin) && (upper(int) isa Infinity) + + # DisjointUnion result on CSG + surf = TestData.verywavybeziersurface() + accelsurf = leaf(AcceleratedParametricSurface(surf, 20))() + + # five hits starting outside + r = Ray([0.9, 0.0, -0.3], [0.0, 1.0, 0.7]) + res = surfaceintersection(accelsurf, r) + a = isapprox(point(lower(res[1])), [0.9, 0.03172286522032046, -0.2777939943457758], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[1])), [0.9, 0.1733979947040411, -0.17862140370717122], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(point(lower(res[3])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) && (upper(res[3]) isa Infinity) + @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c + + # five hits starting inside + r = Ray([0.9, 1.0, 0.4], [0.0, -1.0, -0.7]) + res = surfaceintersection(accelsurf, r) + a = (lower(res[1]) isa RayOrigin) && isapprox(point(upper(res[1])), [0.9, 0.9830891958374246, 0.3881624370861975], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(res[2])), [0.9, 0.7767707607392784, 0.24373953251749486], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[2])), [0.9, 0.5335760974594397, 0.07350326822160776], rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(point(lower(res[3])), [0.9, 0.17339799470404108, -0.1786214037071712], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(res[3])), [0.9, 0.03172286522032046, -0.27779399434577573], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(res, DisjointUnion) && length(res) == 3 && a && b && c + end # testset CSG + + @testset "ThinGrating" begin + angle_from_ray(raydirection) = 90 + atand(raydirection[3], raydirection[2]) + true_diff(order, λ, period, θi) = asind((order * λ / period + sind(θi))) + period = 3.0 + int = TestData.transmissivethingrating(period, 2) + for k in 1:50 + for θi in [-5.0, 0.0, 5.0, 10.0] + for λ in [0.35, 0.55, 1.0] + ray = OpticalRay(SVector(0.0, 0.0, 2.0), SVector(0.0, sind(θi), -cosd(θi)), 1.0, λ) + raydir, _, _ = OpticSim.processintersection(int, SVector(0.0, 0.0, 0.0), SVector(0.0, 0.0, 1.0), ray, 20.0, 1.0, true, true) + # order is random so just check that it is correct for one of them + @test any([isapprox(x, angle_from_ray(raydir), rtol = RTOLERANCE, atol = ATOLERANCE) for x in [true_diff(m, λ, period, θi) for m in -2:2]]) + end + end + end + end # testset ThinGrating + + # TODO Hologram intersection tests + + @testset "Chebyshev" begin + @test_throws AssertionError ChebyshevSurface(0.0, 1.0, [(1, 2, 1.0)]) + @test_throws AssertionError ChebyshevSurface(1.0, 0.0, [(1, 2, 1.0)]) + + z1 = TestData.chebyshevsurface2() + az1 = AcceleratedParametricSurface(z1, 20) + numsamples = 100 + + # test that an on axis ray doesn't miss + @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) + + samples = samplepoints(numsamples, -0.95, 0.95, -0.95, 0.95) + missedintersections = 0 + + for uv in samples + pt = point(z1, uv[1], uv[2]) + origin = [0.0, 0.0, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(az1, r) + if (allintersections isa EmptyInterval) + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + if lower(intsct) isa RayOrigin + missedintersections += 1 + else + # closest point should be on the surface, furthest should be on bounding prism + @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) + end + end + end + end + + if missedintersections > 0 + @warn "$missedintersections out of total $(length(samples)) chebychev suface intersections were missed" + end + + z1 = TestData.chebyshevsurface1() + az1 = AcceleratedParametricSurface(z1, 20) + + # hit from inside + r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.07870079995991423, 0.15740159991982847, -0.10649600020042879], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit from outside + r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.13584702907541094, 0.2716940581508219, -0.1792351453770547], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [1.0, 2.0, -4.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + # miss from inside + r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) + + # miss from outside + r = Ray([0.2, 3.0, -0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 3.0, 0.5], [0.0, 0.0, -1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 3.0, 2.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + + # two hits from outside + r = Ray([0.0, -1.5, 0.25], [-1.0, 0.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [-1.030882920068228, -1.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [-1.786071230727613, -1.5, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + + r = Ray([1.5, -0.8, 0.25], [-1.0, 0.0, 0.0]) + hit = surfaceintersection(az1, r) + a = isapprox(point(lower(hit[1])), [0.21526832427065165, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [-0.25523748548950076, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(hit[2])), [-1.9273057166986296, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-2.0, -0.8, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b + + # hit cyl from outside + r = Ray([0.0, 3.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.0, 2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit cyl from inside + r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -2.0, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + end # testset Chebyshev + + @testset "GridSag" begin + z1 = TestData.gridsagsurfacebicubic() + az1 = AcceleratedParametricSurface(z1, 20) + + numsamples = 100 + + # test that an on axis ray doesn't miss + @test !(surfaceintersection(AcceleratedParametricSurface(z1), Ray([0.0, 0.0, 10.0], [0.0, 0.0, -1.0])) isa EmptyInterval) + + samples1 = samplepoints(numsamples, 0.01, 0.99, 0.01, 2π - 0.01) + missedintersections = 0 + + for uv in samples1 + pt = point(z1, uv[1], uv[2]) + origin = [0.0, 0.0, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(az1, r) + if (allintersections isa EmptyInterval) + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + if lower(intsct) isa RayOrigin + missedintersections += 1 + else + # closest point should be on the surface, furthest should be on bounding prism + @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) + end + end + end + end + + z2 = TestData.gridsagsurfacebicubiccheby() + az2 = AcceleratedParametricSurface(z2, 20) + + samples2 = samplepoints(numsamples, -0.95, 0.95, -0.95, 0.95) + for uv in samples2 + pt = point(z2, uv[1], uv[2]) + origin = [0.0, 0.0, 5.0] + r = Ray(origin, pt .- origin) + allintersections = surfaceintersection(az2, r) + if (allintersections isa EmptyInterval) + # sampled triangle acceleration structure doesn't guarantee all intersecting rays will be detected. + # Have to use patch subdivision and convex hull polyhedron to do this. + missedintersections += 1 + else + if isa(allintersections, Interval) + allintersections = (allintersections,) + end + + for intsct in allintersections + if lower(intsct) isa RayOrigin + missedintersections += 1 + else + # closest point should be on the surface, furthest should be on bounding prism + @test isapprox(point(lower(intsct)), pt, rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(upper(intsct), Intersection{Float64,3}) || (OpticSim.direction(r)[3] == -1 && isa(upper(intsct), Infinity{Float64})) + end + end + end + end + + if missedintersections > 0 + @warn "$missedintersections out of total $(length(samples1) + length(samples2)) gridsag suface intersections were missed" + end + + # hit from inside + r = Ray([0.0, 0.0, -0.5], [0.1, 0.2, 0.5]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.13038780645120746, 0.26077561290241486, 0.15193903225603717], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit from outside + r = Ray([0.0, 0.0, 0.5], [0.1, 0.2, -0.5]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.046677740288643785, 0.0933554805772876, 0.26661129855678095], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.6708203932499369, 1.3416407864998738, -2.8541019662496843], rtol = RTOLERANCE, atol = ATOLERANCE) + + # miss from inside + r = Ray([0.2, 0.2, -0.5], [0.0, 0.0, -1.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && (upper(intvl) isa Infinity) + + # miss from outside + r = Ray([0.2, 2.0, -0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 0.5], [0.0, 0.0, -1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 0.2, 0.5], [0.0, 0.0, 1.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + r = Ray([0.2, 2.0, 2.0], [0.0, -1.0, 0.0]) + @test surfaceintersection(az1, r) isa EmptyInterval + + # two hits from outside + r = Ray([2.0, 0.0, 0.25], [-1.0, 0.0, 0.0]) + hit = surfaceintersection(az1, r) + a = isapprox(point(lower(hit[1])), [1.5, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[1])), [1.394132887629247, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(point(lower(hit[2])), [0.30184444700168467, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(hit[2])), [-0.6437764440680096, 0.0, 0.25], rtol = RTOLERANCE, atol = ATOLERANCE) + @test isa(hit, DisjointUnion) && length(hit) == 2 && a && b + + # hit cyl from outside + r = Ray([0.0, 2.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test isapprox(point(lower(intvl)), [0.0, 1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + + # hit cyl from inside + r = Ray([0.0, 0.0, -0.5], [0.0, -1.0, 0.0]) + intvl = surfaceintersection(az1, r) + @test (lower(intvl) isa RayOrigin) && isapprox(point(upper(intvl)), [0.0, -1.5, -0.5], rtol = RTOLERANCE, atol = ATOLERANCE) + end # testset GridSag +end # testset intersection diff --git a/test/testsets/JuliaLang.jl b/test/testsets/JuliaLang.jl new file mode 100644 index 000000000..4e4c0df87 --- /dev/null +++ b/test/testsets/JuliaLang.jl @@ -0,0 +1,50 @@ +# FIXING ERRONEOUS UNBOUND TYPE ERRORS THAT OCCUR WITH VARARG +#= +The set will contain something like this: + MultiHologramInterface(interfaces::Vararg{HologramInterface{T}, N}) where {T<:Real, N} +To get the signature run: + methods(OpticSim.MultiHologramInterface).ms[I].sig where I is typically 1 or 2 depending on the number of methods +This gives: + Tuple{Type{MultiHologramInterface}, Vararg{HologramInterface{T}, N}} where N where T<:Real +We then have to use which to find the method: + which(OpticSim.MultiHologramInterface, Tuple{Vararg{HologramInterface{T}, N}} where N where T<:Real) +and pop it from the set +=# + +@otestset "JuliaLang" begin + # here we ignore the specific methods which we know are ok but are still failing + # for some weird reason the unbound args check seems to fail for some (seemingly random) methods with Vararg arguments + methods_to_ignore = Dict( + OpticSim => VERSION >= v"1.6.0-DEV" ? [ + (OpticSim.LensAssembly, Tuple{Vararg{Union{CSGTree{T},LensAssembly{T},Surface{T}},N} where N} where {T<:Real}), + (OpticSim.RayListSource, Tuple{Vararg{OpticalRay{T,3},N} where N} where {T<:Real}), + (OpticSim.OpticalSourceGroup, Tuple{Vararg{OpticalRayGenerator{T},N} where N} where {T<:Real}), + (OpticSim.PixelSource, Tuple{Vararg{P,N} where N} where {P<:OpticalRayGenerator{T}} where {T<:Real}), + (OpticSim.MultiHologramInterface, Tuple{Vararg{HologramInterface{T},N}} where {N} where {T<:Real}), + ] : [], + OpticSim.Zernike => [], + OpticSim.QType => [], + OpticSim.Vis => [ + (OpticSim.Vis.drawcurves, Tuple{Vararg{Spline{P,S,N,M},N1} where N1} where {M} where {N} where {S} where {P}), + (OpticSim.Vis.draw, Tuple{Vararg{S,N} where N} where {S<:Union{OpticSim.Surface{T},OpticSim.TriangleMesh{T}}} where {T<:Real}), + (OpticSim.Vis.draw!, Tuple{OpticSim.Vis.MakieLayout.LScene,Vararg{S,N} where N} where {S<:Union{OpticSim.Surface{T},OpticSim.TriangleMesh{T}}} where {T<:Real}) + ], + OpticSim.Examples => [], + OpticSim.Chebyshev => [], + OpticSim.GlassCat => [], + OpticSim.Optimizable => [], + ) + + for (mod, ignore_list) in methods_to_ignore + unbound = setdiff( + detect_unbound_args(mod), + [which(method, signature) for (method, signature) in ignore_list] + ) + # also ignore any generate methods created due to default or keyword arguments + filter!(m -> !occursin("#", string(m.name)), unbound) + @test isempty(unbound) + + ambiguous = detect_ambiguities(mod) + @test isempty(ambiguous) + end +end # testset JuliaLang diff --git a/test/testsets/Lenses.jl b/test/testsets/Lenses.jl new file mode 100644 index 000000000..e17921fb5 --- /dev/null +++ b/test/testsets/Lenses.jl @@ -0,0 +1,81 @@ +@otestset "Lenses" begin + test_n = 5000 + + """Creates a 3D vector uniformly distributed on the sphere by rejection sampling, i.e., discarding all points with norm > 1.0""" + function randunit() + let v = rand(3) + while (norm(v) > 1.0) + v = rand(3) + end + return normalize(v) + end + end + + @testset "Refraction" begin + Random.seed!(SEED) + ηᵢ = 1.4 + ηₜ = 1.2 + for i in 1:test_n + nₛ = randunit() + rᵢ = randunit() + rₜ = refractedray(ηᵢ, ηₜ, nₛ, rᵢ) + if !(rₜ === nothing) + sinθₜ = norm(cross(-nₛ, rₜ)) + sinθᵢ = norm(cross(nₛ, rᵢ)) + a = isapprox(ηₜ * sinθₜ, ηᵢ * sinθᵢ, rtol = RTOLERANCE, atol = ATOLERANCE) #verify snell's law for the the transmitted and incident ray + b = isapprox(1.0, norm(rₜ), rtol = RTOLERANCE, atol = ATOLERANCE) #verify transmitted ray is unit + perp = normalize(cross(nₛ, rᵢ)) + c = isapprox(0.0, rₜ ⋅ perp, rtol = RTOLERANCE, atol = ATOLERANCE) #verify incident and transmitted ray are in the same plane + @test a && b && c + end + end + end # testset refraction + + # snell + @testset "Snell" begin + Random.seed!(SEED) + for i in 1:test_n + r = randunit() + nₛ = randunit() + n1 = 1.0 + n2 = 1.4 + sθ1, sθ2 = snell(nₛ, r, n1, n2) + sθ3, sθ4 = snell(nₛ, r, n2, n1) + @test isapprox(sθ1 * n1, sθ2 * n2, rtol = RTOLERANCE, atol = ATOLERANCE) && isapprox(sθ3 * n2, sθ4 * n1, rtol = RTOLERANCE, atol = ATOLERANCE) + end + end # testset snell + + @testset "Reflection" begin + Random.seed!(SEED) + # reflection + for i in 1:test_n + r = randunit() + nₛ = randunit() + if abs(r ⋅ nₛ) < 0.9999 # if r and n are exactly aligned then their sum will be zero, not a vector pointing in the direction of the normal + reflected = reflectedray(nₛ, r) + #ensure reflected and refracted rays are in the same plane + a = isapprox(norm(reflected ⋅ cross(r, nₛ)), 0.0, rtol = RTOLERANCE, atol = ATOLERANCE) + b = isapprox(norm(reflected), 1.0, rtol = RTOLERANCE, atol = ATOLERANCE) + c = isapprox(0.0, reflected ⋅ nₛ + r ⋅ nₛ, rtol = RTOLERANCE, atol = ATOLERANCE) + #ensure sum of reflected and origin ray are in the direction of the normal + d = isapprox(0.0, norm(nₛ - sign(reflected ⋅ nₛ) * (normalize(reflected - r))), rtol = RTOLERANCE, atol = ATOLERANCE) + @test a & b & c & d + end + end + end # testset reflection + + @testset "Paraxial" begin + # check that normally incident rays are focussed across the lens + l = ParaxialLensEllipse(100.0, 10.0, 10.0, [1.0, 1.0, 0.0], [3.0, 3.0, 3.0]) + r = OpticalRay([2.0, 2.0, 3.0], [1.0, 1.0, 0.0], 1.0, 0.55) + intsct = halfspaceintersection(surfaceintersection(l, r)) + ref, _, _ = OpticSim.processintersection(OpticSim.interface(intsct), OpticSim.point(intsct), OpticSim.normal(intsct), r, 20.0, 1.0, true, true) + @test isapprox(ref, normalize([1.0, 1.0, 0.0]), rtol = RTOLERANCE, atol = ATOLERANCE) + l = ParaxialLensRect(100.0, 10.0, 10.0, [1.0, 1.0, 0.0], [3.0, 3.0, 3.0]) + r = OpticalRay([2.3, 2.4, 3.1], [1.0, 1.0, 0.0], 1.0, 0.55) + intsct = halfspaceintersection(surfaceintersection(l, r)) + fp = [3.0, 3.0, 3.0] + 100 * normalize([1.0, 1.0, 0.0]) + ref, _, _ = OpticSim.processintersection(OpticSim.interface(intsct), OpticSim.point(intsct), OpticSim.normal(intsct), r, 20.0, 1.0, true, true) + @test isapprox(ref, normalize(fp - OpticSim.point(intsct)), rtol = RTOLERANCE, atol = ATOLERANCE) + end # testset Paraxial +end # testset lenses diff --git a/test/testsets/SurfaceDefs.jl b/test/testsets/SurfaceDefs.jl new file mode 100644 index 000000000..09ba0c00c --- /dev/null +++ b/test/testsets/SurfaceDefs.jl @@ -0,0 +1,220 @@ +@otestset "SurfaceDefs" begin + @testset "Knots" begin + # find span + knots = KnotVector{Int64}([0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 5]) + function apply(knots::KnotVector, curveorder, u, correctindex) + knotnum = findspan(knots, curveorder, u) + return knotnum == correctindex + end + @test apply(knots, 2, 2.5, 5) && apply(knots, 2, 0, 3) && apply(knots, 2, 4, 6) && apply(knots, 2, 5, 8) + + # insert knots + knots = [0, 0, 0, 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 7, 7] + insertions = knotstoinsert(knots, 3) + @test insertions == [(5, 2), (6, 2), (7, 1), (9, 1), (11, 1), (13, 2)] + end + + @testset "BSpline" begin + # bspline conversion + correctverts = [[0.0, 0.0, 0.0], [0.0, 0.49625, 0.0], [0.6625, 0.49625, 0.65625], [0.0, 0.0, 0.0], [0.6625, 0.49625, 0.65625], [0.6625, 0.0, 0.0], [0.6625, 0.0, 0.0], [0.6625, 0.49625, 0.65625], [1.33, 0.49625, 0.0], [0.6625, 0.0, 0.0], [1.33, 0.49625, 0.0], [1.33, 0.0, 0.0], [0.0, 0.49625, 0.0], [0.0, 1.0, 0.0], [0.6625, 1.0, 0.0], [0.0, 0.49625, 0.0], [0.6625, 1.0, 0.0], [0.6625, 0.49625, 0.65625], [0.6625, 0.49625, 0.65625], [0.6625, 1.0, 0.0], [1.33, 1.0, 0.0], [0.6625, 0.49625, 0.65625], [1.33, 1.0, 0.0], [1.33, 0.49625, 0.0]] + correcttris = [ + 0x00000001 0x00000002 0x00000003 + 0x00000004 0x00000005 0x00000006 + 0x00000007 0x00000008 0x00000009 + 0x0000000a 0x0000000b 0x0000000c + 0x0000000d 0x0000000e 0x0000000f + 0x00000010 0x00000011 0x00000012 + 0x00000013 0x00000014 0x00000015 + 0x00000016 0x00000017 0x00000018 + ] + surf = TestData.bsplinesurface() + points, triangles = makiemesh(makemesh(surf, 2)) + segments = tobeziersegments(surf) # TODO just testing that this evaluates for now + @test triangles == correcttris + @test all(isapprox.(points, correctverts, rtol = RTOLERANCE, atol = ATOLERANCE)) + end # testset BSpline + + @testset "PowerBasis" begin + # polynomial is 3x^2 + 2x + 1 + coeff = [1.0 2.0 3.0] + curve = PowerBasisCurve{OpticSim.Euclidean,Float64,1,2}(coeff) + @test !all(isapprox.(coeff, coefficients(curve, 1), rtol = RTOLERANCE, atol = ATOLERANCE)) # TODO not sure if this is correct/what this is testing? + correct = true + for x in -10.0:0.1:10 + exact = 3 * x^2 + 2 * x + 1 + calculated = point(curve, x)[1] + @test isapprox(exact, calculated, rtol = RTOLERANCE, atol = ATOLERANCE) + end + end # testset PowerBasis + + @testset "BezierSurface" begin + surf = TestData.beziersurface() + fdm = central_fdm(10, 1) + for u in 0:0.1:1, v in 0:0.1:1 + (du, dv) = partials(surf, u, v) + fu(u) = point(surf, u, v) + fv(v) = point(surf, u, v) + # compute accurate finite difference approximation to derivative + fdu, fdv = (fdm(fu, u), fdm(fv, v)) + @test isapprox(du, fdu, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dv, fdv, rtol = 1e-12, atol = 2 * eps(Float64)) + end + end # testset BezierSurface + + @testset "ZernikeSurface" begin + surf = TestData.zernikesurface1a() # with normradius + fdm = central_fdm(10, 1) + for ρ in 0.05:0.1:0.95, ϕ in 0:(π / 10):(2π) + (dρ, dϕ) = partials(surf, ρ, ϕ) + fρ(ρ) = point(surf, ρ, ϕ) + fϕ(ϕ) = point(surf, ρ, ϕ) + # compute accurate finite difference approximation to derivative + fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) + @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) + end + + @test !any(isnan.(normal(TestData.conicsurface(), 0.0, 0.0))) + end # testset ZernikeSurface + + @testset "QTypeSurface" begin + # test predefined values against papers + # Forbes, G. W. "Robust, efficient computational methods for axially symmetric optical aspheres." OpticSim express 18.19 (2010): 19700-19712. + # Forbes, G. W. "Characterizing the shape of freeform optics." OpticSim express 20.3 (2012): 2483-2499. + + f0_true = [2, sqrt(19 / 4), 4 * sqrt(10 / 19), 1 / 2 * sqrt(509 / 10), 6 * sqrt(259 / 509), 1 / 2 * sqrt(25607 / 259)] + for i in 1:length(f0_true) + @test isapprox(OpticSim.QType.f0(i - 1), f0_true[i], rtol = 2 * eps(Float64)) + end + g0_true = [-1 / 2, -5 / (2 * sqrt(19)), -17 / (2 * sqrt(190)), -91 / (2 * sqrt(5090)), -473 / (2 * sqrt(131831))] + for i in 1:length(g0_true) + @test isapprox(OpticSim.QType.g0(i - 1), g0_true[i], rtol = 2 * eps(Float64)) + end + h0_true = [-1 / 2, -6 / sqrt(19), -3 / 2 * sqrt(19 / 10), -20 * sqrt(10 / 509)] + for i in 1:length(h0_true) + @test isapprox(OpticSim.QType.h0(i - 1), h0_true[i], rtol = 2 * eps(Float64)) + end + + F_true = [1/4 1/2 27/32 5/4; 15/32 7/8 35/16 511/128; 17/72 35/36 35/16 23/6; 29/40 67/40 243/80 12287/2560] + N, M = size(F_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.F(m, n), F_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + G_true = [1/4 3/8 15/32 35/64; -1/24 -5/48 -7/32 -21/64; -7/40 -7/16 -117/160 -33/32] + N, M = size(G_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.G(m, n), G_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + f_true = [1/2 1/sqrt(2) 3 / 4*sqrt(3 / 2) sqrt(5)/2; 1 / 4*sqrt(7 / 2) 1 / 4*sqrt(19 / 2) 1 / 4*sqrt(185 / 6) 3 / 32*sqrt(427); 1 / 6*sqrt(115 / 14) 1 / 2*sqrt(145 / 38) 1 / 4*sqrt(12803 / 370) 1 / 2*sqrt(2785 / 183); 1 / 5*sqrt(3397 / 230) 1 / 4*sqrt(6841 / 290) 9 / 4*sqrt(14113 / 25606) 1 / 16*sqrt(1289057 / 1114)] + N, M = size(f_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.f(m, n), f_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + g_true = [1/2 3/(4 * sqrt(2)) 5/(4 * sqrt(6)) 7 * sqrt(5)/32; -1/(3 * sqrt(14)) -5/(6 * sqrt(38)) -7 / 4*sqrt(3 / 370) -1 / 2*sqrt(7 / 61); -21 / 10*sqrt(7 / 230) -7 / 4*sqrt(19 / 290) -117 / 4*sqrt(37 / 128030) -33 / 16*sqrt(183 / 2785)] + N, M = size(g_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.g(m, n), g_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + A_true = [2 3 5 7 9 11; -4/3 2/3 2 76/27 85/24 106/25; 9/5 26/15 2 117/50 203/75 108/35; 55/28 66/35 2 536/245 135/56 130/49; 161/81 122/63 2 515/243 143/63 161/66] + N, M = size(A_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.A(m, n), A_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + B_true = [-1 -2 -4 -6 -8 -10; -8/3 -4 -4 -40/9 -5 -28/5; -24/5 -4 -4 -21/5 -112/25 -24/5; -30/7 -4 -4 -144/35 -30/7 -220/49; -112/27 -4 -4 -110/27 -88/21 -13/3] + N, M = size(B_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.B(m, n), B_true[n + 1, m], rtol = 2 * eps(Float64)) + end + end + C_true = [NaN NaN NaN NaN NaN NaN; -11/3 -3 -5/3 -35/27 -9/8 -77/75; 0 5/9 7/15 21/50 88/225 13/35; 27/28 21/25 27/35 891/1225 39/56 33/49; 80/81 45/49 55/63 1430/1701 40/49 1105/1386] + N, M = size(C_true) + for m in 1:M + for n in 0:(N - 1) + @test isapprox(OpticSim.QType.C(m, n), C_true[n + 1, m], rtol = 2 * eps(Float64)) || (isnan(OpticSim.QType.C(m, n)) && isnan(C_true[n + 1, m])) + end + end + + # test deriv with finite differences + surf = TestData.qtypesurface1() + fdm = central_fdm(10, 1) + for ρ in 0.05:0.1:0.95, ϕ in 0:(π / 10):(2π) + (dρ, dϕ) = partials(surf, ρ, ϕ) + fρ(ρ) = point(surf, ρ, ϕ) + fϕ(ϕ) = point(surf, ρ, ϕ) + # compute accurate finite difference approximation to derivative + fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) + @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) + end + end # testset QTypeSurface + + @testset "GridSag" begin + surf = TestData.gridsagsurfacelinear() + fdm = central_fdm(10, 1) + # doesn't enforce C1 across patch boundaries meaning that finite differences won't match at all + # just test within a patch to make sure partials() is working + for ρ in 0.02:0.01:0.18, ϕ in 0:(π / 30):(2π) + (dρ, dϕ) = partials(surf, ρ, ϕ) + fρ(ρ) = point(surf, ρ, ϕ) + fϕ(ϕ) = point(surf, ρ, ϕ) + # compute accurate finite difference approximation to derivative + fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) + @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) + end + + surf = TestData.gridsagsurfacebicubic() + fdm = central_fdm(10, 1) + # doesn't enforce C2 across patch boundaries meaning that finite differences won't match exactly + # just test within a patch to make sure partials() is working + for ρ in 0.02:0.01:0.18, ϕ in 0:(π / 30):(2π) + (dρ, dϕ) = partials(surf, ρ, ϕ) + fρ(ρ) = point(surf, ρ, ϕ) + fϕ(ϕ) = point(surf, ρ, ϕ) + # compute accurate finite difference approximation to derivative + fdρ, fdϕ = (fdm(fρ, ρ), fdm(fϕ, ϕ)) + @test isapprox(dρ, fdρ, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dϕ, fdϕ, rtol = 1e-12, atol = 2 * eps(Float64)) + end + + surf = TestData.gridsagsurfacebicubiccheby() + fdm = central_fdm(10, 1) + # not valid at the very boundary of the surface as stuff gets weird outside |u| < 1 and |v| < 1 + for u in -0.3:0.01:0.3, v in -0.15:0.01:0.15 + (du, dv) = partials(surf, u, v) + fu(u) = point(surf, u, v) + fv(v) = point(surf, u, v) + # compute accurate finite difference approximation to derivative + fdu, fdv = (fdm(fu, u), fdm(fv, v)) + @test isapprox(du, fdu, rtol = 1e-12, atol = 2 * eps(Float64)) + @test isapprox(dv, fdv, rtol = 1e-12, atol = 2 * eps(Float64)) + end + end # testset GridSag + + @testset "ChebyshevSurface" begin + surf = TestData.chebyshevsurface1() # with normradius + fdm = central_fdm(10, 1) + # not valid at the very boundary of the surface as stuff gets weird outside |u| < 1 and |v| < 1 + for u in -0.99:0.1:0.99, v in -0.99:0.1:0.99 + (du, dv) = partials(surf, u, v) + fu(u) = point(surf, u, v) + fv(v) = point(surf, u, v) + # compute accurate finite difference approximation to derivative + fdu, fdv = (fdm(fu, u), fdm(fv, v)) + @test isapprox(du, fdu, rtol = 1e-11, atol = 2 * eps(Float64)) #changed rtol from 1e-12 to 1e-11. FiniteDifferences approximation to the derivative had larger than expected error. + @test isapprox(dv, fdv, rtol = 1e-11, atol = 2 * eps(Float64)) #changed rtol from 1e-12 to 1e-11. FiniteDifferences approximation to the derivative had larger than expected error. + end + end # testset ChebyshevSurface + +end # testset SurfaceDefs diff --git a/test/testsets/TestData.jl b/test/testsets/TestData.jl new file mode 100644 index 000000000..861b9ab1a --- /dev/null +++ b/test/testsets/TestData.jl @@ -0,0 +1,3 @@ +@otestset "TestData" begin + @test_all_no_arg_functions TestData +end # testset TestData diff --git a/test/testsets/Visualization.jl b/test/testsets/Visualization.jl new file mode 100644 index 000000000..95a3465be --- /dev/null +++ b/test/testsets/Visualization.jl @@ -0,0 +1,15 @@ +@otestset "Visualization" begin + # test that this all at least runs + surf1 = AcceleratedParametricSurface(TestData.beziersurface(), 15) + surf2 = AcceleratedParametricSurface(TestData.upsidedownbeziersurface(), 15) + m = csgintersection(leaf(surf1, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf1, RigidBodyTransform{Float64}(0.0, Float64(π), 0.0, 0.5, -0.5, 0.0))))() + @test_nowarn Vis.draw(m) + m = csgintersection(leaf(surf1, translation(-0.5, -0.5, 0.0)), csgintersection(leaf(Cylinder(0.3, 5.0)), leaf(surf2, translation(-0.5, -0.5, 0.0))))() + @test_nowarn Vis.draw(m) + m = csgunion(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() + @test_nowarn Vis.draw(m) + m = csgintersection(leaf(Cylinder(0.5, 3.0)), (leaf(Sphere(1.0))))() + @test_nowarn Vis.draw(m) + m = csgdifference(leaf(Cylinder(0.5, 3.0)), leaf(Sphere(1.0)))() + @test_nowarn Vis.draw(m) +end # testset Visualization