[INFO] cloning repository https://github.com/jcholder-thoughtworks/ray_tracer_challenge [INFO] running `Command { std: "git" "-c" "credential.helper=" "-c" "credential.helper=/workspace/cargo-home/bin/git-credential-null" "clone" "--bare" "https://github.com/jcholder-thoughtworks/ray_tracer_challenge" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge", kill_on_drop: false }` [INFO] [stderr] Cloning into bare repository '/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge'... [INFO] running `Command { std: "git" "rev-parse" "HEAD", kill_on_drop: false }` [INFO] [stdout] a76a30dcbb16137910b5f2cc66e452c11c4ef40e [INFO] testing jcholder-thoughtworks/ray_tracer_challenge against beta-2022-04-10 for beta-1.61-1 [INFO] running `Command { std: "git" "clone" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge" "/workspace/builds/worker-108/source", kill_on_drop: false }` [INFO] [stderr] Cloning into '/workspace/builds/worker-108/source'... [INFO] [stderr] done. [INFO] validating manifest of git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge on toolchain beta-2022-04-10 [INFO] running `Command { std: "/workspace/cargo-home/bin/cargo" "+beta-2022-04-10" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }` [INFO] started tweaking git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge [INFO] removed 0 missing tests [INFO] finished tweaking git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge [INFO] tweaked toml for git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge written to /workspace/builds/worker-108/source/Cargo.toml [INFO] crate git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge already has a lockfile, it will not be regenerated [INFO] running `Command { std: "/workspace/cargo-home/bin/cargo" "+beta-2022-04-10" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }` [INFO] [stderr] Blocking waiting for file lock on package cache [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:b0c94ce3c1162fcb8e57cac5b65ec2f72eabb1eebea4fcc35e269e823f681646" "/opt/rustwide/cargo-home/bin/cargo" "+beta-2022-04-10" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }` [INFO] [stdout] 9a644339054895a76ce40fd5a0480440165b40b1135153cb86ab379962fb2911 [INFO] running `Command { std: "docker" "start" "-a" "9a644339054895a76ce40fd5a0480440165b40b1135153cb86ab379962fb2911", kill_on_drop: false }` [INFO] running `Command { std: "docker" "inspect" "9a644339054895a76ce40fd5a0480440165b40b1135153cb86ab379962fb2911", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "9a644339054895a76ce40fd5a0480440165b40b1135153cb86ab379962fb2911", kill_on_drop: false }` [INFO] [stdout] 9a644339054895a76ce40fd5a0480440165b40b1135153cb86ab379962fb2911 [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:b0c94ce3c1162fcb8e57cac5b65ec2f72eabb1eebea4fcc35e269e823f681646" "/opt/rustwide/cargo-home/bin/cargo" "+beta-2022-04-10" "build" "--frozen" "--message-format=json", kill_on_drop: false }` [INFO] [stdout] a60864cca8128b3ef4fd51e012224da6bdc31ede9448b96ed020cf436f79fda0 [INFO] running `Command { std: "docker" "start" "-a" "a60864cca8128b3ef4fd51e012224da6bdc31ede9448b96ed020cf436f79fda0", kill_on_drop: false }` [INFO] [stderr] Compiling ray_tracer_challenge v0.1.0 (/opt/rustwide/workdir) [INFO] [stdout] warning: unnecessary braces around method argument [INFO] [stdout] --> src/lib.rs:86:30 [INFO] [stdout] | [INFO] [stdout] 86 | self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] | ^^ ^^ [INFO] [stdout] | [INFO] [stdout] = note: `#[warn(unused_braces)]` on by default [INFO] [stdout] help: remove these braces [INFO] [stdout] | [INFO] [stdout] 86 - self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] 86 + self.objs.iter().map(|o| Rc::new(o.clone())).collect() [INFO] [stdout] | [INFO] [stdout] [INFO] [stdout] [INFO] [stdout] warning: 1 warning emitted [INFO] [stdout] [INFO] [stdout] [INFO] [stderr] Finished dev [unoptimized + debuginfo] target(s) in 2.04s [INFO] running `Command { std: "docker" "inspect" "a60864cca8128b3ef4fd51e012224da6bdc31ede9448b96ed020cf436f79fda0", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "a60864cca8128b3ef4fd51e012224da6bdc31ede9448b96ed020cf436f79fda0", kill_on_drop: false }` [INFO] [stdout] a60864cca8128b3ef4fd51e012224da6bdc31ede9448b96ed020cf436f79fda0 [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:b0c94ce3c1162fcb8e57cac5b65ec2f72eabb1eebea4fcc35e269e823f681646" "/opt/rustwide/cargo-home/bin/cargo" "+beta-2022-04-10" "test" "--frozen" "--no-run" "--message-format=json", kill_on_drop: false }` [INFO] [stdout] abdd15e921aad74a656ba74e71c9b709272553f09f0f93b00a0ecba14fab2e16 [INFO] running `Command { std: "docker" "start" "-a" "abdd15e921aad74a656ba74e71c9b709272553f09f0f93b00a0ecba14fab2e16", kill_on_drop: false }` [INFO] [stderr] Compiling syn v0.15.44 [INFO] [stderr] Compiling memchr v2.3.0 [INFO] [stderr] Compiling proc-macro2 v1.0.8 [INFO] [stderr] Compiling strsim v0.7.0 [INFO] [stderr] Compiling ident_case v1.0.1 [INFO] [stderr] Compiling ucd-trie v0.1.2 [INFO] [stderr] Compiling syn v1.0.14 [INFO] [stderr] Compiling maplit v1.0.2 [INFO] [stderr] Compiling regex-syntax v0.6.14 [INFO] [stderr] Compiling same-file v1.0.6 [INFO] [stderr] Compiling derive_builder v0.7.2 [INFO] [stderr] Compiling unicode-width v0.1.7 [INFO] [stderr] Compiling vec_map v0.8.1 [INFO] [stderr] Compiling strsim v0.8.0 [INFO] [stderr] Compiling ansi_term v0.11.0 [INFO] [stderr] Compiling termcolor v1.1.0 [INFO] [stderr] Compiling pathdiff v0.1.0 [INFO] [stdout] warning: unnecessary braces around method argument [INFO] [stdout] --> src/lib.rs:86:30 [INFO] [stdout] | [INFO] [stdout] 86 | self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] | ^^ ^^ [INFO] [stdout] | [INFO] [stdout] = note: `#[warn(unused_braces)]` on by default [INFO] [stdout] help: remove these braces [INFO] [stdout] | [INFO] [stdout] 86 - self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] 86 + self.objs.iter().map(|o| Rc::new(o.clone())).collect() [INFO] [stdout] | [INFO] [stdout] [INFO] [stdout] [INFO] [stdout] warning: 1 warning emitted [INFO] [stdout] [INFO] [stdout] [INFO] [stderr] Compiling crossbeam-utils v0.7.0 [INFO] [stderr] Compiling thread_local v1.0.1 [INFO] [stderr] Compiling term_size v0.3.1 [INFO] [stderr] Compiling atty v0.2.14 [INFO] [stderr] Compiling shh v1.0.1 [INFO] [stderr] Compiling walkdir v2.3.1 [INFO] [stderr] Compiling textwrap v0.11.0 [INFO] [stderr] Compiling pest v2.1.2 [INFO] [stderr] Compiling clap v2.33.0 [INFO] [stderr] Compiling aho-corasick v0.7.8 [INFO] [stderr] Compiling bstr v0.2.11 [INFO] [stderr] Compiling quote v1.0.2 [INFO] [stderr] Compiling crossbeam-channel v0.4.0 [INFO] [stderr] Compiling pest_meta v2.1.2 [INFO] [stderr] Compiling regex v1.3.4 [INFO] [stderr] Compiling pest_generator v2.1.1 [INFO] [stderr] Compiling globset v0.4.4 [INFO] [stderr] Compiling ignore v0.4.11 [INFO] [stderr] Compiling pest_derive v2.1.0 [INFO] [stderr] Compiling globwalk v0.7.1 [INFO] [stderr] Compiling darling_core v0.9.0 [INFO] [stderr] Compiling darling_macro v0.9.0 [INFO] [stderr] Compiling darling v0.9.0 [INFO] [stderr] Compiling derive_builder_core v0.5.0 [INFO] [stderr] Compiling gherkin_rust v0.6.0 [INFO] [stderr] Compiling cucumber_rust v0.6.7 [INFO] [stderr] Compiling ray_tracer_challenge v0.1.0 (/opt/rustwide/workdir) [INFO] [stdout] warning: unnecessary braces around method argument [INFO] [stdout] --> src/lib.rs:86:30 [INFO] [stdout] | [INFO] [stdout] 86 | self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] | ^^ ^^ [INFO] [stdout] | [INFO] [stdout] = note: `#[warn(unused_braces)]` on by default [INFO] [stdout] help: remove these braces [INFO] [stdout] | [INFO] [stdout] 86 - self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stdout] 86 + self.objs.iter().map(|o| Rc::new(o.clone())).collect() [INFO] [stdout] | [INFO] [stdout] [INFO] [stdout] [INFO] [stdout] warning: 1 warning emitted [INFO] [stdout] [INFO] [stdout] [INFO] [stderr] Finished test [unoptimized + debuginfo] target(s) in 18.97s [INFO] [stderr] Executable unittests src/lib.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-012344b8f1fc7d42) [INFO] [stderr] Executable unittests src/main.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-e59bf8323aeeaa37) [INFO] [stderr] Executable tests/cucumber.rs (/opt/rustwide/target/debug/deps/cucumber-c48101cdcba70f78) [INFO] running `Command { std: "docker" "inspect" "abdd15e921aad74a656ba74e71c9b709272553f09f0f93b00a0ecba14fab2e16", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "abdd15e921aad74a656ba74e71c9b709272553f09f0f93b00a0ecba14fab2e16", kill_on_drop: false }` [INFO] [stdout] abdd15e921aad74a656ba74e71c9b709272553f09f0f93b00a0ecba14fab2e16 [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-108/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:b0c94ce3c1162fcb8e57cac5b65ec2f72eabb1eebea4fcc35e269e823f681646" "/opt/rustwide/cargo-home/bin/cargo" "+beta-2022-04-10" "test" "--frozen", kill_on_drop: false }` [INFO] [stdout] 320b1c2963e4e6044e108935799f5444b89fc8c4073e71a539d5e661d42e87ef [INFO] running `Command { std: "docker" "start" "-a" "320b1c2963e4e6044e108935799f5444b89fc8c4073e71a539d5e661d42e87ef", kill_on_drop: false }` [INFO] [stderr] warning: unnecessary braces around method argument [INFO] [stderr] --> src/lib.rs:86:30 [INFO] [stderr] | [INFO] [stderr] 86 | self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stderr] | ^^ ^^ [INFO] [stderr] | [INFO] [stderr] = note: `#[warn(unused_braces)]` on by default [INFO] [stderr] help: remove these braces [INFO] [stderr] | [INFO] [stderr] 86 - self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stderr] 86 + self.objs.iter().map(|o| Rc::new(o.clone())).collect() [INFO] [stderr] | [INFO] [stderr] [INFO] [stderr] warning: `ray_tracer_challenge` (lib) generated 1 warning [INFO] [stderr] warning: `ray_tracer_challenge` (lib test) generated 1 warning (1 duplicate) [INFO] [stderr] Finished test [unoptimized + debuginfo] target(s) in 0.12s [INFO] [stderr] Running unittests src/lib.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-012344b8f1fc7d42) [INFO] [stdout] [INFO] [stdout] running 29 tests [INFO] [stdout] test canvas::tests::initialization ... ok [INFO] [stdout] test canvas::tests::ppm_content ... ok [INFO] [stdout] test canvas::tests::ppm_headers ... ok [INFO] [stdout] test canvas::tests::terminating_newline_on_ppm_content ... ok [INFO] [stdout] test canvas::tests::writing_and_reading_pixels ... ok [INFO] [stdout] test color::tests::adding_colors ... ok [INFO] [stdout] test canvas::tests::well_formatted_ppm_content ... ok [INFO] [stdout] test color::tests::multiplying_colors_by_colors ... ok [INFO] [stdout] test color::tests::multiplying_colors_by_scalars ... ok [INFO] [stdout] test color::tests::subtracting_colors ... ok [INFO] [stdout] test tests::equalish_is_false_for_diff_above_epsilon ... ok [INFO] [stdout] test tests::equalish_is_true_for_diff_below_epsilon ... ok [INFO] [stdout] test tests::point_tests::add_vector_sums_each_pair_of_values ... ok [INFO] [stdout] test tests::point_tests::equalish_is_false_for_diff_above_epsilon ... ok [INFO] [stdout] test tests::point_tests::equalish_is_true_for_diff_below_epsilon ... ok [INFO] [stdout] test tests::point_tests::subtract_point_subtracts_latter_from_former_for_each_pair_of_values ... ok [INFO] [stdout] test tests::point_tests::subtract_vector_subtracts_latter_from_former_for_each_pair_of_values ... ok [INFO] [stdout] test tests::vector_tests::add_point_sums_each_pair_of_values ... ok [INFO] [stdout] test tests::vector_tests::add_vector_sums_each_pair_of_values ... ok [INFO] [stdout] test tests::vector_tests::cross_products_of_vectors ... ok [INFO] [stdout] test tests::vector_tests::divide_vector_divides_each_value ... ok [INFO] [stdout] test tests::vector_tests::dot_products_of_vectors ... ok [INFO] [stdout] test tests::vector_tests::equalish_is_false_for_diff_above_epsilon ... ok [INFO] [stdout] test tests::vector_tests::equalish_is_true_for_diff_below_epsilon ... ok [INFO] [stdout] test tests::vector_tests::magnitudes_of_vectors ... ok [INFO] [stdout] test tests::vector_tests::multiply_vector_multiplies_each_value ... ok [INFO] [stdout] test tests::vector_tests::negating_vector_inverts_each_value ... ok [INFO] [stdout] test tests::vector_tests::normalizing_vectors ... ok [INFO] [stdout] test tests::vector_tests::subtract_vector_subtracts_second_value_from_for_for_each_pair ... ok [INFO] [stdout] [INFO] [stdout] test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s [INFO] [stdout] [INFO] [stderr] Running unittests src/main.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-e59bf8323aeeaa37) [INFO] [stdout] [INFO] [stdout] running 0 tests [INFO] [stdout] [INFO] [stdout] test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s [INFO] [stdout] [INFO] [stderr] Running tests/cucumber.rs (/opt/rustwide/target/debug/deps/cucumber-c48101cdcba70f78) [INFO] [stdout] [Cucumber v0.6.7] [INFO] [stdout] [INFO] [stdout] Feature: Camera features/camera.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Constructing a camera features/camera.feature:3:11 [INFO] [stdout] ✔ Given hsize ← 160 features/camera.feature:4:3 [INFO] [stdout] ✔ And vsize ← 120 features/camera.feature:5:5 [INFO] [stdout] ✔ And field_of_view ← π/2 features/camera.feature:6:5 [INFO] [stdout] ✔ When c ← camera(hsize, vsize, field_of_view) features/camera.feature:7:3 [INFO] [stdout] ✔ Then c.hsize = 160 features/camera.feature:8:3 [INFO] [stdout] ✔ And c.vsize = 120 features/camera.feature:9:5 [INFO] [stdout] ✔ And c.field_of_view = π/2 features/camera.feature:10:5 [INFO] [stdout] ✔ And c.transform = identity_matrix features/camera.feature:11:5 [INFO] [stdout] [INFO] [stdout] Scenario: The pixel size for a horizontal canvas features/camera.feature:13:11 [INFO] [stdout] ✔ Given c ← camera(200, 125, π/2) features/camera.feature:14:3 [INFO] [stdout] ✔ Then c.pixel_size = 0.01 features/camera.feature:15:3 [INFO] [stdout] [INFO] [stdout] Scenario: The pixel size for a vertical canvas features/camera.feature:17:11 [INFO] [stdout] ✔ Given c ← camera(125, 200, π/2) features/camera.feature:18:3 [INFO] [stdout] ✔ Then c.pixel_size = 0.01 features/camera.feature:19:3 [INFO] [stdout] [INFO] [stdout] Scenario: Constructing a ray through the center of the canvas [INFO] [stdout] features/camera.feature:21:11 [INFO] [stdout] ✔ Given c ← camera(201, 101, π/2) features/camera.feature:22:3 [INFO] [stdout] ✔ When r ← ray_for_pixel(c, 100, 50) features/camera.feature:23:3 [INFO] [stdout] ✔ Then r.origin = point(0, 0, 0) features/camera.feature:24:3 [INFO] [stdout] ✔ And r.direction = vector(0, 0, -1) features/camera.feature:25:5 [INFO] [stdout] [INFO] [stdout] Scenario: Constructing a ray through a corner of the canvas [INFO] [stdout] features/camera.feature:27:11 [INFO] [stdout] ✔ Given c ← camera(201, 101, π/2) features/camera.feature:28:3 [INFO] [stdout] ✔ When r ← ray_for_pixel(c, 0, 0) features/camera.feature:29:3 [INFO] [stdout] ✔ Then r.origin = point(0, 0, 0) features/camera.feature:30:3 [INFO] [stdout] ✔ And r.direction = vector(0.66519, 0.33259, -0.66851) [INFO] [stdout] features/camera.feature:31:5 [INFO] [stdout] [INFO] [stdout] Scenario: Constructing a ray when the camera is transformed [INFO] [stdout] features/camera.feature:33:11 [INFO] [stdout] ✔ Given c ← camera(201, 101, π/2) features/camera.feature:34:3 [INFO] [stdout] ✔ When c.transform ← rotation_y(π/4) * translation(0, -2, 5) [INFO] [stdout] features/camera.feature:35:3 [INFO] [stdout] ✔ And r ← ray_for_pixel(c, 100, 50) features/camera.feature:36:5 [INFO] [stdout] ✔ Then r.origin = point(0, 2, -5) features/camera.feature:37:3 [INFO] [stdout] ✔ And r.direction = vector(√2/2, 0, -√2/2) features/camera.feature:38:5 [INFO] [stdout] [INFO] [stdout] Scenario: Rendering a world with a camera features/camera.feature:40:11 [INFO] [stdout] ✔ Given w ← default_world() features/camera.feature:41:3 [INFO] [stdout] ✔ And c ← camera(11, 11, π/2) features/camera.feature:42:5 [INFO] [stdout] ✔ And from ← point(0, 0, -5) features/camera.feature:43:5 [INFO] [stdout] ✔ And to ← point(0, 0, 0) features/camera.feature:44:5 [INFO] [stdout] ✔ And up ← vector(0, 1, 0) features/camera.feature:45:5 [INFO] [stdout] ✔ And c.transform ← view_transform(from, to, up) features/camera.feature:46:5 [INFO] [stdout] ✔ When image ← render(c, w) features/camera.feature:47:3 [INFO] [stdout] ✔ Then pixel_at(image, 5, 5) = color(0.38066, 0.47583, 0.2855) [INFO] [stdout] features/camera.feature:48:3 [INFO] [stdout] [INFO] [stdout] Feature: Colors features/colors.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Adding colors features/colors.feature:3:13 [INFO] [stdout] ✔ Given c1 = color(0.9, 0.6, 0.75) features/colors.feature:4:5 [INFO] [stdout] ✔ And c2 = color(0.7, 0.1, 0.25) features/colors.feature:5:5 [INFO] [stdout] ✔ Then c1 + c2 = color(1.6, 0.7, 1.0) features/colors.feature:6:5 [INFO] [stdout] [INFO] [stdout] Feature: Intersections features/intersections.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: An intersection encapsulates t and object [INFO] [stdout] features/intersections.feature:3:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:4:3 [INFO] [stdout] ✔ When i ← intersection(3.5, s) features/intersections.feature:5:3 [INFO] [stdout] ✔ Then i.t = 3.5 features/intersections.feature:6:3 [INFO] [stdout] ✔ And i.object = s features/intersections.feature:7:5 [INFO] [stdout] [INFO] [stdout] Scenario: Precomputing the state of an intersection [INFO] [stdout] features/intersections.feature:9:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/intersections.feature:10:3 [INFO] [stdout] ✔ And shape ← sphere() features/intersections.feature:11:5 [INFO] [stdout] ✔ And i ← intersection(4, shape) features/intersections.feature:12:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/intersections.feature:13:3 [INFO] [stdout] ✔ Then comps.t = i.t features/intersections.feature:14:3 [INFO] [stdout] ✔ And comps.object = i.object features/intersections.feature:15:5 [INFO] [stdout] ✔ And comps.point = point(0, 0, -1) features/intersections.feature:16:5 [INFO] [stdout] ✔ And comps.eyev = vector(0, 0, -1) features/intersections.feature:17:5 [INFO] [stdout] ✔ And comps.normalv = vector(0, 0, -1) features/intersections.feature:18:5 [INFO] [stdout] [INFO] [stdout] Scenario: The hit, when an intersection occurs on the outside [INFO] [stdout] features/intersections.feature:27:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/intersections.feature:28:3 [INFO] [stdout] ✔ And shape ← sphere() features/intersections.feature:29:5 [INFO] [stdout] ✔ And i ← intersection(4, shape) features/intersections.feature:30:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/intersections.feature:31:3 [INFO] [stdout] ✔ Then comps.inside = false features/intersections.feature:32:3 [INFO] [stdout] [INFO] [stdout] Scenario: The hit, when an intersection occurs on the inside [INFO] [stdout] features/intersections.feature:34:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, 0), vector(0, 0, 1)) [INFO] [stdout] features/intersections.feature:35:3 [INFO] [stdout] ✔ And shape ← sphere() features/intersections.feature:36:5 [INFO] [stdout] ✔ And i ← intersection(1, shape) features/intersections.feature:37:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/intersections.feature:38:3 [INFO] [stdout] ✔ Then comps.point = point(0, 0, 1) features/intersections.feature:39:3 [INFO] [stdout] ✔ And comps.eyev = vector(0, 0, -1) features/intersections.feature:40:5 [INFO] [stdout] ✔ And comps.inside = true features/intersections.feature:41:5 [INFO] [stdout] ✔ And comps.normalv = vector(0, 0, -1) features/intersections.feature:43:5 [INFO] [stdout] [INFO] [stdout] Scenario: The hit should offset the point features/intersections.feature:45:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/intersections.feature:46:3 [INFO] [stdout] ✔ And shape ← sphere() with: features/intersections.feature:47:5 [INFO] [stdout] | | | [INFO] [stdout] | transform | translation(0, 0, 1) | [INFO] [stdout] ✔ And i ← intersection(5, shape) features/intersections.feature:49:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/intersections.feature:50:3 [INFO] [stdout] ✔ Then comps.over_point.z < -EPSILON/2 features/intersections.feature:51:3 [INFO] [stdout] ✔ And comps.point.z > comps.over_point.z features/intersections.feature:52:5 [INFO] [stdout] [INFO] [stdout] Scenario: Aggregating intersections features/intersections.feature:64:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:65:3 [INFO] [stdout] ✔ And i1 ← intersection(1, s) features/intersections.feature:66:5 [INFO] [stdout] ✔ And i2 ← intersection(2, s) features/intersections.feature:67:5 [INFO] [stdout] ✔ When xs ← intersections(i1, i2) features/intersections.feature:68:3 [INFO] [stdout] ✔ Then xs.count = 2 features/intersections.feature:69:3 [INFO] [stdout] ✔ And xs[0].t = 1 features/intersections.feature:70:5 [INFO] [stdout] ✔ And xs[1].t = 2 features/intersections.feature:71:5 [INFO] [stdout] [INFO] [stdout] Scenario: The hit, when all intersections have positive t [INFO] [stdout] features/intersections.feature:73:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:74:3 [INFO] [stdout] ✔ And i1 ← intersection(1, s) features/intersections.feature:75:5 [INFO] [stdout] ✔ And i2 ← intersection(2, s) features/intersections.feature:76:5 [INFO] [stdout] ✔ And xs ← intersections(i2, i1) features/intersections.feature:77:5 [INFO] [stdout] ✔ When i ← hit(xs) features/intersections.feature:78:3 [INFO] [stdout] ✔ Then i = i1 features/intersections.feature:79:3 [INFO] [stdout] [INFO] [stdout] Scenario: The hit, when some intersections have negative t [INFO] [stdout] features/intersections.feature:81:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:82:3 [INFO] [stdout] ✔ And i1 ← intersection(-1, s) features/intersections.feature:83:5 [INFO] [stdout] ✔ And i2 ← intersection(1, s) features/intersections.feature:84:5 [INFO] [stdout] ✔ And xs ← intersections(i2, i1) features/intersections.feature:85:5 [INFO] [stdout] ✔ When i ← hit(xs) features/intersections.feature:86:3 [INFO] [stdout] ✔ Then i = i2 features/intersections.feature:87:3 [INFO] [stdout] [INFO] [stdout] Scenario: The hit, when all intersections have negative t [INFO] [stdout] features/intersections.feature:89:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:90:3 [INFO] [stdout] ✔ And i1 ← intersection(-2, s) features/intersections.feature:91:5 [INFO] [stdout] ✔ And i2 ← intersection(-1, s) features/intersections.feature:92:5 [INFO] [stdout] ✔ And xs ← intersections(i2, i1) features/intersections.feature:93:5 [INFO] [stdout] ✔ When i ← hit(xs) features/intersections.feature:94:3 [INFO] [stdout] ✔ Then i is nothing features/intersections.feature:95:3 [INFO] [stdout] [INFO] [stdout] Scenario: The hit is always the lowest nonnegative intersection [INFO] [stdout] features/intersections.feature:97:11 [INFO] [stdout] ✔ Given s ← sphere() features/intersections.feature:98:3 [INFO] [stdout] ✔ And i1 ← intersection(5, s) features/intersections.feature:99:3 [INFO] [stdout] ✔ And i2 ← intersection(7, s) features/intersections.feature:100:3 [INFO] [stdout] ✔ And i3 ← intersection(-3, s) features/intersections.feature:101:3 [INFO] [stdout] ✔ And i4 ← intersection(2, s) features/intersections.feature:102:3 [INFO] [stdout] ✔ And xs ← intersections(i1, i2, i3, i4) features/intersections.feature:103:3 [INFO] [stdout] ✔ When i ← hit(xs) features/intersections.feature:104:1 [INFO] [stdout] ✔ Then i = i4 features/intersections.feature:105:1 [INFO] [stdout] [INFO] [stdout] Feature: Lights features/lights.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: A point light has a position and intensity [INFO] [stdout] features/lights.feature:3:11 [INFO] [stdout] ✔ Given intensity ← color(1, 1, 1) features/lights.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/lights.feature:5:5 [INFO] [stdout] ✔ When light ← point_light(position, intensity) features/lights.feature:6:3 [INFO] [stdout] ✔ Then light.position = position features/lights.feature:7:3 [INFO] [stdout] ✔ And light.intensity = intensity features/lights.feature:8:5 [INFO] [stdout] [INFO] [stdout] Feature: Materials features/materials.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: The default material features/materials.feature:7:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:8:3 [INFO] [stdout] ✔ Then m.color = color(1, 1, 1) features/materials.feature:9:3 [INFO] [stdout] ✔ And m.ambient = 0.1 features/materials.feature:10:5 [INFO] [stdout] ✔ And m.diffuse = 0.9 features/materials.feature:11:5 [INFO] [stdout] ✔ And m.specular = 0.9 features/materials.feature:12:5 [INFO] [stdout] ✔ And m.shininess = 200.0 features/materials.feature:13:5 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with the eye between the light and the surface [INFO] [stdout] features/materials.feature:24:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, 0, -1) features/materials.feature:25:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:26:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:27:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv) [INFO] [stdout] features/materials.feature:28:3 [INFO] [stdout] ✔ Then result = color(1.9, 1.9, 1.9) features/materials.feature:29:3 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with the eye between light and surface, eye offset 45° [INFO] [stdout] features/materials.feature:31:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, √2/2, -√2/2) features/materials.feature:32:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:33:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:34:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv) [INFO] [stdout] features/materials.feature:35:3 [INFO] [stdout] ✔ Then result = color(1.0, 1.0, 1.0) features/materials.feature:36:3 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with eye opposite surface, light offset 45° [INFO] [stdout] features/materials.feature:38:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, 0, -1) features/materials.feature:39:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:40:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 10, -10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:41:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv) [INFO] [stdout] features/materials.feature:42:3 [INFO] [stdout] ✔ Then result = color(0.7364, 0.7364, 0.7364) features/materials.feature:43:3 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with eye in the path of the reflection vector [INFO] [stdout] features/materials.feature:45:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, -√2/2, -√2/2) features/materials.feature:46:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:47:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 10, -10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:48:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv) [INFO] [stdout] features/materials.feature:49:3 [INFO] [stdout] ✔ Then result = color(1.63639, 1.63639, 1.63639) [INFO] [stdout] features/materials.feature:50:3 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with the light behind the surface [INFO] [stdout] features/materials.feature:52:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, 0, -1) features/materials.feature:53:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:54:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 0, 10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:55:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv) [INFO] [stdout] features/materials.feature:56:3 [INFO] [stdout] ✔ Then result = color(0.1, 0.1, 0.1) features/materials.feature:57:3 [INFO] [stdout] [INFO] [stdout] Scenario: Lighting with the surface in shadow features/materials.feature:59:11 [INFO] [stdout] ✔ Given m ← material() features/materials.feature:4:3 [INFO] [stdout] ✔ And position ← point(0, 0, 0) features/materials.feature:5:5 [INFO] [stdout] ✔ Given eyev ← vector(0, 0, -1) features/materials.feature:60:3 [INFO] [stdout] ✔ And normalv ← vector(0, 0, -1) features/materials.feature:61:5 [INFO] [stdout] ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1)) [INFO] [stdout] features/materials.feature:62:5 [INFO] [stdout] ✔ And in_shadow ← true features/materials.feature:63:5 [INFO] [stdout] ✔ When result ← lighting(m, light, position, eyev, normalv, in_shadow) [INFO] [stdout] features/materials.feature:64:3 [INFO] [stdout] ✔ Then result = color(0.1, 0.1, 0.1) features/materials.feature:65:3 [INFO] [stdout] [INFO] [stdout] Feature: Matrices features/matrices.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Constructing and inspecting a 4x4 matrix [INFO] [stdout] features/matrices.feature:3:11 [INFO] [stdout] ✔ Given the following 4x4 matrix M: features/matrices.feature:4:3 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 5.5 | 6.5 | 7.5 | 8.5 | [INFO] [stdout] | 9 | 10 | 11 | 12 | [INFO] [stdout] | 13.5 | 14.5 | 15.5 | 16.5 | [INFO] [stdout] ✔ Then M[0,0] = 1 features/matrices.feature:9:3 [INFO] [stdout] ✔ And M[0,3] = 4 features/matrices.feature:10:5 [INFO] [stdout] ✔ And M[1,0] = 5.5 features/matrices.feature:11:5 [INFO] [stdout] ✔ And M[1,2] = 7.5 features/matrices.feature:12:5 [INFO] [stdout] ✔ And M[2,2] = 11 features/matrices.feature:13:5 [INFO] [stdout] ✔ And M[3,0] = 13.5 features/matrices.feature:14:5 [INFO] [stdout] ✔ And M[3,2] = 15.5 features/matrices.feature:15:5 [INFO] [stdout] [INFO] [stdout] Scenario: A 2x2 matrix ought to be representable [INFO] [stdout] features/matrices.feature:17:11 [INFO] [stdout] ✔ Given the following 2x2 matrix M: features/matrices.feature:18:3 [INFO] [stdout] | -3 | 5 | [INFO] [stdout] | 1 | -2 | [INFO] [stdout] ✔ Then M[0,0] = -3 features/matrices.feature:21:3 [INFO] [stdout] ✔ And M[0,1] = 5 features/matrices.feature:22:5 [INFO] [stdout] ✔ And M[1,0] = 1 features/matrices.feature:23:5 [INFO] [stdout] ✔ And M[1,1] = -2 features/matrices.feature:24:5 [INFO] [stdout] [INFO] [stdout] Scenario: A 3x3 matrix ought to be representable [INFO] [stdout] features/matrices.feature:26:11 [INFO] [stdout] ✔ Given the following 3x3 matrix M: features/matrices.feature:27:3 [INFO] [stdout] | -3 | 5 | 0 | [INFO] [stdout] | 1 | -2 | -7 | [INFO] [stdout] | 0 | 1 | 1 | [INFO] [stdout] ✔ Then M[0,0] = -3 features/matrices.feature:31:3 [INFO] [stdout] ✔ And M[1,1] = -2 features/matrices.feature:32:5 [INFO] [stdout] ✔ And M[2,2] = 1 features/matrices.feature:33:5 [INFO] [stdout] [INFO] [stdout] Scenario: Matrix equality with identical matrices [INFO] [stdout] features/matrices.feature:35:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:36:3 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 5 | 6 | 7 | 8 | [INFO] [stdout] | 9 | 8 | 7 | 6 | [INFO] [stdout] | 5 | 4 | 3 | 2 | [INFO] [stdout] ✔ And the following matrix B: features/matrices.feature:41:5 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 5 | 6 | 7 | 8 | [INFO] [stdout] | 9 | 8 | 7 | 6 | [INFO] [stdout] | 5 | 4 | 3 | 2 | [INFO] [stdout] ✔ Then A = B features/matrices.feature:46:3 [INFO] [stdout] [INFO] [stdout] Scenario: Matrix equality with different matrices [INFO] [stdout] features/matrices.feature:48:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:49:3 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 5 | 6 | 7 | 8 | [INFO] [stdout] | 9 | 8 | 7 | 6 | [INFO] [stdout] | 5 | 4 | 3 | 2 | [INFO] [stdout] ✔ And the following matrix B: features/matrices.feature:54:5 [INFO] [stdout] | 2 | 3 | 4 | 5 | [INFO] [stdout] | 6 | 7 | 8 | 9 | [INFO] [stdout] | 8 | 7 | 6 | 5 | [INFO] [stdout] | 4 | 3 | 2 | 1 | [INFO] [stdout] ✔ Then A != B features/matrices.feature:59:3 [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying two matrices features/matrices.feature:61:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:62:3 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 5 | 6 | 7 | 8 | [INFO] [stdout] | 9 | 8 | 7 | 6 | [INFO] [stdout] | 5 | 4 | 3 | 2 | [INFO] [stdout] ✔ And the following matrix B: features/matrices.feature:67:5 [INFO] [stdout] | -2 | 1 | 2 | 3 | [INFO] [stdout] | 3 | 2 | 1 | -1 | [INFO] [stdout] | 4 | 3 | 6 | 5 | [INFO] [stdout] | 1 | 2 | 7 | 8 | [INFO] [stdout] ✔ Then A * B is the following 4x4 matrix: features/matrices.feature:72:3 [INFO] [stdout] | 20 | 22 | 50 | 48 | [INFO] [stdout] | 44 | 54 | 114 | 108 | [INFO] [stdout] | 40 | 58 | 110 | 102 | [INFO] [stdout] | 16 | 26 | 46 | 42 | [INFO] [stdout] [INFO] [stdout] Scenario: A matrix multiplied by a tuple features/matrices.feature:78:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:79:3 [INFO] [stdout] | 1 | 2 | 3 | 4 | [INFO] [stdout] | 2 | 4 | 4 | 2 | [INFO] [stdout] | 8 | 6 | 4 | 1 | [INFO] [stdout] | 0 | 0 | 0 | 1 | [INFO] [stdout] ✔ And b ← tuple(1, 2, 3, 1) features/matrices.feature:84:5 [INFO] [stdout] ✔ Then A * b = tuple(18, 24, 33, 1) features/matrices.feature:85:3 [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying a matrix by the identity matrix [INFO] [stdout] features/matrices.feature:87:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:88:3 [INFO] [stdout] | 0 | 1 | 2 | 4 | [INFO] [stdout] | 1 | 2 | 4 | 8 | [INFO] [stdout] | 2 | 4 | 8 | 16 | [INFO] [stdout] | 4 | 8 | 16 | 32 | [INFO] [stdout] ✔ Then A * identity_matrix = A features/matrices.feature:93:3 [INFO] [stdout] [INFO] [stdout] Scenario: Transposing a matrix features/matrices.feature:99:11 [INFO] [stdout] ✔ Given the following matrix A: features/matrices.feature:100:3 [INFO] [stdout] | 0 | 9 | 3 | 0 | [INFO] [stdout] | 9 | 8 | 0 | 8 | [INFO] [stdout] | 1 | 8 | 5 | 3 | [INFO] [stdout] | 0 | 0 | 5 | 8 | [INFO] [stdout] ✔ Then transpose(A) is the following matrix: features/matrices.feature:105:3 [INFO] [stdout] | 0 | 9 | 1 | 0 | [INFO] [stdout] | 9 | 8 | 8 | 0 | [INFO] [stdout] | 3 | 0 | 5 | 5 | [INFO] [stdout] | 0 | 8 | 3 | 8 | [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the determinant of a 2x2 matrix [INFO] [stdout] features/matrices.feature:115:11 [INFO] [stdout] ✔ Given the following 2x2 matrix A: features/matrices.feature:116:3 [INFO] [stdout] | 1 | 5 | [INFO] [stdout] | -3 | 2 | [INFO] [stdout] ✔ Then determinant(A) = 17 features/matrices.feature:119:3 [INFO] [stdout] [INFO] [stdout] Scenario: A submatrix of a 3x3 matrix is a 2x2 matrix [INFO] [stdout] features/matrices.feature:121:11 [INFO] [stdout] ✔ Given the following 3x3 matrix A: features/matrices.feature:122:3 [INFO] [stdout] | 1 | 5 | 0 | [INFO] [stdout] | -3 | 2 | 7 | [INFO] [stdout] | 0 | 6 | -3 | [INFO] [stdout] ✔ Then submatrix(A, 0, 2) is the following 2x2 matrix: [INFO] [stdout] features/matrices.feature:126:3 [INFO] [stdout] | -3 | 2 | [INFO] [stdout] | 0 | 6 | [INFO] [stdout] [INFO] [stdout] Scenario: A submatrix of a 4x4 matrix is a 3x3 matrix [INFO] [stdout] features/matrices.feature:130:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:131:3 [INFO] [stdout] | -6 | 1 | 1 | 6 | [INFO] [stdout] | -8 | 5 | 8 | 6 | [INFO] [stdout] | -1 | 0 | 8 | 2 | [INFO] [stdout] | -7 | 1 | -1 | 1 | [INFO] [stdout] ✔ Then submatrix(A, 2, 1) is the following 3x3 matrix: [INFO] [stdout] features/matrices.feature:136:3 [INFO] [stdout] | -6 | 1 | 6 | [INFO] [stdout] | -8 | 8 | 6 | [INFO] [stdout] | -7 | -1 | 1 | [INFO] [stdout] [INFO] [stdout] Scenario: Calculating a minor of a 3x3 matrix features/matrices.feature:141:11 [INFO] [stdout] ✔ Given the following 3x3 matrix A: features/matrices.feature:142:3 [INFO] [stdout] | 3 | 5 | 0 | [INFO] [stdout] | 2 | -1 | -7 | [INFO] [stdout] | 6 | -1 | 5 | [INFO] [stdout] ✔ And B ← submatrix(A, 1, 0) features/matrices.feature:146:5 [INFO] [stdout] ✔ Then determinant(B) = 25 features/matrices.feature:147:3 [INFO] [stdout] ✔ And minor(A, 1, 0) = 25 features/matrices.feature:148:5 [INFO] [stdout] [INFO] [stdout] Scenario: Calculating a cofactor of a 3x3 matrix [INFO] [stdout] features/matrices.feature:150:11 [INFO] [stdout] ✔ Given the following 3x3 matrix A: features/matrices.feature:151:3 [INFO] [stdout] | 3 | 5 | 0 | [INFO] [stdout] | 2 | -1 | -7 | [INFO] [stdout] | 6 | -1 | 5 | [INFO] [stdout] ✔ Then minor(A, 0, 0) = -12 features/matrices.feature:155:3 [INFO] [stdout] ✔ And cofactor(A, 0, 0) = -12 features/matrices.feature:156:5 [INFO] [stdout] ✔ And minor(A, 1, 0) = 25 features/matrices.feature:157:5 [INFO] [stdout] ✔ And cofactor(A, 1, 0) = -25 features/matrices.feature:158:5 [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the determinant of a 3x3 matrix [INFO] [stdout] features/matrices.feature:160:11 [INFO] [stdout] ✔ Given the following 3x3 matrix A: features/matrices.feature:161:3 [INFO] [stdout] | 1 | 2 | 6 | [INFO] [stdout] | -5 | 8 | -4 | [INFO] [stdout] | 2 | 6 | 4 | [INFO] [stdout] ✔ Then cofactor(A, 0, 0) = 56 features/matrices.feature:165:3 [INFO] [stdout] ✔ And cofactor(A, 0, 1) = 12 features/matrices.feature:166:5 [INFO] [stdout] ✔ And cofactor(A, 0, 2) = -46 features/matrices.feature:167:5 [INFO] [stdout] ✔ And determinant(A) = -196 features/matrices.feature:168:5 [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the determinant of a 4x4 matrix [INFO] [stdout] features/matrices.feature:170:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:171:3 [INFO] [stdout] | -2 | -8 | 3 | 5 | [INFO] [stdout] | -3 | 1 | 7 | 3 | [INFO] [stdout] | 1 | 2 | -9 | 6 | [INFO] [stdout] | -6 | 7 | 7 | -9 | [INFO] [stdout] ✔ Then cofactor(A, 0, 0) = 690 features/matrices.feature:176:3 [INFO] [stdout] ✔ And cofactor(A, 0, 1) = 447 features/matrices.feature:177:5 [INFO] [stdout] ✔ And cofactor(A, 0, 2) = 210 features/matrices.feature:178:5 [INFO] [stdout] ✔ And cofactor(A, 0, 3) = 51 features/matrices.feature:179:5 [INFO] [stdout] ✔ And determinant(A) = -4071 features/matrices.feature:180:5 [INFO] [stdout] [INFO] [stdout] Scenario: Testing an invertible matrix for invertibility [INFO] [stdout] features/matrices.feature:182:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:183:3 [INFO] [stdout] | 6 | 4 | 4 | 4 | [INFO] [stdout] | 5 | 5 | 7 | 6 | [INFO] [stdout] | 4 | -9 | 3 | -7 | [INFO] [stdout] | 9 | 1 | 7 | -6 | [INFO] [stdout] ✔ Then determinant(A) = -2120 features/matrices.feature:188:3 [INFO] [stdout] ✔ And A is invertible features/matrices.feature:189:5 [INFO] [stdout] [INFO] [stdout] Scenario: Testing a noninvertible matrix for invertibility [INFO] [stdout] features/matrices.feature:191:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:192:3 [INFO] [stdout] | -4 | 2 | -2 | -3 | [INFO] [stdout] | 9 | 6 | 2 | 6 | [INFO] [stdout] | 0 | -5 | 1 | -5 | [INFO] [stdout] | 0 | 0 | 0 | 0 | [INFO] [stdout] ✔ Then determinant(A) = 0 features/matrices.feature:197:3 [INFO] [stdout] ✔ And A is not invertible features/matrices.feature:198:5 [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the inverse of a matrix features/matrices.feature:200:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:201:3 [INFO] [stdout] | -5 | 2 | 6 | -8 | [INFO] [stdout] | 1 | -5 | 1 | 8 | [INFO] [stdout] | 7 | 7 | -6 | -7 | [INFO] [stdout] | 1 | -3 | 7 | 4 | [INFO] [stdout] ✔ And B ← inverse(A) features/matrices.feature:206:5 [INFO] [stdout] ✔ Then determinant(A) = 532 features/matrices.feature:207:3 [INFO] [stdout] ✔ And cofactor(A, 2, 3) = -160 features/matrices.feature:208:5 [INFO] [stdout] ✔ And B[3,2] = -160/532 features/matrices.feature:209:5 [INFO] [stdout] ✔ And cofactor(A, 3, 2) = 105 features/matrices.feature:210:5 [INFO] [stdout] ✔ And B[2,3] = 105/532 features/matrices.feature:211:5 [INFO] [stdout] ✔ And B is the following 4x4 matrix: features/matrices.feature:212:5 [INFO] [stdout] | 0.21805 | 0.45113 | 0.24060 | -0.04511 | [INFO] [stdout] | -0.80827 | -1.45677 | -0.44361 | 0.52068 | [INFO] [stdout] | -0.07895 | -0.22368 | -0.05263 | 0.19737 | [INFO] [stdout] | -0.52256 | -0.81391 | -0.30075 | 0.30639 | [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the inverse of another matrix [INFO] [stdout] features/matrices.feature:218:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:219:3 [INFO] [stdout] | 8 | -5 | 9 | 2 | [INFO] [stdout] | 7 | 5 | 6 | 1 | [INFO] [stdout] | -6 | 0 | 9 | 6 | [INFO] [stdout] | -3 | 0 | -9 | -4 | [INFO] [stdout] ✔ Then inverse(A) is the following 4x4 matrix: [INFO] [stdout] features/matrices.feature:224:3 [INFO] [stdout] | -0.15385 | -0.15385 | -0.28205 | -0.53846 | [INFO] [stdout] | -0.07692 | 0.12308 | 0.02564 | 0.03077 | [INFO] [stdout] | 0.35897 | 0.35897 | 0.43590 | 0.92308 | [INFO] [stdout] | -0.69231 | -0.69231 | -0.76923 | -1.92308 | [INFO] [stdout] [INFO] [stdout] Scenario: Calculating the inverse of a third matrix [INFO] [stdout] features/matrices.feature:230:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:231:3 [INFO] [stdout] | 9 | 3 | 0 | 9 | [INFO] [stdout] | -5 | -2 | -6 | -3 | [INFO] [stdout] | -4 | 9 | 6 | 4 | [INFO] [stdout] | -7 | 6 | 6 | 2 | [INFO] [stdout] ✔ Then inverse(A) is the following 4x4 matrix: [INFO] [stdout] features/matrices.feature:236:3 [INFO] [stdout] | -0.04074 | -0.07778 | 0.14444 | -0.22222 | [INFO] [stdout] | -0.07778 | 0.03333 | 0.36667 | -0.33333 | [INFO] [stdout] | -0.02901 | -0.14630 | -0.10926 | 0.12963 | [INFO] [stdout] | 0.17778 | 0.06667 | -0.26667 | 0.33333 | [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying a product by its inverse [INFO] [stdout] features/matrices.feature:242:11 [INFO] [stdout] ✔ Given the following 4x4 matrix A: features/matrices.feature:243:3 [INFO] [stdout] | 3 | -9 | 7 | 3 | [INFO] [stdout] | 3 | -8 | 2 | -9 | [INFO] [stdout] | -4 | 4 | 4 | 1 | [INFO] [stdout] | -6 | 5 | -1 | 1 | [INFO] [stdout] ✔ And the following 4x4 matrix B: features/matrices.feature:248:5 [INFO] [stdout] | 8 | 2 | 2 | 2 | [INFO] [stdout] | 3 | -1 | 7 | 0 | [INFO] [stdout] | 7 | 0 | 5 | 4 | [INFO] [stdout] | 6 | -2 | 0 | 5 | [INFO] [stdout] ✔ And C ← A * B features/matrices.feature:253:5 [INFO] [stdout] ✔ Then C * inverse(B) = A features/matrices.feature:254:3 [INFO] [stdout] [INFO] [stdout] Feature: Planes features/planes.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: The normal of a plane is constant everywhere [INFO] [stdout] features/planes.feature:3:11 [INFO] [stdout] ✔ Given p ← plane() features/planes.feature:4:3 [INFO] [stdout] ✔ When n1 ← local_normal_at(p, point(0, 0, 0)) features/planes.feature:5:3 [INFO] [stdout] ✔ And n2 ← local_normal_at(p, point(10, 0, -10)) features/planes.feature:6:5 [INFO] [stdout] ✔ And n3 ← local_normal_at(p, point(-5, 0, 150)) features/planes.feature:7:5 [INFO] [stdout] ✔ Then n1 = vector(0, 1, 0) features/planes.feature:8:3 [INFO] [stdout] ✔ And n2 = vector(0, 1, 0) features/planes.feature:9:5 [INFO] [stdout] ✔ And n3 = vector(0, 1, 0) features/planes.feature:10:5 [INFO] [stdout] [INFO] [stdout] Scenario: Intersect with a ray parallel to the plane [INFO] [stdout] features/planes.feature:12:11 [INFO] [stdout] ✔ Given p ← plane() features/planes.feature:13:3 [INFO] [stdout] ✔ And r ← ray(point(0, 10, 0), vector(0, 0, 1)) features/planes.feature:14:5 [INFO] [stdout] ✔ When xs ← local_intersect(p, r) features/planes.feature:15:3 [INFO] [stdout] ✔ Then xs is empty features/planes.feature:16:3 [INFO] [stdout] [INFO] [stdout] Scenario: Intersect with a coplanar ray features/planes.feature:18:11 [INFO] [stdout] ✔ Given p ← plane() features/planes.feature:19:3 [INFO] [stdout] ✔ And r ← ray(point(0, 0, 0), vector(0, 0, 1)) features/planes.feature:20:5 [INFO] [stdout] ✔ When xs ← local_intersect(p, r) features/planes.feature:21:3 [INFO] [stdout] ✔ Then xs is empty features/planes.feature:22:3 [INFO] [stdout] [INFO] [stdout] Scenario: A ray intersecting a plane from above features/planes.feature:24:11 [INFO] [stdout] ✔ Given p ← plane() features/planes.feature:25:3 [INFO] [stdout] ✔ And r ← ray(point(0, 1, 0), vector(0, -1, 0)) features/planes.feature:26:5 [INFO] [stdout] ✔ When xs ← local_intersect(p, r) features/planes.feature:27:3 [INFO] [stdout] ✔ Then xs.count = 1 features/planes.feature:28:3 [INFO] [stdout] ✔ And xs[0].t = 1 features/planes.feature:29:5 [INFO] [stdout] ✔ And xs[0].object = p features/planes.feature:30:5 [INFO] [stdout] [INFO] [stdout] Scenario: A ray intersecting a plane from below features/planes.feature:32:11 [INFO] [stdout] ✔ Given p ← plane() features/planes.feature:33:3 [INFO] [stdout] ✔ And r ← ray(point(0, -1, 0), vector(0, 1, 0)) features/planes.feature:34:5 [INFO] [stdout] ✔ When xs ← local_intersect(p, r) features/planes.feature:35:3 [INFO] [stdout] ✔ Then xs.count = 1 features/planes.feature:36:3 [INFO] [stdout] ✔ And xs[0].t = 1 features/planes.feature:37:5 [INFO] [stdout] ✔ And xs[0].object = p features/planes.feature:38:5 [INFO] [stdout] [INFO] [stdout] Feature: Rays features/rays.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Creating and querying a ray features/rays.feature:3:11 [INFO] [stdout] ✔ Given origin ← point(1, 2, 3) features/rays.feature:4:3 [INFO] [stdout] ✔ And direction ← vector(4, 5, 6) features/rays.feature:5:5 [INFO] [stdout] ✔ When r ← ray(origin, direction) features/rays.feature:6:3 [INFO] [stdout] ✔ Then r.origin = origin features/rays.feature:7:3 [INFO] [stdout] ✔ And r.direction = direction features/rays.feature:8:5 [INFO] [stdout] [INFO] [stdout] Scenario: Computing a point from a distance features/rays.feature:10:11 [INFO] [stdout] ✔ Given r ← ray(point(2, 3, 4), vector(1, 0, 0)) features/rays.feature:11:3 [INFO] [stdout] ✔ Then position(r, 0) = point(2, 3, 4) features/rays.feature:12:3 [INFO] [stdout] ✔ And position(r, 1) = point(3, 3, 4) features/rays.feature:13:5 [INFO] [stdout] ✔ And position(r, -1) = point(1, 3, 4) features/rays.feature:14:5 [INFO] [stdout] ✔ And position(r, 2.5) = point(4.5, 3, 4) features/rays.feature:15:5 [INFO] [stdout] [INFO] [stdout] Scenario: Translating a ray features/rays.feature:17:11 [INFO] [stdout] ✔ Given r ← ray(point(1, 2, 3), vector(0, 1, 0)) features/rays.feature:18:3 [INFO] [stdout] ✔ And m ← translation(3, 4, 5) features/rays.feature:19:5 [INFO] [stdout] ✔ When r2 ← transform(r, m) features/rays.feature:20:3 [INFO] [stdout] ✔ Then r2.origin = point(4, 6, 8) features/rays.feature:21:3 [INFO] [stdout] ✔ And r2.direction = vector(0, 1, 0) features/rays.feature:22:5 [INFO] [stdout] [INFO] [stdout] Scenario: Scaling a ray features/rays.feature:24:11 [INFO] [stdout] ✔ Given r ← ray(point(1, 2, 3), vector(0, 1, 0)) features/rays.feature:25:3 [INFO] [stdout] ✔ And m ← scaling(2, 3, 4) features/rays.feature:26:5 [INFO] [stdout] ✔ When r2 ← transform(r, m) features/rays.feature:27:3 [INFO] [stdout] ✔ Then r2.origin = point(2, 6, 12) features/rays.feature:28:3 [INFO] [stdout] ✔ And r2.direction = vector(0, 3, 0) features/rays.feature:29:5 [INFO] [stdout] [INFO] [stdout] Feature: Abstract Shapes features/shapes.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: The default transformation features/shapes.feature:3:11 [INFO] [stdout] ✔ Given s ← test_shape() features/shapes.feature:4:3 [INFO] [stdout] ✔ Then s.transform = identity_matrix features/shapes.feature:5:3 [INFO] [stdout] [INFO] [stdout] Scenario: Assigning a transformation features/shapes.feature:7:11 [INFO] [stdout] ✔ Given s ← test_shape() features/shapes.feature:8:3 [INFO] [stdout] ✔ When set_transform(s, translation(2, 3, 4)) features/shapes.feature:9:3 [INFO] [stdout] ✔ Then s.transform = translation(2, 3, 4) features/shapes.feature:10:3 [INFO] [stdout] [INFO] [stdout] Scenario: The default material features/shapes.feature:12:11 [INFO] [stdout] ✔ Given s ← test_shape() features/shapes.feature:13:3 [INFO] [stdout] ✔ When m ← s.material features/shapes.feature:14:3 [INFO] [stdout] ✔ Then m = material() features/shapes.feature:15:3 [INFO] [stdout] [INFO] [stdout] Scenario: Assigning a material features/shapes.feature:17:11 [INFO] [stdout] ✔ Given s ← test_shape() features/shapes.feature:18:3 [INFO] [stdout] ✔ And m ← material() features/shapes.feature:19:5 [INFO] [stdout] ✔ And m.ambient ← 1 features/shapes.feature:20:5 [INFO] [stdout] ✔ When s.material ← m features/shapes.feature:21:3 [INFO] [stdout] ✔ Then s.material = m features/shapes.feature:22:3 [INFO] [stdout] [INFO] [stdout] Feature: Spheres features/spheres.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: A ray intersects a sphere at two points [INFO] [stdout] features/spheres.feature:3:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:4:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:5:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:6:3 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:7:3 [INFO] [stdout] ✔ And xs[0].t = 4.0 features/spheres.feature:8:5 [INFO] [stdout] ✔ And xs[1].t = 6.0 features/spheres.feature:9:5 [INFO] [stdout] [INFO] [stdout] Scenario: A ray intersects a sphere at a tangent [INFO] [stdout] features/spheres.feature:11:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 1, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:12:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:13:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:14:3 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:15:3 [INFO] [stdout] ✔ And xs[0].t = 5.0 features/spheres.feature:16:5 [INFO] [stdout] ✔ And xs[1].t = 5.0 features/spheres.feature:17:5 [INFO] [stdout] [INFO] [stdout] Scenario: A ray misses a sphere features/spheres.feature:19:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 2, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:20:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:21:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:22:3 [INFO] [stdout] ✔ Then xs.count = 0 features/spheres.feature:23:3 [INFO] [stdout] [INFO] [stdout] Scenario: A ray originates inside a sphere features/spheres.feature:25:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, 0), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:26:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:27:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:28:3 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:29:3 [INFO] [stdout] ✔ And xs[0].t = -1.0 features/spheres.feature:30:5 [INFO] [stdout] ✔ And xs[1].t = 1.0 features/spheres.feature:31:5 [INFO] [stdout] [INFO] [stdout] Scenario: A sphere is behind a ray features/spheres.feature:33:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, 5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:34:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:35:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:36:3 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:37:3 [INFO] [stdout] ✔ And xs[0].t = -6.0 features/spheres.feature:38:5 [INFO] [stdout] ✔ And xs[1].t = -4.0 features/spheres.feature:39:5 [INFO] [stdout] [INFO] [stdout] Scenario: Intersect sets the object on the intersection [INFO] [stdout] features/spheres.feature:41:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:42:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:43:5 [INFO] [stdout] ✔ When xs ← intersect(s, r) features/spheres.feature:44:3 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:45:3 [INFO] [stdout] ✔ And xs[0].object = s features/spheres.feature:46:5 [INFO] [stdout] ✔ And xs[1].object = s features/spheres.feature:47:5 [INFO] [stdout] [INFO] [stdout] Scenario: A sphere's default transformation features/spheres.feature:49:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:50:3 [INFO] [stdout] ✔ Then s.transform = identity_matrix features/spheres.feature:51:3 [INFO] [stdout] [INFO] [stdout] Scenario: Changing a sphere's transformation features/spheres.feature:53:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:54:3 [INFO] [stdout] ✔ And t ← translation(2, 3, 4) features/spheres.feature:55:5 [INFO] [stdout] ✔ When set_transform(s, t) features/spheres.feature:56:3 [INFO] [stdout] ✔ Then s.transform = t features/spheres.feature:57:3 [INFO] [stdout] [INFO] [stdout] Scenario: Intersecting a scaled sphere with a ray [INFO] [stdout] features/spheres.feature:59:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:60:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:61:5 [INFO] [stdout] ✔ When set_transform(s, scaling(2, 2, 2)) features/spheres.feature:62:3 [INFO] [stdout] ✔ And xs ← intersect(s, r) features/spheres.feature:63:5 [INFO] [stdout] ✔ Then xs.count = 2 features/spheres.feature:64:3 [INFO] [stdout] ✔ And xs[0].t = 3 features/spheres.feature:65:5 [INFO] [stdout] ✔ And xs[1].t = 7 features/spheres.feature:66:5 [INFO] [stdout] [INFO] [stdout] Scenario: Intersecting a translated sphere with a ray [INFO] [stdout] features/spheres.feature:68:11 [INFO] [stdout] ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1)) [INFO] [stdout] features/spheres.feature:69:3 [INFO] [stdout] ✔ And s ← sphere() features/spheres.feature:70:5 [INFO] [stdout] ✔ When set_transform(s, translation(5, 0, 0)) features/spheres.feature:71:3 [INFO] [stdout] ✔ And xs ← intersect(s, r) features/spheres.feature:72:5 [INFO] [stdout] ✔ Then xs.count = 0 features/spheres.feature:73:3 [INFO] [stdout] [INFO] [stdout] Scenario: The normal on a sphere at a point on the x axis [INFO] [stdout] features/spheres.feature:75:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:76:3 [INFO] [stdout] ✔ When n ← normal_at(s, point(1, 0, 0)) features/spheres.feature:77:3 [INFO] [stdout] ✔ Then n = vector(1, 0, 0) features/spheres.feature:78:3 [INFO] [stdout] [INFO] [stdout] Scenario: The normal on a sphere at a point on the y axis [INFO] [stdout] features/spheres.feature:80:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:81:3 [INFO] [stdout] ✔ When n ← normal_at(s, point(0, 1, 0)) features/spheres.feature:82:3 [INFO] [stdout] ✔ Then n = vector(0, 1, 0) features/spheres.feature:83:3 [INFO] [stdout] [INFO] [stdout] Scenario: The normal on a sphere at a point on the z axis [INFO] [stdout] features/spheres.feature:85:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:86:3 [INFO] [stdout] ✔ When n ← normal_at(s, point(0, 0, 1)) features/spheres.feature:87:3 [INFO] [stdout] ✔ Then n = vector(0, 0, 1) features/spheres.feature:88:3 [INFO] [stdout] [INFO] [stdout] Scenario: The normal on a sphere at a nonaxial point [INFO] [stdout] features/spheres.feature:90:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:91:3 [INFO] [stdout] ✔ When n ← normal_at(s, point(√3/3, √3/3, √3/3)) [INFO] [stdout] features/spheres.feature:92:3 [INFO] [stdout] ✔ Then n = vector(√3/3, √3/3, √3/3) features/spheres.feature:93:3 [INFO] [stdout] [INFO] [stdout] Scenario: The normal is a normalized vector features/spheres.feature:95:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:96:3 [INFO] [stdout] ✔ When n ← normal_at(s, point(√3/3, √3/3, √3/3)) [INFO] [stdout] features/spheres.feature:97:3 [INFO] [stdout] ✔ Then n = normalize(n) features/spheres.feature:98:3 [INFO] [stdout] [INFO] [stdout] Scenario: Computing the normal on a translated sphere [INFO] [stdout] features/spheres.feature:100:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:101:3 [INFO] [stdout] ✔ And set_transform(s, translation(0, 1, 0)) features/spheres.feature:102:5 [INFO] [stdout] ✔ When n ← normal_at(s, point(0, 1.70711, -0.70711)) [INFO] [stdout] features/spheres.feature:103:3 [INFO] [stdout] ✔ Then n = vector(0, 0.70711, -0.70711) features/spheres.feature:104:3 [INFO] [stdout] [INFO] [stdout] Scenario: Computing the normal on a transformed sphere [INFO] [stdout] features/spheres.feature:106:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:107:3 [INFO] [stdout] ✔ And m ← scaling(1, 0.5, 1) * rotation_z(π/5) features/spheres.feature:108:5 [INFO] [stdout] ✔ And set_transform(s, m) features/spheres.feature:109:5 [INFO] [stdout] ✔ When n ← normal_at(s, point(0, √2/2, -√2/2)) features/spheres.feature:110:3 [INFO] [stdout] ✔ Then n = vector(0, 0.97014, -0.24254) features/spheres.feature:111:3 [INFO] [stdout] [INFO] [stdout] Scenario: A sphere has a default material features/spheres.feature:113:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:114:3 [INFO] [stdout] ✔ When m ← s.material features/spheres.feature:115:3 [INFO] [stdout] ✔ Then m = material() features/spheres.feature:116:3 [INFO] [stdout] [INFO] [stdout] Scenario: A sphere may be assigned a material features/spheres.feature:118:11 [INFO] [stdout] ✔ Given s ← sphere() features/spheres.feature:119:3 [INFO] [stdout] ✔ And m ← material() features/spheres.feature:120:5 [INFO] [stdout] ✔ And m.ambient ← 1 features/spheres.feature:121:5 [INFO] [stdout] ✔ When s.material ← m features/spheres.feature:122:3 [INFO] [stdout] ✔ Then s.material = m features/spheres.feature:123:3 [INFO] [stdout] [INFO] [stdout] Feature: Matrix Transformations features/transformations.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying by a translation matrix [INFO] [stdout] features/transformations.feature:3:11 [INFO] [stdout] ✔ Given transform ← translation(5, -3, 2) [INFO] [stdout] features/transformations.feature:4:3 [INFO] [stdout] ✔ And p ← point(-3, 4, 5) features/transformations.feature:5:5 [INFO] [stdout] ✔ Then transform * p = point(2, 1, 7) features/transformations.feature:6:4 [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying by the inverse of a translation matrix [INFO] [stdout] features/transformations.feature:8:11 [INFO] [stdout] ✔ Given transform ← translation(5, -3, 2) [INFO] [stdout] features/transformations.feature:9:3 [INFO] [stdout] ✔ And inv ← inverse(transform) features/transformations.feature:10:5 [INFO] [stdout] ✔ And p ← point(-3, 4, 5) features/transformations.feature:11:5 [INFO] [stdout] ✔ Then inv * p = point(-8, 7, 3) features/transformations.feature:12:4 [INFO] [stdout] [INFO] [stdout] Scenario: Translation does not affect vectors [INFO] [stdout] features/transformations.feature:14:11 [INFO] [stdout] ✔ Given transform ← translation(5, -3, 2) [INFO] [stdout] features/transformations.feature:15:3 [INFO] [stdout] ✔ And v ← vector(-3, 4, 5) features/transformations.feature:16:5 [INFO] [stdout] ✔ Then transform * v = v features/transformations.feature:17:4 [INFO] [stdout] [INFO] [stdout] Scenario: A scaling matrix applied to a point [INFO] [stdout] features/transformations.feature:19:11 [INFO] [stdout] ✔ Given transform ← scaling(2, 3, 4) features/transformations.feature:20:3 [INFO] [stdout] ✔ And p ← point(-4, 6, 8) features/transformations.feature:21:5 [INFO] [stdout] ✔ Then transform * p = point(-8, 18, 32) [INFO] [stdout] features/transformations.feature:22:4 [INFO] [stdout] [INFO] [stdout] Scenario: A scaling matrix applied to a vector [INFO] [stdout] features/transformations.feature:24:11 [INFO] [stdout] ✔ Given transform ← scaling(2, 3, 4) features/transformations.feature:25:3 [INFO] [stdout] ✔ And v ← vector(-4, 6, 8) features/transformations.feature:26:5 [INFO] [stdout] ✔ Then transform * v = vector(-8, 18, 32) [INFO] [stdout] features/transformations.feature:27:4 [INFO] [stdout] [INFO] [stdout] Scenario: Multiplying by the inverse of a scaling matrix [INFO] [stdout] features/transformations.feature:29:11 [INFO] [stdout] ✔ Given transform ← scaling(2, 3, 4) features/transformations.feature:30:3 [INFO] [stdout] ✔ And inv ← inverse(transform) features/transformations.feature:31:5 [INFO] [stdout] ✔ And v ← vector(-4, 6, 8) features/transformations.feature:32:5 [INFO] [stdout] ✔ Then inv * v = vector(-2, 2, 2) features/transformations.feature:33:4 [INFO] [stdout] [INFO] [stdout] Scenario: Reflection is scaling by a negative value [INFO] [stdout] features/transformations.feature:35:11 [INFO] [stdout] ✔ Given transform ← scaling(-1, 1, 1) features/transformations.feature:36:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:37:5 [INFO] [stdout] ✔ Then transform * p = point(-2, 3, 4) features/transformations.feature:38:4 [INFO] [stdout] [INFO] [stdout] Scenario: Rotating a point around the x axis [INFO] [stdout] features/transformations.feature:40:11 [INFO] [stdout] ✔ Given p ← point(0, 1, 0) features/transformations.feature:41:3 [INFO] [stdout] ✔ And half_quarter ← rotation_x(π / 4) features/transformations.feature:42:5 [INFO] [stdout] ✔ And full_quarter ← rotation_x(π / 2) features/transformations.feature:43:5 [INFO] [stdout] ✔ Then half_quarter * p = point(0, √2/2, √2/2) [INFO] [stdout] features/transformations.feature:44:3 [INFO] [stdout] ✔ And full_quarter * p = point(0, 0, 1) features/transformations.feature:45:5 [INFO] [stdout] [INFO] [stdout] Scenario: The inverse of an x-rotation rotates in the opposite direction [INFO] [stdout] features/transformations.feature:47:11 [INFO] [stdout] ✔ Given p ← point(0, 1, 0) features/transformations.feature:48:3 [INFO] [stdout] ✔ And half_quarter ← rotation_x(π / 4) features/transformations.feature:49:5 [INFO] [stdout] ✔ And inv ← inverse(half_quarter) features/transformations.feature:50:5 [INFO] [stdout] ✔ Then inv * p = point(0, √2/2, -√2/2) features/transformations.feature:51:3 [INFO] [stdout] [INFO] [stdout] Scenario: Rotating a point around the y axis [INFO] [stdout] features/transformations.feature:53:11 [INFO] [stdout] ✔ Given p ← point(0, 0, 1) features/transformations.feature:54:3 [INFO] [stdout] ✔ And half_quarter ← rotation_y(π / 4) features/transformations.feature:55:5 [INFO] [stdout] ✔ And full_quarter ← rotation_y(π / 2) features/transformations.feature:56:5 [INFO] [stdout] ✔ Then half_quarter * p = point(√2/2, 0, √2/2) [INFO] [stdout] features/transformations.feature:57:3 [INFO] [stdout] ✔ And full_quarter * p = point(1, 0, 0) features/transformations.feature:58:5 [INFO] [stdout] [INFO] [stdout] Scenario: Rotating a point around the z axis [INFO] [stdout] features/transformations.feature:60:11 [INFO] [stdout] ✔ Given p ← point(0, 1, 0) features/transformations.feature:61:3 [INFO] [stdout] ✔ And half_quarter ← rotation_z(π / 4) features/transformations.feature:62:5 [INFO] [stdout] ✔ And full_quarter ← rotation_z(π / 2) features/transformations.feature:63:5 [INFO] [stdout] ✔ Then half_quarter * p = point(-√2/2, √2/2, 0) [INFO] [stdout] features/transformations.feature:64:3 [INFO] [stdout] ✔ And full_quarter * p = point(-1, 0, 0) [INFO] [stdout] features/transformations.feature:65:5 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves x in proportion to y [INFO] [stdout] features/transformations.feature:67:11 [INFO] [stdout] ✔ Given transform ← shearing(1, 0, 0, 0, 0, 0) [INFO] [stdout] features/transformations.feature:68:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:69:5 [INFO] [stdout] ✔ Then transform * p = point(5, 3, 4) features/transformations.feature:70:3 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves x in proportion to z [INFO] [stdout] features/transformations.feature:72:11 [INFO] [stdout] ✔ Given transform ← shearing(0, 1, 0, 0, 0, 0) [INFO] [stdout] features/transformations.feature:73:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:74:5 [INFO] [stdout] ✔ Then transform * p = point(6, 3, 4) features/transformations.feature:75:3 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves y in proportion to x [INFO] [stdout] features/transformations.feature:77:11 [INFO] [stdout] ✔ Given transform ← shearing(0, 0, 1, 0, 0, 0) [INFO] [stdout] features/transformations.feature:78:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:79:5 [INFO] [stdout] ✔ Then transform * p = point(2, 5, 4) features/transformations.feature:80:3 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves y in proportion to z [INFO] [stdout] features/transformations.feature:82:11 [INFO] [stdout] ✔ Given transform ← shearing(0, 0, 0, 1, 0, 0) [INFO] [stdout] features/transformations.feature:83:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:84:5 [INFO] [stdout] ✔ Then transform * p = point(2, 7, 4) features/transformations.feature:85:3 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves z in proportion to x [INFO] [stdout] features/transformations.feature:87:11 [INFO] [stdout] ✔ Given transform ← shearing(0, 0, 0, 0, 1, 0) [INFO] [stdout] features/transformations.feature:88:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:89:5 [INFO] [stdout] ✔ Then transform * p = point(2, 3, 6) features/transformations.feature:90:3 [INFO] [stdout] [INFO] [stdout] Scenario: A shearing transformation moves z in proportion to y [INFO] [stdout] features/transformations.feature:92:11 [INFO] [stdout] ✔ Given transform ← shearing(0, 0, 0, 0, 0, 1) [INFO] [stdout] features/transformations.feature:93:3 [INFO] [stdout] ✔ And p ← point(2, 3, 4) features/transformations.feature:94:5 [INFO] [stdout] ✔ Then transform * p = point(2, 3, 7) features/transformations.feature:95:3 [INFO] [stdout] [INFO] [stdout] Scenario: Individual transformations are applied in sequence [INFO] [stdout] features/transformations.feature:97:11 [INFO] [stdout] ✔ Given p ← point(1, 0, 1) features/transformations.feature:98:3 [INFO] [stdout] ✔ And A ← rotation_x(π / 2) features/transformations.feature:99:5 [INFO] [stdout] ✔ And B ← scaling(5, 5, 5) features/transformations.feature:100:5 [INFO] [stdout] ✔ And C ← translation(10, 5, 7) features/transformations.feature:101:5 [INFO] [stdout] ✔ When p2 ← A * p features/transformations.feature:103:3 [INFO] [stdout] ✔ Then p2 = point(1, -1, 0) features/transformations.feature:104:3 [INFO] [stdout] ✔ When p3 ← B * p2 features/transformations.feature:106:3 [INFO] [stdout] ✔ Then p3 = point(5, -5, 0) features/transformations.feature:107:3 [INFO] [stdout] ✔ When p4 ← C * p3 features/transformations.feature:109:3 [INFO] [stdout] ✔ Then p4 = point(15, 0, 7) features/transformations.feature:110:3 [INFO] [stdout] [INFO] [stdout] Scenario: Chained transformations must be applied in reverse order [INFO] [stdout] features/transformations.feature:112:11 [INFO] [stdout] ✔ Given p ← point(1, 0, 1) features/transformations.feature:113:3 [INFO] [stdout] ✔ And A ← rotation_x(π / 2) features/transformations.feature:114:5 [INFO] [stdout] ✔ And B ← scaling(5, 5, 5) features/transformations.feature:115:5 [INFO] [stdout] ✔ And C ← translation(10, 5, 7) features/transformations.feature:116:5 [INFO] [stdout] ✔ When T ← C * B * A features/transformations.feature:117:3 [INFO] [stdout] ✔ Then T * p = point(15, 0, 7) features/transformations.feature:118:3 [INFO] [stdout] [INFO] [stdout] Scenario: The transformation matrix for the default orientation [INFO] [stdout] features/transformations.feature:120:11 [INFO] [stdout] ✔ Given from ← point(0, 0, 0) features/transformations.feature:121:3 [INFO] [stdout] ✔ And to ← point(0, 0, -1) features/transformations.feature:122:5 [INFO] [stdout] ✔ And up ← vector(0, 1, 0) features/transformations.feature:123:5 [INFO] [stdout] ✔ When t ← view_transform(from, to, up) [INFO] [stdout] features/transformations.feature:124:3 [INFO] [stdout] ✔ Then t = identity_matrix features/transformations.feature:125:3 [INFO] [stdout] [INFO] [stdout] Scenario: A view transformation matrix looking in positive z direction [INFO] [stdout] features/transformations.feature:127:11 [INFO] [stdout] ✔ Given from ← point(0, 0, 0) features/transformations.feature:128:3 [INFO] [stdout] ✔ And to ← point(0, 0, 1) features/transformations.feature:129:5 [INFO] [stdout] ✔ And up ← vector(0, 1, 0) features/transformations.feature:130:5 [INFO] [stdout] ✔ When t ← view_transform(from, to, up) [INFO] [stdout] features/transformations.feature:131:3 [INFO] [stdout] ✔ Then t = scaling(-1, 1, -1) features/transformations.feature:132:3 [INFO] [stdout] [INFO] [stdout] Scenario: The view transformation moves the world [INFO] [stdout] features/transformations.feature:134:11 [INFO] [stdout] ✔ Given from ← point(0, 0, 8) features/transformations.feature:135:3 [INFO] [stdout] ✔ And to ← point(0, 0, 0) features/transformations.feature:136:5 [INFO] [stdout] ✔ And up ← vector(0, 1, 0) features/transformations.feature:137:5 [INFO] [stdout] ✔ When t ← view_transform(from, to, up) [INFO] [stdout] features/transformations.feature:138:3 [INFO] [stdout] ✔ Then t = translation(0, 0, -8) features/transformations.feature:139:3 [INFO] [stdout] [INFO] [stdout] Scenario: An arbitrary view transformation [INFO] [stdout] features/transformations.feature:141:11 [INFO] [stdout] ✔ Given from ← point(1, 3, 2) features/transformations.feature:142:3 [INFO] [stdout] ✔ And to ← point(4, -2, 8) features/transformations.feature:143:5 [INFO] [stdout] ✔ And up ← vector(1, 1, 0) features/transformations.feature:144:5 [INFO] [stdout] ✔ When t ← view_transform(from, to, up) [INFO] [stdout] features/transformations.feature:145:3 [INFO] [stdout] ✔ Then t is the following 4x4 matrix: features/transformations.feature:146:3 [INFO] [stdout] | -0.50709 | 0.50709 | 0.67612 | -2.36643 | [INFO] [stdout] | 0.76772 | 0.60609 | 0.12122 | -2.82843 | [INFO] [stdout] | -0.35857 | 0.59761 | -0.71714 | 0.00000 | [INFO] [stdout] | 0.00000 | 0.00000 | 0.00000 | 1.00000 | [INFO] [stdout] [INFO] [stdout] Feature: Tuples, Vectors, and Points features/tuples.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Reflecting a vector approaching at 45° [INFO] [stdout] features/tuples.feature:140:11 [INFO] [stdout] ✔ Given v ← vector(1, -1, 0) features/tuples.feature:141:3 [INFO] [stdout] ✔ And n ← vector(0, 1, 0) features/tuples.feature:142:5 [INFO] [stdout] ✔ When r ← reflect(v, n) features/tuples.feature:143:3 [INFO] [stdout] ✔ Then r = vector(1, 1, 0) features/tuples.feature:144:3 [INFO] [stdout] [INFO] [stdout] Scenario: Reflecting a vector off a slanted surface [INFO] [stdout] features/tuples.feature:146:11 [INFO] [stdout] ✔ Given v ← vector(0, -1, 0) features/tuples.feature:147:3 [INFO] [stdout] ✔ And n ← vector(√2/2, √2/2, 0) features/tuples.feature:148:5 [INFO] [stdout] ✔ When r ← reflect(v, n) features/tuples.feature:149:3 [INFO] [stdout] ✔ Then r = vector(1, 0, 0) features/tuples.feature:150:3 [INFO] [stdout] [INFO] [stdout] Feature: World features/world.feature:1:1 [INFO] [stdout] [INFO] [stdout] Scenario: Creating a world features/world.feature:3:11 [INFO] [stdout] ✔ Given w ← world() features/world.feature:4:3 [INFO] [stdout] ✔ Then w contains no objects features/world.feature:5:3 [INFO] [stdout] ✔ And w has no light source features/world.feature:6:5 [INFO] [stdout] [INFO] [stdout] Scenario: The default world features/world.feature:8:11 [INFO] [stdout] ✔ Given light ← point_light(point(-10, 10, -10), color(1, 1, 1)) [INFO] [stdout] features/world.feature:9:3 [INFO] [stdout] ✔ And s1 ← sphere() with: features/world.feature:10:5 [INFO] [stdout] | material.color | (0.8, 1.0, 0.6) | [INFO] [stdout] | material.diffuse | 0.7 | [INFO] [stdout] | material.specular | 0.2 | [INFO] [stdout] ✔ And s2 ← sphere() with: features/world.feature:14:5 [INFO] [stdout] | | | [INFO] [stdout] | transform | scaling(0.5, 0.5, 0.5) | [INFO] [stdout] ✔ When w ← default_world() features/world.feature:16:3 [INFO] [stdout] ✔ Then w.light = light features/world.feature:17:3 [INFO] [stdout] ✔ And w contains s1 features/world.feature:18:5 [INFO] [stdout] ✔ And w contains s2 features/world.feature:19:5 [INFO] [stdout] [INFO] [stdout] Scenario: Intersect a world with a ray features/world.feature:21:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:22:3 [INFO] [stdout] ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1)) features/world.feature:23:5 [INFO] [stdout] ✔ When xs ← intersect_world(w, r) features/world.feature:24:3 [INFO] [stdout] ✔ Then xs.count = 4 features/world.feature:25:3 [INFO] [stdout] ✔ And xs[0].t = 4 features/world.feature:26:5 [INFO] [stdout] ✔ And xs[1].t = 4.5 features/world.feature:27:5 [INFO] [stdout] ✔ And xs[2].t = 5.5 features/world.feature:28:5 [INFO] [stdout] ✔ And xs[3].t = 6 features/world.feature:29:5 [INFO] [stdout] [INFO] [stdout] Scenario: Shading an intersection features/world.feature:31:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:32:3 [INFO] [stdout] ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1)) features/world.feature:33:5 [INFO] [stdout] ✔ And shape ← the first object in w features/world.feature:34:5 [INFO] [stdout] ✔ And i ← intersection(4, shape) features/world.feature:35:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/world.feature:36:3 [INFO] [stdout] ✔ And c ← shade_hit(w, comps) features/world.feature:37:5 [INFO] [stdout] ✔ Then c = color(0.38066, 0.47583, 0.2855) features/world.feature:38:3 [INFO] [stdout] [INFO] [stdout] Scenario: Shading an intersection from the inside features/world.feature:40:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:41:3 [INFO] [stdout] ✔ And w.light ← point_light(point(0, 0.25, 0), color(1, 1, 1)) [INFO] [stdout] features/world.feature:42:5 [INFO] [stdout] ✔ And r ← ray(point(0, 0, 0), vector(0, 0, 1)) features/world.feature:43:5 [INFO] [stdout] ✔ And shape ← the second object in w features/world.feature:44:5 [INFO] [stdout] ✔ And i ← intersection(0.5, shape) features/world.feature:45:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/world.feature:46:3 [INFO] [stdout] ✔ And c ← shade_hit(w, comps) features/world.feature:47:5 [INFO] [stdout] ✔ Then c = color(0.90498, 0.90498, 0.90498) features/world.feature:48:3 [INFO] [stdout] [INFO] [stdout] Scenario: The color when a ray misses features/world.feature:50:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:51:3 [INFO] [stdout] ✔ And r ← ray(point(0, 0, -5), vector(0, 1, 0)) features/world.feature:52:5 [INFO] [stdout] ✔ When c ← color_at(w, r) features/world.feature:53:3 [INFO] [stdout] ✔ Then c = color(0, 0, 0) features/world.feature:54:3 [INFO] [stdout] [INFO] [stdout] Scenario: The color when a ray hits features/world.feature:56:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:57:3 [INFO] [stdout] ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1)) features/world.feature:58:5 [INFO] [stdout] ✔ When c ← color_at(w, r) features/world.feature:59:3 [INFO] [stdout] ✔ Then c = color(0.38066, 0.47583, 0.2855) features/world.feature:60:3 [INFO] [stdout] [INFO] [stdout] Scenario: The color with an intersection behind the ray [INFO] [stdout] features/world.feature:62:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:63:3 [INFO] [stdout] ✔ And outer ← the first object in w features/world.feature:64:5 [INFO] [stdout] ✔ And outer.material.ambient ← 1 features/world.feature:65:5 [INFO] [stdout] ✔ And inner ← the second object in w features/world.feature:66:5 [INFO] [stdout] ✔ And inner.material.ambient ← 1 features/world.feature:67:5 [INFO] [stdout] ✔ And r ← ray(point(0, 0, 0.75), vector(0, 0, -1)) [INFO] [stdout] features/world.feature:68:5 [INFO] [stdout] ✔ When c ← color_at(w, r) features/world.feature:69:3 [INFO] [stdout] ✔ Then c = inner.material.color features/world.feature:70:3 [INFO] [stdout] [INFO] [stdout] Scenario: There is no shadow when nothing is collinear with point and light [INFO] [stdout] features/world.feature:72:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:73:3 [INFO] [stdout] ✔ And p ← point(0, 10, 0) features/world.feature:74:5 [INFO] [stdout] ✔ Then is_shadowed(w, p) is false features/world.feature:75:4 [INFO] [stdout] [INFO] [stdout] Scenario: The shadow when an object is between the point and the light [INFO] [stdout] features/world.feature:77:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:78:3 [INFO] [stdout] ✔ And p ← point(10, -10, 10) features/world.feature:79:5 [INFO] [stdout] ✔ Then is_shadowed(w, p) is true features/world.feature:80:4 [INFO] [stdout] [INFO] [stdout] Scenario: There is no shadow when an object is behind the light [INFO] [stdout] features/world.feature:82:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:83:3 [INFO] [stdout] ✔ And p ← point(-20, 20, -20) features/world.feature:84:5 [INFO] [stdout] ✔ Then is_shadowed(w, p) is false features/world.feature:85:4 [INFO] [stdout] [INFO] [stdout] Scenario: There is no shadow when an object is behind the point [INFO] [stdout] features/world.feature:87:11 [INFO] [stdout] ✔ Given w ← default_world() features/world.feature:88:3 [INFO] [stdout] ✔ And p ← point(-2, 2, -2) features/world.feature:89:5 [INFO] [stdout] ✔ Then is_shadowed(w, p) is false features/world.feature:90:4 [INFO] [stdout] [INFO] [stdout] Scenario: shade_hit() is given an intersection in shadow [INFO] [stdout] features/world.feature:92:11 [INFO] [stdout] ✔ Given w ← world() features/world.feature:93:3 [INFO] [stdout] ✔ And w.light ← point_light(point(0, 0, -10), color(1, 1, 1)) [INFO] [stdout] features/world.feature:94:5 [INFO] [stdout] ✔ And s1 ← sphere() features/world.feature:95:5 [INFO] [stdout] ✔ And s1 is added to w features/world.feature:96:5 [INFO] [stdout] ✔ And s2 ← sphere() with: features/world.feature:97:5 [INFO] [stdout] | | | [INFO] [stdout] | transform | translation(0, 0, 10) | [INFO] [stdout] ✔ And s2 is added to w features/world.feature:99:5 [INFO] [stdout] ✔ And r ← ray(point(0, 0, 5), vector(0, 0, 1)) features/world.feature:100:5 [INFO] [stdout] ✔ And i ← intersection(4, s2) features/world.feature:101:5 [INFO] [stdout] ✔ When comps ← prepare_computations(i, r) features/world.feature:102:3 [INFO] [stdout] ✔ And c ← shade_hit(w, comps) features/world.feature:103:5 [INFO] [stdout] ✔ Then c = color(0.1, 0.1, 0.1) features/world.feature:104:3 [INFO] [stdout] [INFO] [stdout] 13 features [INFO] [stdout] 118 scenarios (118 passed) [INFO] [stdout] 558 steps (558 passed) [INFO] [stdout] [INFO] [stderr] Doc-tests ray_tracer_challenge [INFO] [stderr] warning: unnecessary braces around method argument [INFO] [stderr] --> /opt/rustwide/workdir/src/lib.rs:86:30 [INFO] [stderr] | [INFO] [stderr] 86 | self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stderr] | ^^ ^^ [INFO] [stderr] | [INFO] [stderr] = note: `#[warn(unused_braces)]` on by default [INFO] [stderr] help: remove these braces [INFO] [stderr] | [INFO] [stderr] 86 - self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect() [INFO] [stderr] 86 + self.objs.iter().map(|o| Rc::new(o.clone())).collect() [INFO] [stderr] | [INFO] [stderr] [INFO] [stderr] warning: 1 warning emitted [INFO] [stderr] [INFO] [stdout] [INFO] [stdout] running 0 tests [INFO] [stdout] [INFO] [stdout] test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s [INFO] [stdout] [INFO] running `Command { std: "docker" "inspect" "320b1c2963e4e6044e108935799f5444b89fc8c4073e71a539d5e661d42e87ef", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "320b1c2963e4e6044e108935799f5444b89fc8c4073e71a539d5e661d42e87ef", kill_on_drop: false }` [INFO] [stdout] 320b1c2963e4e6044e108935799f5444b89fc8c4073e71a539d5e661d42e87ef