Compare commits

...

17 Commits

Author SHA1 Message Date
462c90e8c8 matrices: benchmark Matrix::inverse & inverse_old
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-05 16:44:06 -07:00
ac3a18a864 matrices: implement Matrix4x4::inverse. 2021-07-05 16:26:27 -07:00
656f1c3a94 Small whitespace change. 2021-07-05 16:26:21 -07:00
762cd45f63 implement determinant on 3x3 and 4x4 matrices 2021-07-05 15:27:32 -07:00
d6ad12e344 implement cofactor of 3x3 matrix 2021-07-05 15:07:18 -07:00
f5d79908f6 implement minor for matrix3x3 2021-07-01 21:29:58 -07:00
a69e404817 implement submatrix for matrix4x4 2021-07-01 21:20:40 -07:00
43d95041af implement submatrix for matrix3x3 2021-07-01 21:12:43 -07:00
c97bc25323 Implement 2x2 determinant 2021-07-01 20:54:00 -07:00
dda29eb836 test identity transpose 2021-07-01 20:47:29 -07:00
fa5971faa4 Test identity multiplication 2021-07-01 20:46:05 -07:00
4d649c735b Implement 4x4 * 4x1 2021-06-30 21:02:42 -07:00
3d2d763a3b test 4x4 multiplication 2021-06-29 20:56:08 -07:00
ea6114b9ae test 4x4 equality and inequality 2021-06-29 20:51:38 -07:00
ec0331b88b constructor/index methods for Matrix2x2 and Matrix3x3 2021-06-29 20:48:41 -07:00
72b15e5516 Copy Matrix4x4 impl from pbrt and start on tests 2021-06-29 20:12:23 -07:00
3cf580f607 Fix tests after recent refactor 2021-06-29 19:52:57 -07:00
8 changed files with 1388 additions and 66 deletions

537
rtchallenge/Cargo.lock generated
View File

@ -12,6 +12,17 @@ version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -24,18 +35,56 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bstr"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cast"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
dependencies = [
"rustc_version",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.2.1" version = "1.2.1"
@ -45,6 +94,108 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "criterion"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools 0.10.1",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
dependencies = [
"cast",
"itertools 0.9.0",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "deflate" name = "deflate"
version = "0.8.6" version = "0.8.6"
@ -55,6 +206,12 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "float-cmp" name = "float-cmp"
version = "0.8.0" version = "0.8.0"
@ -64,6 +221,90 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "half"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.3.7" version = "0.3.7"
@ -82,6 +323,50 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "plotters"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c"
[[package]]
name = "plotters-svg"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9"
dependencies = [
"plotters-backend",
]
[[package]] [[package]]
name = "png" name = "png"
version = "0.16.8" version = "0.16.8"
@ -112,16 +397,137 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "rtchallenge" name = "rtchallenge"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"criterion",
"float-cmp", "float-cmp",
"png", "png",
"thiserror", "thiserror",
] ]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
[[package]]
name = "serde_cbor"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.73" version = "1.0.73"
@ -133,6 +539,15 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.25" version = "1.0.25"
@ -153,8 +568,130 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
[[package]]
name = "web-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -8,6 +8,12 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0.41" anyhow = "1.0.41"
criterion = "0.3.4"
float-cmp = "0.8.0" float-cmp = "0.8.0"
png = "0.16.8" png = "0.16.8"
thiserror = "1.0.25" thiserror = "1.0.25"
[[bench]]
name = "matrices"
harness = false

View File

