From c6ece7776fc0b09228ccd33148ac0c51bb3a3b27 Mon Sep 17 00:00:00 2001
From: Udo Eisenbarth <u.eisenbarth@gsi.de>
Date: Wed, 30 Oct 2024 08:22:57 +0000
Subject: [PATCH] * Add fn with_oap_angles to ParabolicMirror for modelling
 off-axis parabolas.

* Add example parabolic_mirror and streamline code.

* feat: :sparkles: Add ParabolicMirror node.

* Add calculation of normal vector for parabola.

* Add (yet empty) parabola surface.

Attacks #292
---
 Cargo.lock                            | 321 ++++++++++++--------------
 opossum/examples/parabolic_mirror.rs  |  49 ++++
 opossum/src/main.rs                   |   2 +-
 opossum/src/nodes/mod.rs              |   8 +
 opossum/src/nodes/node_group/mod.rs   |  30 ++-
 opossum/src/nodes/parabolic_mirror.rs | 231 ++++++++++++++++++
 opossum/src/surface/mod.rs            |   2 +
 opossum/src/surface/parabola.rs       | 209 +++++++++++++++++
 opossum/src/surface/sphere.rs         |   8 -
 opossum/test                          | Bin 0 -> 116972 bytes
 10 files changed, 675 insertions(+), 185 deletions(-)
 create mode 100644 opossum/examples/parabolic_mirror.rs
 create mode 100644 opossum/src/nodes/parabolic_mirror.rs
 create mode 100644 opossum/src/surface/parabola.rs
 create mode 100644 opossum/test

diff --git a/Cargo.lock b/Cargo.lock
index a0fa4571..a3cfd778 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -207,9 +207,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.6.15"
+version = "0.6.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -222,43 +222,43 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.8"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.1.1"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.4"
+version = "3.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
 dependencies = [
  "anstyle",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.89"
+version = "1.0.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
 
 [[package]]
 name = "approx"
@@ -283,7 +283,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -421,9 +421,9 @@ dependencies = [
 
 [[package]]
 name = "avif-serialize"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
+checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
 dependencies = [
  "arrayvec",
 ]
@@ -571,7 +571,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -655,7 +655,7 @@ checksum = "3fbfc33a4c6b80760bb8bf850a2cc65a1e031da62fd3ca8b552189104dc98514"
 dependencies = [
  "bevy_macro_utils",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -704,7 +704,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -773,7 +773,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -900,7 +900,7 @@ checksum = "bfc65e570012e64a21f3546df68591aaede8349e6174fb500071677f54f06630"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
  "toml_edit",
 ]
 
@@ -989,7 +989,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
  "uuid",
 ]
 
@@ -1025,7 +1025,7 @@ dependencies = [
  "encase",
  "futures-lite",
  "hexasphere",
- "image 0.25.2",
+ "image 0.25.4",
  "js-sys",
  "ktx2",
  "naga",
@@ -1050,7 +1050,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1122,7 +1122,7 @@ dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1242,7 +1242,7 @@ checksum = "38f1ab8f2f6f58439d260081d89a42b02690e5fdd64f814edc9417d33fcf2857"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1304,7 +1304,7 @@ dependencies = [
  "regex",
  "rustc-hash",
  "shlex",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1392,9 +1392,9 @@ dependencies = [
 
 [[package]]
 name = "built"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
+checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
 
 [[package]]
 name = "bumpalo"
@@ -1404,9 +1404,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "bytemuck"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
+checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
 dependencies = [
  "bytemuck_derive",
 ]
@@ -1419,7 +1419,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1436,9 +1436,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
 
 [[package]]
 name = "bytes"
-version = "1.7.2"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
 
 [[package]]
 name = "calloop"
@@ -1456,9 +1456,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.1.28"
+version = "1.1.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
+checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
 dependencies = [
  "jobserver",
  "libc",
@@ -1551,9 +1551,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.19"
+version = "4.5.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
+checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -1561,9 +1561,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.19"
+version = "4.5.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
+checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1580,7 +1580,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1616,9 +1616,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
 [[package]]
 name = "colorchoice"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
 name = "colorous"
@@ -1959,7 +1959,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim 0.11.1",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -1981,7 +1981,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
  "darling_core 0.20.10",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2025,11 +2025,11 @@ dependencies = [
 
 [[package]]
 name = "derive_builder"
-version = "0.20.1"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
 dependencies = [
- "derive_builder_macro 0.20.1",
+ "derive_builder_macro 0.20.2",
 ]
 
 [[package]]
@@ -2046,14 +2046,14 @@ dependencies = [
 
 [[package]]
 name = "derive_builder_core"
-version = "0.20.1"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
 dependencies = [
  "darling 0.20.10",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2068,12 +2068,12 @@ dependencies = [
 
 [[package]]
 name = "derive_builder_macro"
-version = "0.20.1"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
 dependencies = [
- "derive_builder_core 0.20.1",
- "syn 2.0.79",
+ "derive_builder_core 0.20.2",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2159,9 +2159,9 @@ dependencies = [
 
 [[package]]
 name = "dwrote"
-version = "0.11.1"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c"
+checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd"
 dependencies = [
  "lazy_static",
  "libc",
@@ -2216,7 +2216,7 @@ checksum = "fd31dbbd9743684d339f907a87fe212cb7b51d75b9e8e74181fe363199ee9b47"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2334,9 +2334,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
 
 [[package]]
 name = "fdeflate"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab"
+checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
 dependencies = [
  "simd-adler32",
 ]
@@ -2371,9 +2371,9 @@ checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
 
 [[package]]
 name = "flume"
-version = "0.11.0"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
 dependencies = [
  "spin",
 ]
@@ -2427,7 +2427,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2644,7 +2644,7 @@ dependencies = [
  "inflections",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -2893,9 +2893,9 @@ dependencies = [
 
 [[package]]
 name = "image"
-version = "0.25.2"
+version = "0.25.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
+checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
 dependencies = [
  "bytemuck",
  "byteorder-lite",
@@ -2916,9 +2916,9 @@ dependencies = [
 
 [[package]]
 name = "image-webp"
-version = "0.1.3"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
+checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
 dependencies = [
  "byteorder-lite",
  "quick-error",
@@ -2926,15 +2926,15 @@ dependencies = [
 
 [[package]]
 name = "imgref"
-version = "1.10.1"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
+checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
 
 [[package]]
 name = "immutable-chunkmap"
-version = "2.0.5"
+version = "2.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4419f022e55cc63d5bbd6b44b71e1d226b9c9480a47824c706e9d54e5c40c5eb"
+checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578"
 dependencies = [
  "arrayvec",
 ]
@@ -2993,7 +2993,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -3075,9 +3075,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
 
 [[package]]
 name = "js-sys"
-version = "0.3.70"
+version = "0.3.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -3142,9 +3142,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.159"
+version = "0.2.161"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
 
 [[package]]
 name = "libflate"
@@ -3209,17 +3209,6 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
-[[package]]
-name = "libredox"
-version = "0.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
-dependencies = [
- "bitflags 2.6.0",
- "libc",
- "redox_syscall 0.4.1",
-]
-
 [[package]]
 name = "libredox"
 version = "0.1.3"
@@ -3228,6 +3217,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
  "bitflags 2.6.0",
  "libc",
+ "redox_syscall 0.5.7",
 ]
 
 [[package]]
@@ -3434,9 +3424,9 @@ dependencies = [
 
 [[package]]
 name = "nalgebra"
-version = "0.33.0"
+version = "0.33.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c4b5f057b303842cf3262c27e465f4c303572e7f6b0648f60e16248ac3397f4"
+checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
 dependencies = [
  "approx",
  "matrixmultiply",
@@ -3457,7 +3447,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -3621,7 +3611,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -3683,7 +3673,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -3963,13 +3953,13 @@ dependencies = [
  "bevy",
  "bevy_flycam",
  "chrono",
- "clap 4.5.19",
+ "clap 4.5.20",
  "colorous",
  "csv",
  "delaunator",
  "embed-doc-image",
  "env_logger",
- "image 0.25.2",
+ "image 0.25.4",
  "itertools 0.13.0",
  "kahan",
  "log",
@@ -4006,11 +3996,11 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "orbclient"
-version = "0.3.47"
+version = "0.3.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
+checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43"
 dependencies = [
- "libredox 0.0.2",
+ "libredox",
 ]
 
 [[package]]
@@ -4127,29 +4117,29 @@ dependencies = [
 
 [[package]]
 name = "pin-project"
-version = "1.1.6"
+version = "1.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec"
+checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.6"
+version = "1.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8"
+checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.14"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
 
 [[package]]
 name = "piper"
@@ -4283,30 +4273,30 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.87"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
+checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "profiling"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
+checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
 dependencies = [
  "profiling-procmacros",
 ]
 
 [[package]]
 name = "profiling-procmacros"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
+checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
 dependencies = [
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -4412,9 +4402,9 @@ dependencies = [
 
 [[package]]
 name = "ravif"
-version = "0.11.10"
+version = "0.11.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd"
+checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
 dependencies = [
  "avif-serialize",
  "imgref",
@@ -4487,15 +4477,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
 dependencies = [
  "getrandom",
- "libredox 0.1.3",
+ "libredox",
  "thiserror",
 ]
 
 [[package]]
 name = "regex"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -4546,9 +4536,6 @@ name = "rgb"
 version = "0.8.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
-dependencies = [
- "bytemuck",
-]
 
 [[package]]
 name = "rle-decode-fast"
@@ -4640,9 +4627,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.37"
+version = "0.38.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
+checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
 dependencies = [
  "bitflags 2.6.0",
  "errno",
@@ -4653,9 +4640,9 @@ dependencies = [
 
 [[package]]
 name = "rustversion"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
 
 [[package]]
 name = "ruzstd"
@@ -4710,29 +4697,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
 
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.214"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.128"
+version = "1.0.132"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
+checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
 dependencies = [
  "itoa",
  "memchr",
@@ -4931,7 +4918,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -4953,9 +4940,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.79"
+version = "2.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5047,22 +5034,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
 
 [[package]]
 name = "thiserror"
-version = "1.0.64"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.64"
+version = "1.0.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -5197,7 +5184,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -5376,9 +5363,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uuid"
-version = "1.10.0"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
 dependencies = [
  "getrandom",
  "rand",
@@ -5421,7 +5408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349ed9e45296a581f455bc18039878f409992999bc1d5da12a6800eb18c8752f"
 dependencies = [
  "anyhow",
- "derive_builder 0.20.1",
+ "derive_builder 0.20.2",
  "rustversion",
  "vergen-lib",
 ]
@@ -5433,7 +5420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e771aff771c0d7c2f42e434e2766d304d917e29b40f0424e8faaaa936bbc3f29"
 dependencies = [
  "anyhow",
- "derive_builder 0.20.1",
+ "derive_builder 0.20.2",
  "git2",
  "rustversion",
  "time",
@@ -5448,7 +5435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "229eaddb0050920816cf051e619affaf18caa3dd512de8de5839ccbc8e53abb0"
 dependencies = [
  "anyhow",
- "derive_builder 0.20.1",
+ "derive_builder 0.20.2",
  "rustversion",
 ]
 
@@ -5491,9 +5478,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -5502,24 +5489,24 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.43"
+version = "0.4.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -5529,9 +5516,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -5539,28 +5526,28 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
 
 [[package]]
 name = "web-sys"
-version = "0.3.70"
+version = "0.3.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -5810,7 +5797,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -5821,7 +5808,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -5832,7 +5819,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -5843,7 +5830,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
@@ -6265,7 +6252,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.79",
+ "syn 2.0.85",
 ]
 
 [[package]]
diff --git a/opossum/examples/parabolic_mirror.rs b/opossum/examples/parabolic_mirror.rs
new file mode 100644
index 00000000..b60b2646
--- /dev/null
+++ b/opossum/examples/parabolic_mirror.rs
@@ -0,0 +1,49 @@
+use opossum::{
+    analyzers::{AnalyzerType, RayTraceConfig},
+    coatings::CoatingType,
+    degree,
+    error::OpmResult,
+    joule, millimeter,
+    nodes::{
+        round_collimated_ray_source, EnergyMeter, NodeGroup, ParabolicMirror,
+        RayPropagationVisualizer, SpotDiagram, ThinMirror, WaveFront,
+    },
+    optic_node::{Alignable, OpticNode},
+    optic_ports::PortType,
+    OpmDocument,
+};
+use std::path::Path;
+
+fn main() -> OpmResult<()> {
+    let mut scenery = NodeGroup::default();
+    let i_src = scenery.add_node(&round_collimated_ray_source(
+        millimeter!(20.0),
+        joule!(1.0),
+        3,
+    )?)?;
+    let mut mirror1 = ThinMirror::new("mirror 1").with_tilt(degree!(22.5, 0.0, 0.0))?;
+    mirror1.set_coating(
+        &PortType::Input,
+        "input",
+        &CoatingType::ConstantR { reflectivity: 0.5 },
+    )?;
+    let i_m1 = scenery.add_node(&mirror1)?;
+    let i_m2 = scenery.add_node(
+        &ParabolicMirror::new("parabola", millimeter!(-50.0))?
+            .with_oap_angles(degree!(45.0, 0.0))?,
+    )?;
+    let i_prop_vis = scenery.add_node(&RayPropagationVisualizer::default())?;
+    let i_sd = scenery.add_node(&SpotDiagram::default())?;
+    let i_wf = scenery.add_node(&WaveFront::default())?;
+    let i_pm = scenery.add_node(&EnergyMeter::default())?;
+    scenery.connect_nodes(i_src, "out1", i_m1, "input", millimeter!(100.0))?;
+    scenery.connect_nodes(i_m1, "reflected", i_m2, "input", millimeter!(100.0))?;
+    scenery.connect_nodes(i_m2, "reflected", i_prop_vis, "in1", millimeter!(80.0))?;
+    scenery.connect_nodes(i_prop_vis, "out1", i_sd, "in1", millimeter!(0.1))?;
+    scenery.connect_nodes(i_sd, "out1", i_wf, "in1", millimeter!(0.1))?;
+    scenery.connect_nodes(i_wf, "out1", i_pm, "in1", millimeter!(0.1))?;
+
+    let mut doc = OpmDocument::new(scenery);
+    doc.add_analyzer(AnalyzerType::RayTrace(RayTraceConfig::default()));
+    doc.save_to_file(Path::new("./opossum/playground/parabolic_mirror.opm"))
+}
diff --git a/opossum/src/main.rs b/opossum/src/main.rs
index d92ab1d5..f05a81d4 100644
--- a/opossum/src/main.rs
+++ b/opossum/src/main.rs
@@ -110,11 +110,11 @@ fn opossum() -> OpmResult<()> {
     let analyzers = document.analyzers();
     let scenery = document.scenery_mut();
     //create the dot file of the scenery
+    create_data_dir(&opossum_args.report_directory)?;
     create_dot_file(&opossum_args.report_directory, scenery)?;
     if analyzers.is_empty() {
         info!("No analyzer defined in document. Stopping here.");
     } else {
-        create_data_dir(&opossum_args.report_directory)?;
         for ana in analyzers.iter().enumerate() {
             let analyzer: &dyn Analyzer = match ana.1 {
                 AnalyzerType::Energy => &EnergyAnalyzer::default(),
diff --git a/opossum/src/nodes/mod.rs b/opossum/src/nodes/mod.rs
index f5811860..f463f05b 100644
--- a/opossum/src/nodes/mod.rs
+++ b/opossum/src/nodes/mod.rs
@@ -10,6 +10,7 @@ mod ideal_filter;
 mod lens;
 mod node_attr;
 mod node_group;
+mod parabolic_mirror;
 mod paraxial_surface;
 mod reference;
 mod source;
@@ -34,6 +35,7 @@ pub use ideal_filter::{FilterType, IdealFilter};
 pub use lens::Lens;
 pub use node_attr::NodeAttr;
 pub use node_group::{NodeGroup, OpticGraph};
+pub use parabolic_mirror::ParabolicMirror;
 pub use paraxial_surface::ParaxialSurface;
 pub use ray_propagation_visualizer::RayPropagationVisualizer;
 pub use reference::NodeReference;
@@ -62,6 +64,7 @@ use uuid::Uuid;
 /// # Errors
 ///
 /// This function will return an [`OpossumError`] if there is no node with the given type.
+#[allow(clippy::too_many_lines)]
 pub fn create_node_ref(node_type: &str, uuid: Option<Uuid>) -> OpmResult<OpticRef> {
     match node_type {
         "dummy" => Ok(OpticRef::new(
@@ -159,6 +162,11 @@ pub fn create_node_ref(node_type: &str, uuid: Option<Uuid>) -> OpmResult<OpticRe
             uuid,
             None,
         )),
+        "parabolic mirror" => Ok(OpticRef::new(
+            Rc::new(RefCell::new(ParabolicMirror::default())),
+            uuid,
+            None,
+        )),
         _ => Err(OpossumError::Other(format!(
             "cannot create node type <{node_type}>"
         ))),
diff --git a/opossum/src/nodes/node_group/mod.rs b/opossum/src/nodes/node_group/mod.rs
index e159827a..0ba27cc7 100644
--- a/opossum/src/nodes/node_group/mod.rs
+++ b/opossum/src/nodes/node_group/mod.rs
@@ -29,9 +29,9 @@ use std::{
     cell::RefCell,
     collections::{BTreeMap, HashMap},
     io::Write,
+    process::Stdio,
     rc::Rc,
 };
-use tempfile::NamedTempFile;
 use uom::si::f64::Length;
 use uuid::Uuid;
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -396,17 +396,29 @@ impl NodeGroup {
     /// This function will return an error if the image generation fails (e.g. program not found, no memory left etc.).
     pub fn toplevel_dot_svg(&self) -> OpmResult<String> {
         let dot_string = self.toplevel_dot("")?;
-        let mut f = NamedTempFile::new()
-            .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
-        f.write_all(dot_string.as_bytes())
-            .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
-        let r = std::process::Command::new("dot")
-            .arg(f.path())
+        let mut child = std::process::Command::new("dot")
             .arg("-Tsvg:cairo")
             .arg("-Kdot")
-            .output()
+            .stdin(Stdio::piped())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::null())
+            .spawn()
+            .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
+
+        let Some(child_stdin) = child.stdin.as_mut() else {
+            return Err(OpossumError::Other(
+                "conversion to image failed: could not set stdin for graphviz command".into(),
+            ));
+        };
+        child_stdin
+            .write_all(dot_string.as_bytes())
             .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
-        let svg_string = String::from_utf8(r.stdout)
+
+        let output = child
+            .wait_with_output()
+            .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
+
+        let svg_string = String::from_utf8(output.stdout)
             .map_err(|e| OpossumError::Other(format!("conversion to image failed: {e}")))?;
         Ok(svg_string)
     }
diff --git a/opossum/src/nodes/parabolic_mirror.rs b/opossum/src/nodes/parabolic_mirror.rs
new file mode 100644
index 00000000..915b2679
--- /dev/null
+++ b/opossum/src/nodes/parabolic_mirror.rs
@@ -0,0 +1,231 @@
+use nalgebra::Point2;
+use uom::si::f64::{Angle, Length};
+
+use crate::{
+    analyzers::{
+        energy::AnalysisEnergy, ghostfocus::AnalysisGhostFocus, raytrace::AnalysisRayTrace,
+        Analyzable, RayTraceConfig,
+    },
+    coatings::CoatingType,
+    degree,
+    dottable::Dottable,
+    error::{OpmResult, OpossumError},
+    light_result::LightResult,
+    lightdata::LightData,
+    meter,
+    optic_node::{Alignable, OpticNode, LIDT},
+    optic_ports::{OpticPorts, PortType},
+    properties::Proptype,
+    surface::{OpticalSurface, Parabola},
+    utils::geom_transformation::Isometry,
+};
+
+use super::NodeAttr;
+
+#[derive(Debug, Clone)]
+/// An infinitely thin mirror with a spherical (or flat) surface.
+///
+///
+/// ## Optical Ports
+///   - Inputs
+///     - `input`
+///   - Outputs
+///     - `reflected`
+///
+/// ## Properties
+///   - `name`
+///   - `inverted`
+///   - `curvature`
+pub struct ParabolicMirror {
+    node_attr: NodeAttr,
+    surface: OpticalSurface,
+}
+impl Default for ParabolicMirror {
+    /// Create a parabolic mirror with a focal length of 1 meter.
+    fn default() -> Self {
+        let mut node_attr = NodeAttr::new("parabolic mirror");
+        node_attr
+            .create_property("focal length", "focal length", None, meter!(-1.0).into())
+            .unwrap();
+        node_attr
+            .create_property(
+                "oap angle x",
+                "off axis angle around local x axis",
+                None,
+                degree!(0.0).into(),
+            )
+            .unwrap();
+        node_attr
+            .create_property(
+                "oap angle y",
+                "off axis angle around local y axis",
+                None,
+                degree!(0.0).into(),
+            )
+            .unwrap();
+        let mut ports = OpticPorts::new();
+        ports.add(&PortType::Input, "input").unwrap();
+        ports
+            .set_coating(
+                &PortType::Input,
+                "input",
+                &CoatingType::ConstantR { reflectivity: 1.0 },
+            )
+            .unwrap();
+        ports.add(&PortType::Output, "reflected").unwrap();
+        node_attr.set_ports(ports);
+
+        Self {
+            node_attr,
+            surface: OpticalSurface::new(Box::new(
+                Parabola::new(meter!(-1.0), &Isometry::identity()).unwrap(),
+            )),
+        }
+    }
+}
+impl ParabolicMirror {
+    /// Creates a new [`ParabolicMirror`] node.
+    ///
+    /// This function creates a infinitely thin parabolic mirror with a given focal length.
+    ///
+    /// # Errors
+    ///
+    /// This function returns an error if the given focal length is zero or not finite.
+    pub fn new(name: &str, focal_length: Length) -> OpmResult<Self> {
+        if !focal_length.is_normal() {
+            return Err(OpossumError::Other(
+                "focal length must not be 0.0 and finite".into(),
+            ));
+        }
+        let mut parabola = Self::default();
+        parabola.node_attr.set_name(name);
+        parabola
+            .node_attr
+            .set_property("focal length", focal_length.into())?;
+        parabola.update_surfaces()?;
+        Ok(parabola)
+    }
+    /// Returns / modifies a [`ParabolicMirror`] with given off-axis angles.
+    ///
+    /// The angles define the off axis angles around the local x and y axis of the node. The given angles denote the full
+    /// angle between an incoming and a reflected beam. Effectively this introduces a decentering
+    /// of the node during positioning in 3D space such that the desired angles are met.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if the node properties cannot be set.
+    pub fn with_oap_angles(mut self, angles: Point2<Angle>) -> OpmResult<Self> {
+        self.set_property("oap angle x", angles[0].into())?;
+        self.set_property("oap angle y", angles[1].into())?;
+        self.update_surfaces()?;
+        Ok(self)
+    }
+}
+impl OpticNode for ParabolicMirror {
+    fn node_attr(&self) -> &NodeAttr {
+        &self.node_attr
+    }
+    fn node_attr_mut(&mut self) -> &mut NodeAttr {
+        &mut self.node_attr
+    }
+    fn update_surfaces(&mut self) -> OpmResult<()> {
+        let Ok(Proptype::Length(focal_length)) = self.node_attr.get_property("focal length") else {
+            return Err(OpossumError::Analysis("cannot read focal length".into()));
+        };
+        let Ok(Proptype::Angle(oap_angle_x)) = self.node_attr.get_property("oap angle x") else {
+            return Err(OpossumError::Analysis(
+                "cannot read off axis angle x".into(),
+            ));
+        };
+        let Ok(Proptype::Angle(oap_angle_y)) = self.node_attr.get_property("oap angle y") else {
+            return Err(OpossumError::Analysis(
+                "cannot read off axis angle y".into(),
+            ));
+        };
+        let mut parabola = Parabola::new(*focal_length, &Isometry::identity())?;
+        parabola.set_off_axis_angles((*oap_angle_x, *oap_angle_y));
+        self.surface = OpticalSurface::new(Box::new(parabola));
+        Ok(())
+    }
+    fn get_surface_mut(&mut self, _surf_name: &str) -> &mut OpticalSurface {
+        &mut self.surface
+    }
+}
+impl Alignable for ParabolicMirror {}
+impl Dottable for ParabolicMirror {
+    fn node_color(&self) -> &str {
+        "chocolate2"
+    }
+}
+impl LIDT for ParabolicMirror {}
+impl Analyzable for ParabolicMirror {}
+impl AnalysisGhostFocus for ParabolicMirror {}
+impl AnalysisEnergy for ParabolicMirror {
+    fn analyze(&mut self, incoming_data: LightResult) -> OpmResult<LightResult> {
+        let (inport, outport) = if self.inverted() {
+            ("reflected", "input")
+        } else {
+            ("input", "reflected")
+        };
+        let Some(data) = incoming_data.get(inport) else {
+            return Ok(LightResult::default());
+        };
+        Ok(LightResult::from([(outport.into(), data.clone())]))
+    }
+}
+impl AnalysisRayTrace for ParabolicMirror {
+    fn analyze(
+        &mut self,
+        incoming_data: LightResult,
+        config: &RayTraceConfig,
+    ) -> OpmResult<LightResult> {
+        let (inport, outport) = if self.inverted() {
+            ("reflected", "input")
+        } else {
+            ("input", "reflected")
+        };
+        let Some(data) = incoming_data.get(inport) else {
+            return Ok(LightResult::default());
+        };
+        if let LightData::Geometric(mut rays) = data.clone() {
+            let reflected = if let Some(iso) = self.effective_iso() {
+                let coating = self
+                    .node_attr()
+                    .ports()
+                    .coating(&PortType::Input, "input")
+                    .unwrap()
+                    .clone();
+                let surface = self.get_surface_mut("");
+                surface.set_isometry(&iso);
+                surface.set_coating(coating);
+                let mut reflected_rays = rays.refract_on_surface(surface, None)?;
+                if let Some(aperture) = self.ports().aperture(&PortType::Input, inport) {
+                    reflected_rays.apodize(aperture, &iso)?;
+                    reflected_rays.invalidate_by_threshold_energy(config.min_energy_per_ray())?;
+                    reflected_rays
+                } else {
+                    return Err(OpossumError::OpticPort("input aperture not found".into()));
+                }
+            } else {
+                return Err(OpossumError::Analysis(
+                    "no location for surface defined. Aborting".into(),
+                ));
+            };
+            let light_data = LightData::Geometric(reflected);
+            let light_result = LightResult::from([(outport.into(), light_data)]);
+            Ok(light_result)
+        } else {
+            Err(OpossumError::Analysis(
+                "expected ray data at input port".into(),
+            ))
+        }
+    }
+
+    fn calc_node_position(
+        &mut self,
+        incoming_data: LightResult,
+        config: &RayTraceConfig,
+    ) -> OpmResult<LightResult> {
+        AnalysisRayTrace::analyze(self, incoming_data, config)
+    }
+}
diff --git a/opossum/src/surface/mod.rs b/opossum/src/surface/mod.rs
index 5377bd20..5caf321a 100644
--- a/opossum/src/surface/mod.rs
+++ b/opossum/src/surface/mod.rs
@@ -4,6 +4,7 @@
 //! This module handles only the geometric aspect of an optical surface. So a [`GeoSurface`] has no [`Coating`](crate::coatings::Coating) or
 //! [`Aperture`](crate::aperture::Aperture)s.
 mod cylinder;
+mod parabola;
 mod plane;
 mod sphere;
 
@@ -11,6 +12,7 @@ pub mod hit_map;
 mod optical_surface;
 pub use cylinder::Cylinder;
 pub use optical_surface::OpticalSurface;
+pub use parabola::Parabola;
 pub use plane::Plane;
 pub use sphere::Sphere;
 
diff --git a/opossum/src/surface/parabola.rs b/opossum/src/surface/parabola.rs
new file mode 100644
index 00000000..1df78a7c
--- /dev/null
+++ b/opossum/src/surface/parabola.rs
@@ -0,0 +1,209 @@
+//! Parabolic surface
+//!
+//! This module implements a parabolic surface with a given focal length and a given z position on the optical axis.
+
+use crate::{
+    degree,
+    error::{OpmResult, OpossumError},
+    meter, radian,
+    utils::geom_transformation::Isometry,
+};
+use nalgebra::{vector, Point3, Vector3};
+use num::Zero;
+use roots::{find_roots_quadratic, Roots};
+use uom::si::{
+    f64::{Angle, Length, Ratio},
+    ratio::basis_point,
+};
+
+use super::GeoSurface;
+
+#[derive(Debug, Clone)]
+/// A spherical surface with its anchor point on the optical axis.
+pub struct Parabola {
+    focal_length: Length,
+    isometry: Isometry,
+    off_axis_angles: (Angle, Angle),
+}
+
+impl Parabola {
+    /// Create a new [`Parabola`] located and oriented by the given [`Isometry`].
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if the focal length is not normal.
+    pub fn new(focal_length: Length, isometry: &Isometry) -> OpmResult<Self> {
+        if !focal_length.is_normal() {
+            return Err(OpossumError::Other(
+                "focal length must be != 0.0 and finite".into(),
+            ));
+        }
+        let anchor_isometry = Isometry::new(
+            Point3::new(Length::zero(), Length::zero(), focal_length),
+            radian!(0., 0., 0.),
+        )?;
+        let isometry = isometry.append(&anchor_isometry);
+        Ok(Self {
+            focal_length,
+            isometry,
+            off_axis_angles: (Angle::zero(), Angle::zero()),
+        })
+    }
+    /// Sets the off-axis angles (full refelction) of this [`Parabola`].
+    pub fn set_off_axis_angles(&mut self, off_axis_angles: (Angle, Angle)) {
+        //  factor 2. because of full reflection angle <-> angle of incidence
+        self.off_axis_angles = (off_axis_angles.0 / 2., off_axis_angles.1 / 2.);
+    }
+    /// Returns the off axis angles of this [`Parabola`].
+    #[must_use]
+    pub fn off_axis_angles(&self) -> (Angle, Angle) {
+        self.off_axis_angles
+    }
+    fn calc_oap_decenter(&self) -> (Length, Length) {
+        let f_x =
+            2. * self.focal_length / (Ratio::new::<basis_point>(1.) + self.off_axis_angles.0.cos());
+        let f_y =
+            2. * self.focal_length / (Ratio::new::<basis_point>(1.) + self.off_axis_angles.0.cos());
+        let oad_x = f_y * (self.off_axis_angles.1.sin());
+        let oad_y = f_x * (self.off_axis_angles.0.sin());
+        (oad_x, oad_y)
+    }
+}
+
+impl GeoSurface for Parabola {
+    fn calc_intersect_and_normal_do(
+        &self,
+        ray: &crate::ray::Ray,
+    ) -> Option<(Point3<Length>, Vector3<f64>)> {
+        let dir = ray.direction();
+        let pos = vector![
+            ray.position().x.value,
+            ray.position().y.value,
+            ray.position().z.value
+        ];
+        let f_length = self.focal_length.value;
+        let is_back_propagating = dir.z.is_sign_negative();
+        // parabola formula (at origin)
+        // x^2 + y^2 - 4fz = 0
+        //
+        // insert ray (p: position, d: direction):
+        // (p_x+t*d_x)^2 + (p_y+t*d_y)^2 - 4f*(p_z+t*d_z) = 0
+        // This translates into the qudratic equation
+        // at^2 + bt + c = 0 with
+        // a = d_x^2+d_y^2
+        // b = 2* (p_x*d_x + p_y*d_y - 2*f*d_z)
+        // c = p_x^2 + p_y^2 - 4f*p_z
+        let a = dir.x.mul_add(dir.x, dir.y * dir.y);
+        let b = 2. * (2. * f_length).mul_add(-dir.z, pos.x.mul_add(dir.x, pos.y * dir.y));
+        let c = (4. * f_length).mul_add(-pos.z, pos.x.mul_add(pos.x, pos.y * pos.y));
+        // Solve t of qudaratic equation
+        let roots = find_roots_quadratic(a, b, c);
+        let intersection_point = match roots {
+            // no intersection
+            Roots::No(_) => return None,
+            // "just touching" intersection
+            Roots::One(t) => {
+                if t[0] >= 0.0 {
+                    pos + t[0] * dir
+                } else {
+                    return None;
+                }
+            }
+            // "regular" intersection
+            Roots::Two(t) => {
+                let real_t = if self.focal_length.is_sign_positive() {
+                    // convex surface => use min t
+                    if is_back_propagating {
+                        f64::max(t[0], t[1])
+                    } else {
+                        f64::min(t[0], t[1])
+                    }
+                } else {
+                    // concave surface => use max t
+                    if is_back_propagating {
+                        f64::min(t[0], t[1])
+                    } else {
+                        f64::max(t[0], t[1])
+                    }
+                };
+                if real_t.is_sign_negative() {
+                    // surface behind beam
+                    return None;
+                }
+                pos + real_t * dir
+            }
+            _ => unreachable!(),
+        };
+        // calc surface normal
+        // calculate grad F(x,y,z) =(2* p_x, 2* p_y, -4 * f)
+        let normal_vector = vector![
+            2. * intersection_point.x,
+            2. * intersection_point.y,
+            -4. * f_length
+        ];
+        Some((
+            meter!(
+                intersection_point.x,
+                intersection_point.y,
+                intersection_point.z
+            ),
+            -1.0 * normal_vector,
+        ))
+    }
+    fn isometry(&self) -> &Isometry {
+        &self.isometry
+    }
+    fn set_isometry(&mut self, isometry: &Isometry) {
+        let oap_decenter = self.calc_oap_decenter();
+        let oap_iso = Isometry::new(
+            Point3::new(oap_decenter.0, oap_decenter.1, Length::zero()),
+            degree!(0.0, 0.0, 0.0),
+        )
+        .unwrap();
+        let total_iso = isometry.append(&oap_iso);
+        self.isometry = total_iso;
+    }
+    fn box_clone(&self) -> Box<dyn GeoSurface> {
+        Box::new(self.clone())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use nalgebra::vector;
+
+    use super::Parabola;
+    use crate::{
+        degree, joule, meter, millimeter, nanometer, ray::Ray, surface::GeoSurface,
+        utils::geom_transformation::Isometry,
+    };
+
+    #[test]
+    fn intersect() {
+        let parabola = Parabola::new(meter!(-1.0), &Isometry::identity()).unwrap();
+        let ray = Ray::new_collimated(meter!(-1.0, -1.0, -10.0), nanometer!(1000.0), joule!(1.0))
+            .unwrap();
+        let intersection = parabola.calc_intersect_and_normal_do(&ray).unwrap();
+        assert_eq!(intersection.0, meter!(-1.0, -1.0, -0.5));
+        assert_eq!(intersection.1, vector![2.0, 2.0, -4.]);
+    }
+    #[test]
+    fn intersect_ray_through_focus() {
+        let parabola = Parabola::new(meter!(-1.0), &Isometry::identity()).unwrap();
+        let direction = vector![0.0, 1.0, 1. - 0.25];
+        let ray = Ray::new(
+            meter!(0.0, 0.0, -1.0),
+            direction,
+            nanometer!(1000.0),
+            joule!(1.0),
+        )
+        .unwrap();
+        dbg!(parabola.calc_intersect_and_normal_do(&ray));
+    }
+    #[test]
+    fn off_axis_decenter() {
+        let mut parabola = Parabola::new(millimeter!(-50.0), &Isometry::identity()).unwrap();
+        parabola.set_off_axis_angles((degree!(22.5), degree!(0.0)));
+        dbg!(parabola.calc_oap_decenter());
+    }
+}
diff --git a/opossum/src/surface/sphere.rs b/opossum/src/surface/sphere.rs
index eb148c59..0895d0ec 100644
--- a/opossum/src/surface/sphere.rs
+++ b/opossum/src/surface/sphere.rs
@@ -62,11 +62,6 @@ impl Sphere {
         let isometry = isometry.append(&anchor_isometry);
         Ok(Self { radius, isometry })
     }
-    /// Returns the center position of this [`Sphere`]
-    #[must_use]
-    pub fn get_pos(&self) -> Point3<Length> {
-        self.isometry.transform_point(&Point3::origin())
-    }
 }
 impl GeoSurface for Sphere {
     fn calc_intersect_and_normal_do(&self, ray: &Ray) -> Option<(Point3<Length>, Vector3<f64>)> {
@@ -194,11 +189,9 @@ mod test {
 
         let s = Sphere::new(millimeter!(2.0), &iso).unwrap();
         assert_eq!(s.radius, millimeter!(2.0));
-        assert_eq!(s.get_pos(), millimeter!(0.0, 0.0, 3.0));
 
         let s = Sphere::new(millimeter!(-2.0), &iso).unwrap();
         assert_eq!(s.radius, millimeter!(-2.0));
-        assert_eq!(s.get_pos(), millimeter!(0.0, 0.0, -1.0));
     }
     #[test]
     fn new_at_position() {
@@ -229,7 +222,6 @@ mod test {
 
         let s = Sphere::new_at_position(millimeter!(2.0), millimeter!(1.0, 2.0, 3.0)).unwrap();
         assert_eq!(s.radius, millimeter!(2.0));
-        assert_eq!(s.get_pos(), millimeter!(1.0, 2.0, 5.0));
     }
     #[test]
     fn intersect_positive_on_axis_forward() {
diff --git a/opossum/test b/opossum/test
new file mode 100644
index 0000000000000000000000000000000000000000..da1b18e37b125fd2530d4db80b5c2f25bf1a1a15
GIT binary patch
literal 116972
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

literal 0
HcmV?d00001

-- 
GitLab