@ -0,0 +1,13 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rtchallenge::matrices::Matrix4x4;
fn criterion_benchmark(c: &mut Criterion) {
let mut i = Matrix4x4::identity();
c.bench_function("inverse_new", |b| b.iter(|| i = i.inverse()));
let mut i = Matrix4x4::identity();
c.bench_function("inverse_old", |b| b.iter(|| i = i.inverse_old()));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -1,4 +1,4 @@
use rtchallenge::tuples::{point, vector, Tuple}; use rtchallenge::tuples::Tuple;
#[derive(Debug)] #[derive(Debug)]
struct Environment { struct Environment {
@ -18,12 +18,12 @@ fn tick(env: &Environment, proj: &Projectile) -> Projectile {
} }
fn main() { fn main() {
let mut p = Projectile { let mut p = Projectile {
position: point(0., 1., 0.), position: Tuple::point(0., 1., 0.),
velocity: 4. * vector(1., 1., 0.).normalize(), velocity: 4. * Tuple::vector(1., 1., 0.).normalize(),
}; };
let e = Environment { let e = Environment {
gravity: vector(0., -0.1, 0.).normalize(), gravity: Tuple::vector(0., -0.1, 0.).normalize(),
wind: vector(-0.01, 0., 0.), wind: Tuple::vector(-0.01, 0., 0.),
}; };
let mut i = 0; let mut i = 0;

View File

@ -72,13 +72,13 @@ impl Canvas {
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::canvas; use super::Canvas;
use crate::tuples::Color; use crate::tuples::Color;
#[test] #[test]
fn create_canvas() { fn create_canvas() {
let c = canvas(10, 20); let c = Canvas::new(10, 20);
assert_eq!(c.width, 10); assert_eq!(c.width, 10);
assert_eq!(c.height, 20); assert_eq!(c.height, 20);
let black = Color::new(0., 0., 0.); let black = Color::new(0., 0., 0.);
@ -89,7 +89,7 @@ mod tests {
#[test] #[test]
fn write_to_canvas() { fn write_to_canvas() {
let mut c = canvas(10, 20); let mut c = Canvas::new(10, 20);
let red = Color::new(1., 0., 0.); let red = Color::new(1., 0., 0.);
c.set(2, 3, red); c.set(2, 3, red);
assert_eq!(c.get(2, 3), red); assert_eq!(c.get(2, 3), red);

View File

@ -1,2 +1,3 @@
pub mod canvas; pub mod canvas;
pub mod matrices;
pub mod tuples; pub mod tuples;

761
rtchallenge/src/matrices.rs Normal file
View File

@ -0,0 +1,761 @@
use std::fmt;
use std::ops::{Index, IndexMut, Mul};
use float_cmp::{ApproxEq, F32Margin};
use crate::tuples::Tuple;
#[derive(Default, Clone, Copy)]
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to m<sub>i,j</sub>
/// where `i` is the row number and `j` is the column number.
pub struct Matrix4x4 {
m: [[f32; 4]; 4],
}
#[derive(Debug, PartialEq)]
pub struct Matrix2x2 {
m: [[f32; 2]; 2],
}
impl Matrix2x2 {
/// Create a `Matrix2x2` with each of the given rows.
pub fn new(r0: [f32; 2], r1: [f32; 2]) -> Matrix2x2 {
Matrix2x2 { m: [r0, r1] }
}
/// Calculate the determinant of a 2x2.
///
/// # Examples
///
/// ```
/// use rtchallenge::matrices::Matrix2x2;
///
/// let a = Matrix2x2::new([1., 5.], [-3., 2.]);
///
/// assert_eq!(a.determinant(), 17.);
/// ```
pub fn determinant(&self) -> f32 {
let m = self;
m[(0, 0)] * m[(1, 1)] - m[(0, 1)] * m[(1, 0)]
}
}
impl Index<(usize, usize)> for Matrix2x2 {
type Output = f32;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
#[derive(Debug, PartialEq)]
pub struct Matrix3x3 {
m: [[f32; 3]; 3],
}
impl Matrix3x3 {
/// Create a `Matrix3x2` with each of the given rows.
pub fn new(r0: [f32; 3], r1: [f32; 3], r2: [f32; 3]) -> Matrix3x3 {
Matrix3x3 { m: [r0, r1, r2] }
}
/// submatrix extracts a 2x2 matrix ignoring the 0-based `row` and `col` given.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{Matrix2x2, Matrix3x3};
///
/// assert_eq!(
/// Matrix3x3::new([1., 5., 0.], [-3., 2., 7.], [0., 6., -3.],).submatrix(0, 2),
/// Matrix2x2::new([-3., 2.], [0., 6.])
/// );
/// ```
pub fn submatrix(&self, row: usize, col: usize) -> Matrix2x2 {
assert!(row < 3);
assert!(col < 3);
let mut rows = vec![];
for r in 0..3 {
if r != row {
let mut v = vec![];
for c in 0..3 {
if c != col {
v.push(self[(r, c)]);
}
}
rows.push(v);
}
}
let m = [[rows[0][0], rows[0][1]], [rows[1][0], rows[1][1]]];
Matrix2x2 { m }
}
/// Compute minor of a 3x3 matrix.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix3x3;
///
/// let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
/// let b = a.submatrix(1, 0);
/// assert_eq!(b.determinant(), 25.0);
/// assert_eq!(b.determinant(), a.minor(1, 0));
/// ```
pub fn minor(&self, row: usize, col: usize) -> f32 {
self.submatrix(row, col).determinant()
}
/// Compute cofactor of a 3x3 matrix.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix3x3;
///
/// let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
/// assert_eq!(a.minor(0, 0), -12.);
/// assert_eq!(a.cofactor(0, 0), -12.);
/// assert_eq!(a.minor(1, 0), 25.);
/// assert_eq!(a.cofactor(1, 0), -25.);
/// ```
pub fn cofactor(&self, row: usize, col: usize) -> f32 {
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
self.submatrix(row, col).determinant() * negate
}
/// Compute determinant of a 3x3 matrix.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix3x3;
///
/// let a = Matrix3x3::new([1., 2., 6.], [-5., 8., -4.], [2., 6., 4.]);
/// assert_eq!(a.cofactor(0, 0), 56.);
/// assert_eq!(a.cofactor(0, 1), 12.);
/// assert_eq!(a.cofactor(0, 2), -46.);
/// assert_eq!(a.determinant(), -196.);
/// ```
pub fn determinant(&self) -> f32 {
(0..3).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
}
}
impl Index<(usize, usize)> for Matrix3x3 {
type Output = f32;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
impl From<[f32; 16]> for Matrix4x4 {
fn from(t: [f32; 16]) -> Self {
Matrix4x4 {
m: [
[t[0], t[1], t[2], t[3]],
[t[4], t[5], t[6], t[7]],
[t[8], t[9], t[10], t[11]],
[t[12], t[13], t[14], t[15]],
],
}
}
}
impl<'a> ApproxEq for &'a Matrix4x4 {
type Margin = F32Margin;
/// Implement float_cmp::ApproxEq for Matrix4x4
///
/// # Examples
/// ```
/// use float_cmp::approx_eq;
/// use rtchallenge::matrices::Matrix4x4;
///
/// assert!(!approx_eq!(
/// &Matrix4x4,
/// &Matrix4x4::default(),
/// &Matrix4x4::identity(),
/// ulps = 1
/// ));
///
/// assert!(approx_eq!(
/// &Matrix4x4,
/// &Matrix4x4::identity(),
/// &Matrix4x4::identity(),
/// ulps = 1
/// ));
/// ```
fn approx_eq<T: Into<Self::Margin>>(self, m2: Self, margin: T) -> bool {
let m = self;
let margin = margin.into();
for row in 0..4 {
for col in 0..4 {
if !m[(row, col)].approx_eq(m2[(row, col)], margin) {
return false;
}
}
}
true
}
}
impl Matrix4x4 {
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
/// # Examples
///
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let a = Matrix4x4::new(
/// [0., 1., 2., 3.],
/// [1., 2., 4., 8.],
/// [2., 4., 8., 16.],
/// [4., 8., 16., 32.],
/// );
/// let i = Matrix4x4::identity();
///
/// assert_eq!(a * i, a);
/// ```
pub fn identity() -> Matrix4x4 {
Matrix4x4::new(
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
)
}
/// Create a `Matrix4x4` with each of the given rows.
pub fn new(r0: [f32; 4], r1: [f32; 4], r2: [f32; 4], r3: [f32; 4]) -> Matrix4x4 {
Matrix4x4 {
m: [r0, r1, r2, r3],
}
}
/// Transpose self, returning a new matrix that has been reflected across the diagonal.
/// # Examples
///
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let m = Matrix4x4::new(
/// [2., 0., 0., 0.],
/// [3., 1., 0., 0.],
/// [4., 0., 1., 0.],
/// [5., 6., 7., 1.],
/// );
/// let m_t = Matrix4x4::new(
/// [2., 3., 4., 5.],
/// [0., 1., 0., 6.],
/// [0., 0., 1., 7.],
/// [0., 0., 0., 1.],
/// );
/// assert_eq!(m.transpose(), m_t);
///
/// assert_eq!(Matrix4x4::identity(), Matrix4x4::identity().transpose());
pub fn transpose(&self) -> Matrix4x4 {
let m = self.m;
Matrix4x4 {
m: [
[m[0][0], m[1][0], m[2][0], m[3][0]],
[m[0][1], m[1][1], m[2][1], m[3][1]],
[m[0][2], m[1][2], m[2][2], m[3][2]],
[m[0][3], m[1][3], m[2][3], m[3][3]],
],
}
}
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A<sup>-1</sup>, where
/// AA<sup>-1</sup> = I.
/// This implementation uses a numerically stable GaussJordan elimination routine to compute the inverse.
///
/// # Examples
///
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let i = Matrix4x4::identity();
/// assert_eq!(i.inverse_old() * i, i);
///
/// let m = Matrix4x4::new(
/// [2., 0., 0., 0.],
/// [0., 3., 0., 0.],
/// [0., 0., 4., 0.],
/// [0., 0., 0., 1.],
/// );
/// assert_eq!(m.inverse_old() * m, i);
/// assert_eq!(m * m.inverse_old(), i);
/// ```
pub fn inverse_old(&self) -> Matrix4x4 {
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
// matrix.
let mut indxc: [usize; 4] = Default::default();
let mut indxr: [usize; 4] = Default::default();
let mut ipiv: [usize; 4] = Default::default();
let mut minv = self.m;
for i in 0..4 {
let mut irow: usize = 0;
let mut icol: usize = 0;
let mut big: f32 = 0.;
// Choose pivot
for j in 0..4 {
if ipiv[j] != 1 {
for (k, ipivk) in ipiv.iter().enumerate() {
if *ipivk == 0 {
if minv[j][k].abs() >= big {
big = minv[j][k].abs();
irow = j;
icol = k;
}
} else if *ipivk > 1 {
eprintln!("Singular matrix in MatrixInvert");
}
}
}
}
ipiv[icol] += 1;
// Swap rows _irow_ and _icol_ for pivot
if irow != icol {
// Can't figure out how to make swap work here.
#[allow(clippy::manual_swap)]
for k in 0..4 {
let tmp = minv[irow][k];
minv[irow][k] = minv[icol][k];
minv[icol][k] = tmp;
}
}
indxr[i] = irow;
indxc[i] = icol;
if minv[icol][icol] == 0. {
eprintln!("Singular matrix in MatrixInvert");
}
// Set $m[icol][icol]$ to one by scaling row _icol_ appropriately
let pivinv: f32 = minv[icol][icol].recip();
minv[icol][icol] = 1.;
for j in 0..4 {
minv[icol][j] *= pivinv;
}
// Subtract this row from others to zero out their columns
for j in 0..4 {
if j != icol {
let save = minv[j][icol];
minv[j][icol] = 0.;
for k in 0..4 {
minv[j][k] -= minv[icol][k] * save;
}
}
}
}
// Swap columns to reflect permutation
for j in (0..4).rev() {
if indxr[j] != indxc[j] {
for mi in &mut minv {
mi.swap(indxr[j], indxc[j])
}
}
}
Matrix4x4 { m: minv }
}
/// submatrix extracts a 3x3 matrix ignoring the 0-based `row` and `col` given.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::{Matrix3x3, Matrix4x4};
///
/// assert_eq!(
/// Matrix4x4::new(
/// [-6., 1., 1., 6.],
/// [-8., 5., 8., 6.],
/// [-1., 0., 8., 2.],
/// [-7., 1., -1., 1.],
/// )
/// .submatrix(2, 1),
/// Matrix3x3::new([-6., 1., 6.], [-8., 8., 6.], [-7., -1., 1.],)
/// );
/// ```
pub fn submatrix(&self, row: usize, col: usize) -> Matrix3x3 {
assert!(row < 4);
assert!(col < 4);
let mut rows = vec![];
for r in 0..4 {
if r != row {
let mut v = vec![];
for c in 0..4 {
if c != col {
v.push(self[(r, c)]);
}
}
rows.push(v);
}
}
let m = [
[rows[0][0], rows[0][1], rows[0][2]],
[rows[1][0], rows[1][1], rows[1][2]],
[rows[2][0], rows[2][1], rows[2][2]],
];
Matrix3x3 { m }
}
/// Compute minor of a 4x4 matrix.
pub fn minor(&self, row: usize, col: usize) -> f32 {
self.submatrix(row, col).determinant()
}
/// Compute cofactor of a 4x4 matrix.
pub fn cofactor(&self, row: usize, col: usize) -> f32 {
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
self.submatrix(row, col).determinant() * negate
}
/// Compute determinant of a 4x4 matrix.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let a = Matrix4x4::new(
/// [-2., -8., 3., 5.],
/// [-3., 1., 7., 3.],
/// [1., 2., -9., 6.],
/// [-6., 7., 7., -9.],
/// );
/// assert_eq!(a.cofactor(0, 0), 690.);
/// assert_eq!(a.cofactor(0, 1), 447.);
/// assert_eq!(a.cofactor(0, 2), 210.);
/// assert_eq!(a.cofactor(0, 3), 51.);
/// assert_eq!(a.determinant(), -4071.);
/// ```
pub fn determinant(&self) -> f32 {
(0..4).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
}
/// Compute invertibility of matrix (i.e. non-zero determinant.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let a = Matrix4x4::new(
/// [6., 4., 4., 4.],
/// [5., 5., 7., 6.],
/// [4., -9., 3., -7.],
/// [9., 1., 7., -6.],
/// );
/// assert_eq!(a.determinant(), -2120.);
/// assert_eq!(a.invertable(), true);
///
/// let a = Matrix4x4::new(
/// [-4., 2., -2., -3.],
/// [9., 6., 2., 6.],
/// [0., -5., 1., -5.],
/// [0., 0., 0., 0.],
/// );
/// assert_eq!(a.determinant(), 0.);
/// assert_eq!(a.invertable(), false);
/// ```
pub fn invertable(&self) -> bool {
self.determinant() != 0.
}
/// Compute the inverse of a 4x4 matrix.
///
/// # Examples
/// ```
/// use float_cmp::approx_eq;
/// use rtchallenge::matrices::Matrix4x4;
///
/// let a = Matrix4x4::new(
/// [-5., 2., 6., -8.],
/// [1., -5., 1., 8.],
/// [7., 7., -6., -7.],
/// [1., -3., 7., 4.],
/// );
/// let b = a.inverse();
///
/// assert_eq!(a.determinant(), 532.);
/// assert_eq!(a.cofactor(2, 3), -160.);
/// assert_eq!(b[(3, 2)], -160. / 532.);
/// assert_eq!(a.cofactor(3, 2), 105.);
/// assert_eq!(b[(2, 3)], 105. / 532.);
/// assert!(approx_eq!(
/// &Matrix4x4,
/// &b,
/// &Matrix4x4::new(
/// [0.21805, 0.45113, 0.24060, -0.04511],
/// [-0.80827, -1.45677, -0.44361, 0.52068],
/// [-0.07895, -0.22368, -0.05263, 0.19737],
/// [-0.52256, -0.81391, -0.30075, 0.30639],
/// ),
/// epsilon = 0.0001
/// ));
///
/// // Second test case
/// assert!(approx_eq!(
/// &Matrix4x4,
/// &Matrix4x4::new(
/// [8., -5., 9., 2.],
/// [7., 5., 6., 1.],
/// [-6., 0., 9., 6.],
/// [-3., 0., -9., -4.],
/// )
/// .inverse(),
/// &Matrix4x4::new(
/// [-0.15385, -0.15385, -0.28205, -0.53846],
/// [-0.07692, 0.12308, 0.02564, 0.03077],
/// [0.35897, 0.35897, 0.43590, 0.92308],
/// [-0.69231, -0.69241, -0.76923, -1.92308],
/// ),
/// epsilon = 0.0005
/// ));
///
/// // Third test case
/// assert!(approx_eq!(
/// &Matrix4x4,
/// &Matrix4x4::new(
/// [9., 3., 0., 9.],
/// [-5., -2., -6., -3.],
/// [-4., 9., 6., 4.],
/// [-7., 6., 6., 2.],
/// )
/// .inverse(),
/// &Matrix4x4::new(
/// [-0.04074, -0.07778, 0.14444, -0.22222],
/// [-0.07778, 0.03333, 0.36667, -0.33333],
/// [-0.02901, -0.14630, -0.10926, 0.12963],
/// [0.17778, 0.06667, -0.26667, 0.33333],
/// ),
/// epsilon = 0.0001
/// ));
///
/// let a = Matrix4x4::new(
/// [3., -9., 7., 3.],
/// [3., -8., 2., -9.],
/// [-4., 4., 4., 1.],
/// [-6., 5., -1., 1.],
/// );
/// let b = Matrix4x4::new(
/// [8., 2., 2., 2.],
/// [3., -1., 7., 0.],
/// [7., 0., 5., 4.],
/// [6., -2., 0., 5.],
/// );
/// let c = a * b;
/// assert!(approx_eq!(
/// &Matrix4x4,
/// &(c * b.inverse()),
/// &a,
/// epsilon = 0.0001
/// ));
/// ```
pub fn inverse(&self) -> Matrix4x4 {
let m = self;
if !m.invertable() {
panic!("Matrix4x4::inverse called on matrix with determinant() == 0");
}
let mut m2 = Matrix4x4::identity();
let det = m.determinant();
for row in 0..4 {
for col in 0..4 {
let c = m.cofactor(row, col);
m2[(col, row)] = c / det;
}
}
m2
}
}
impl fmt::Debug for Matrix4x4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(
f,
"{:?}\n {:?}\n {:?}\n {:?}",
self.m[0], self.m[1], self.m[2], self.m[3]
)
} else {
write!(
f,
"[{:?} {:?} {:?} {:?}]",
self.m[0], self.m[1], self.m[2], self.m[3]
)
}
}
}
impl Mul<Matrix4x4> for Matrix4x4 {
type Output = Matrix4x4;
/// Implement matrix multiplication for `Matrix4x4`.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let i = Matrix4x4::identity();
/// let m1 = Matrix4x4::identity();
/// let m2 = Matrix4x4::identity();
///
/// assert_eq!(m1 * m2, i);
/// ```
fn mul(self, m2: Matrix4x4) -> Matrix4x4 {
let m1 = self;
let mut r: Matrix4x4 = Default::default();
for i in 0..4 {
for j in 0..4 {
r.m[i][j] = m1.m[i][0] * m2.m[0][j]
+ m1.m[i][1] * m2.m[1][j]
+ m1.m[i][2] * m2.m[2][j]
+ m1.m[i][3] * m2.m[3][j];
}
}
r
}
}
impl Mul<Tuple> for Matrix4x4 {
type Output = Tuple;
/// Implement matrix multiplication for `Matrix4x4` * `Tuple`.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
/// use rtchallenge::tuples::Tuple;
///
/// let a = Matrix4x4::new(
/// [1., 2., 3., 4.],
/// [2., 4., 4., 2.],
/// [8., 6., 4., 1.],
/// [0., 0., 0., 1.],
/// );
/// let b = Tuple::new(1., 2., 3., 1.);
///
/// assert_eq!(a * b, Tuple::new(18., 24., 33., 1.));
/// ```
fn mul(self, t: Tuple) -> Tuple {
let m = self;
Tuple {
x: m.m[0][0] * t.x + m.m[0][1] * t.y + m.m[0][2] * t.z + m.m[0][3] * t.w,
y: m.m[1][0] * t.x + m.m[1][1] * t.y + m.m[1][2] * t.z + m.m[1][3] * t.w,
z: m.m[2][0] * t.x + m.m[2][1] * t.y + m.m[2][2] * t.z + m.m[2][3] * t.w,
w: m.m[3][0] * t.x + m.m[3][1] * t.y + m.m[3][2] * t.z + m.m[3][3] * t.w,
}
}
}
impl PartialEq for Matrix4x4 {
fn eq(&self, rhs: &Matrix4x4) -> bool {
let l = self.m;
let r = rhs.m;
for i in 0..4 {
for j in 0..4 {
let d = (l[i][j] - r[i][j]).abs();
if d > f32::EPSILON {
return false;
}
}
}
true
}
}
impl Index<(usize, usize)> for Matrix4x4 {
type Output = f32;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
impl IndexMut<(usize, usize)> for Matrix4x4 {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
&mut self.m[row][col]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn construct2x2() {
let m = Matrix2x2::new([-3., 5.], [1., -2.]);
assert_eq!(m[(0, 0)], -3.);
assert_eq!(m[(0, 1)], 5.);
assert_eq!(m[(1, 0)], 1.);
assert_eq!(m[(1, 1)], -2.);
}
#[test]
fn construct3x3() {
let m = Matrix3x3::new([-3., 5., 0.], [1., -2., -7.], [0., 1., 1.]);
assert_eq!(m[(0, 0)], -3.);
assert_eq!(m[(1, 1)], -2.);
assert_eq!(m[(2, 2)], 1.);
}
#[test]
fn construct4x4() {
let m = Matrix4x4::new(
[1., 2., 3., 4.],
[5.5, 6.5, 7.5, 8.5],
[9., 10., 11., 12.],
[13.5, 14.5, 15.5, 16.5],
);
assert_eq!(m[(0, 0)], 1.);
assert_eq!(m[(0, 3)], 4.);
assert_eq!(m[(1, 0)], 5.5);
assert_eq!(m[(1, 2)], 7.5);
assert_eq!(m[(2, 2)], 11.);
assert_eq!(m[(3, 0)], 13.5);
assert_eq!(m[(3, 2)], 15.5);
}
#[test]
fn equality4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
assert_eq!(a, b);
}
#[test]
fn inequality4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[2., 3., 4., 5.],
[6., 7., 8., 9.],
[8., 7., 6., 5.],
[4., 3., 2., 1.],
);
assert_ne!(a, b);
}
#[test]
fn mul4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[-2., 1., 2., 3.],
[3., 2., 1., -1.],
[4., 3., 6., 5.],
[1., 2., 7., 8.],
);
assert_eq!(
a * b,
Matrix4x4::new(
[20., 22., 50., 48.],
[44., 54., 114., 108.],
[40., 58., 110., 102.],
[16., 26., 46., 42.],
)
);
}
}

View File

@ -1,4 +1,5 @@
use std::ops::{Add, Div, Mul, Neg, Sub}; use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub struct Tuple { pub struct Tuple {
pub x: f32, pub x: f32,
@ -214,11 +215,11 @@ impl Sub for Color {
mod tests { mod tests {
use float_cmp::approx_eq; use float_cmp::approx_eq;
use super::{color, cross, dot, point, tuple, vector}; use super::{cross, dot, Color, Tuple};
#[test] #[test]
fn is_point() { fn is_point() {
// A tuple with w = 1 is a point // A tuple with w = 1 is a point
let a = tuple(4.3, -4.2, 3.1, 1.0); let a = Tuple::new(4.3, -4.2, 3.1, 1.0);
assert_eq!(a.x, 4.3); assert_eq!(a.x, 4.3);
assert_eq!(a.y, -4.2); assert_eq!(a.y, -4.2);
assert_eq!(a.z, 3.1); assert_eq!(a.z, 3.1);
@ -229,7 +230,7 @@ mod tests {
#[test] #[test]
fn is_vector() { fn is_vector() {
// A tuple with w = 0 is a point // A tuple with w = 0 is a point
let a = tuple(4.3, -4.2, 3.1, 0.0); let a = Tuple::new(4.3, -4.2, 3.1, 0.0);
assert_eq!(a.x, 4.3); assert_eq!(a.x, 4.3);
assert_eq!(a.y, -4.2); assert_eq!(a.y, -4.2);
assert_eq!(a.z, 3.1); assert_eq!(a.z, 3.1);
@ -239,78 +240,81 @@ mod tests {
} }
#[test] #[test]
fn point_tuple() { fn point_tuple() {
assert_eq!(point(4., -4., 3.), tuple(4., -4., 3., 1.)) assert_eq!(Tuple::point(4., -4., 3.), Tuple::new(4., -4., 3., 1.))
} }
#[test] #[test]
fn vector_tuple() { fn vector_tuple() {
assert_eq!(vector(4., -4., 3.), tuple(4., -4., 3., 0.)) assert_eq!(Tuple::vector(4., -4., 3.), Tuple::new(4., -4., 3., 0.))
} }
#[test] #[test]
fn add_two_tuples() { fn add_two_tuples() {
let a1 = tuple(3., -2., 5., 1.); let a1 = Tuple::new(3., -2., 5., 1.);
let a2 = tuple(-2., 3., 1., 0.); let a2 = Tuple::new(-2., 3., 1., 0.);
assert_eq!(a1 + a2, tuple(1., 1., 6., 1.)); assert_eq!(a1 + a2, Tuple::new(1., 1., 6., 1.));
} }
#[test] #[test]
fn sub_two_points() { fn sub_two_points() {
let p1 = point(3., 2., 1.); let p1 = Tuple::point(3., 2., 1.);
let p2 = point(5., 6., 7.); let p2 = Tuple::point(5., 6., 7.);
assert_eq!(p1 - p2, vector(-2., -4., -6.)); assert_eq!(p1 - p2, Tuple::vector(-2., -4., -6.));
} }
#[test] #[test]
fn sub_vector_point() { fn sub_vector_point() {
let p = point(3., 2., 1.); let p = Tuple::point(3., 2., 1.);
let v = vector(5., 6., 7.); let v = Tuple::vector(5., 6., 7.);
assert_eq!(p - v, point(-2., -4., -6.)); assert_eq!(p - v, Tuple::point(-2., -4., -6.));
} }
#[test] #[test]
fn sub_two_vectors() { fn sub_two_vectors() {
let v1 = vector(3., 2., 1.); let v1 = Tuple::vector(3., 2., 1.);
let v2 = vector(5., 6., 7.); let v2 = Tuple::vector(5., 6., 7.);
assert_eq!(v1 - v2, vector(-2., -4., -6.)); assert_eq!(v1 - v2, Tuple::vector(-2., -4., -6.));
} }
#[test] #[test]
fn sub_zero_vector() { fn sub_zero_vector() {
let zero = vector(0., 0., 0.); let zero = Tuple::vector(0., 0., 0.);
let v = vector(1., -2., 3.); let v = Tuple::vector(1., -2., 3.);
assert_eq!(zero - v, vector(-1., 2., -3.)); assert_eq!(zero - v, Tuple::vector(-1., 2., -3.));
} }
#[test] #[test]
fn negate_tuple() { fn negate_tuple() {
let a = tuple(1., -2., 3., -4.); let a = Tuple::new(1., -2., 3., -4.);
assert_eq!(-a, tuple(-1., 2., -3., 4.)); assert_eq!(-a, Tuple::new(-1., 2., -3., 4.));
} }
#[test] #[test]
fn mul_tuple_scalar() { fn mul_tuple_scalar() {
let a = tuple(1., -2., 3., -4.); let a = Tuple::new(1., -2., 3., -4.);
assert_eq!(a * 3.5, tuple(3.5, -7., 10.5, -14.)); assert_eq!(a * 3.5, Tuple::new(3.5, -7., 10.5, -14.));
assert_eq!(3.5 * a, tuple(3.5, -7., 10.5, -14.)); assert_eq!(3.5 * a, Tuple::new(3.5, -7., 10.5, -14.));
} }
#[test] #[test]
fn mul_tuple_fraction() { fn mul_tuple_fraction() {
let a = tuple(1., -2., 3., -4.); let a = Tuple::new(1., -2., 3., -4.);
assert_eq!(a * 0.5, tuple(0.5, -1., 1.5, -2.)); assert_eq!(a * 0.5, Tuple::new(0.5, -1., 1.5, -2.));
assert_eq!(0.5 * a, tuple(0.5, -1., 1.5, -2.)); assert_eq!(0.5 * a, Tuple::new(0.5, -1., 1.5, -2.));
} }
#[test] #[test]
fn div_tuple_scalar() { fn div_tuple_scalar() {
let a = tuple(1., -2., 3., -4.); let a = Tuple::new(1., -2., 3., -4.);
assert_eq!(a / 2., tuple(0.5, -1., 1.5, -2.)); assert_eq!(a / 2., Tuple::new(0.5, -1., 1.5, -2.));
} }
#[test] #[test]
fn vector_magnitude() { fn vector_magnitude() {
assert_eq!(1., vector(1., 0., 0.).magnitude()); assert_eq!(1., Tuple::vector(1., 0., 0.).magnitude());
assert_eq!(1., vector(0., 1., 0.).magnitude()); assert_eq!(1., Tuple::vector(0., 1., 0.).magnitude());
assert_eq!(1., vector(0., 0., 1.).magnitude()); assert_eq!(1., Tuple::vector(0., 0., 1.).magnitude());
assert_eq!(14_f32.sqrt(), vector(1., 2., 3.).magnitude()); assert_eq!(14_f32.sqrt(), Tuple::vector(1., 2., 3.).magnitude());
assert_eq!(14_f32.sqrt(), vector(-1., -2., -3.).magnitude()); assert_eq!(14_f32.sqrt(), Tuple::vector(-1., -2., -3.).magnitude());
} }
#[test] #[test]
fn vector_normalize() { fn vector_normalize() {
assert_eq!(vector(1., 0., 0.), vector(4., 0., 0.).normalize());
assert_eq!( assert_eq!(
vector(1. / 14_f32.sqrt(), 2. / 14_f32.sqrt(), 3. / 14_f32.sqrt()), Tuple::vector(1., 0., 0.),
vector(1., 2., 3.).normalize() Tuple::vector(4., 0., 0.).normalize()
);
assert_eq!(
Tuple::vector(1. / 14_f32.sqrt(), 2. / 14_f32.sqrt(), 3. / 14_f32.sqrt()),
Tuple::vector(1., 2., 3.).normalize()
); );
} }
#[test] #[test]
@ -318,53 +322,53 @@ mod tests {
assert!(approx_eq!( assert!(approx_eq!(
f32, f32,
1., 1.,
vector(1., 2., 3.).normalize().magnitude(), Tuple::vector(1., 2., 3.).normalize().magnitude(),
ulps = 1 ulps = 1
)); ));
} }
#[test] #[test]
fn dot_two_tuples() { fn dot_two_tuples() {
let a = vector(1., 2., 3.); let a = Tuple::vector(1., 2., 3.);
let b = vector(2., 3., 4.); let b = Tuple::vector(2., 3., 4.);
assert_eq!(20., dot(a, b)); assert_eq!(20., dot(a, b));
} }
#[test] #[test]
fn cross_two_tuples() { fn cross_two_tuples() {
let a = vector(1., 2., 3.); let a = Tuple::vector(1., 2., 3.);
let b = vector(2., 3., 4.); let b = Tuple::vector(2., 3., 4.);
assert_eq!(vector(-1., 2., -1.), cross(a, b)); assert_eq!(Tuple::vector(-1., 2., -1.), cross(a, b));
assert_eq!(vector(1., -2., 1.), cross(b, a)); assert_eq!(Tuple::vector(1., -2., 1.), cross(b, a));
} }
#[test] #[test]
fn color_rgb() { fn color_rgb() {
let c = color(-0.5, 0.4, 1.7); let c = Color::new(-0.5, 0.4, 1.7);
assert_eq!(c.red, -0.5); assert_eq!(c.red, -0.5);
assert_eq!(c.green, 0.4); assert_eq!(c.green, 0.4);
assert_eq!(c.blue, 1.7); assert_eq!(c.blue, 1.7);
} }
#[test] #[test]
fn add_color() { fn add_color() {
let c1 = color(0.9, 0.6, 0.75); let c1 = Color::new(0.9, 0.6, 0.75);
let c2 = color(0.7, 0.1, 0.25); let c2 = Color::new(0.7, 0.1, 0.25);
assert_eq!(c1 + c2, color(0.9 + 0.7, 0.6 + 0.1, 0.75 + 0.25)); assert_eq!(c1 + c2, Color::new(0.9 + 0.7, 0.6 + 0.1, 0.75 + 0.25));
} }
#[test] #[test]
fn sub_color() { fn sub_color() {
let c1 = color(0.9, 0.6, 0.75); let c1 = Color::new(0.9, 0.6, 0.75);
let c2 = color(0.7, 0.1, 0.25); let c2 = Color::new(0.7, 0.1, 0.25);
assert_eq!(c1 - c2, color(0.9 - 0.7, 0.6 - 0.1, 0.75 - 0.25)); assert_eq!(c1 - c2, Color::new(0.9 - 0.7, 0.6 - 0.1, 0.75 - 0.25));
} }
#[test] #[test]
fn mul_color_scalar() { fn mul_color_scalar() {
let c = color(0.2, 0.3, 0.4); let c = Color::new(0.2, 0.3, 0.4);
assert_eq!(c * 2., color(0.2 * 2., 0.3 * 2., 0.4 * 2.)); assert_eq!(c * 2., Color::new(0.2 * 2., 0.3 * 2., 0.4 * 2.));
assert_eq!(2. * c, color(0.2 * 2., 0.3 * 2., 0.4 * 2.)); assert_eq!(2. * c, Color::new(0.2 * 2., 0.3 * 2., 0.4 * 2.));
} }
#[test] #[test]
fn mul_colors() { fn mul_colors() {
let c1 = color(1., 0.2, 0.4); let c1 = Color::new(1., 0.2, 0.4);
let c2 = color(0.9, 1., 0.1); let c2 = Color::new(0.9, 1., 0.1);
assert_eq!(c1 * c2, color(1.0 * 0.9, 0.2 * 1., 0.4 * 0.1)); assert_eq!(c1 * c2, Color::new(1.0 * 0.9, 0.2 * 1., 0.4 * 0.1));
} }
} }