Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Sandbox

Post History

60%
+1 −0
Sandbox Plain ​​spheres

posted 5mo ago by trichoplax‭  ·  edited 2mo ago by trichoplax‭

#20: Post edited by user avatar trichoplax‭ · 2024-10-12T22:00:57Z (about 2 months ago)
Add TODO for checking choice of sphere makes little difference
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • - You may assume that all ray intersection points (not just nearest intersections) will have a $z$ value greater than zero, and less than $1024$.
  • ```text
  • TODO: Adjust test cases to fit within this specified z range
  • ```
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If $2$ or more spheres have an intersection point with the smallest $z$ coordinate $d$ then you may set the pixel to the greyscale value of any of these spheres.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • - You may assume that all ray intersection points (not just nearest intersections) will have a $z$ value greater than zero, and less than $1024$.
  • ```text
  • TODO: Adjust test cases to fit within this specified z range
  • ```
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If $2$ or more spheres have an intersection point with the smallest $z$ coordinate $d$ then you may set the pixel to the greyscale value of any of these spheres.
  • ```text
  • TODO: generate output images for each test case
  • both ways to confirm that this makes less than
  • a pixel's width difference.
  • ```
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#19: Post edited by user avatar trichoplax‭ · 2024-10-12T18:51:03Z (about 2 months ago)
Specify z range
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If $2$ or more spheres have an intersection point with the smallest $z$ coordinate $d$ then you may set the pixel to the greyscale value of any of these spheres.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • - You may assume that all ray intersection points (not just nearest intersections) will have a $z$ value greater than zero, and less than $1024$.
  • ```text
  • TODO: Adjust test cases to fit within this specified z range
  • ```
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If $2$ or more spheres have an intersection point with the smallest $z$ coordinate $d$ then you may set the pixel to the greyscale value of any of these spheres.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#18: Post edited by user avatar trichoplax‭ · 2024-10-12T18:37:54Z (about 2 months ago)
Specify what happens when 2 or more intersections are nearest
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If $2$ or more spheres have an intersection point with the smallest $z$ coordinate $d$ then you may set the pixel to the greyscale value of any of these spheres.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#17: Post edited by user avatar trichoplax‭ · 2024-08-09T18:47:25Z (4 months ago)
Collapse example implementations because they are optional
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • <details>
  • <summary>Python</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • </details>
  • <details>
  • <summary>Rust</summary>
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • </details>
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#16: Post edited by user avatar trichoplax‭ · 2024-08-09T18:30:57Z (4 months ago)
Separate accuracy section from test cases section
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • Output using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding and subtracting 1000 to the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Accuracy
  • The outputs for the test cases were generated using 64 bit floating point numbers. The following is the last test case using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding 1000 to and then subtracting 1000 from the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#15: Post edited by user avatar trichoplax‭ · 2024-07-26T15:01:16Z (4 months ago)
Include example image with too much inaccuracy
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • Output using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • Output using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball. These occasional inaccurate pixels are acceptable in your output (allowing languages which only support 32 bit floating point to compete).
  • More widespread speckling as in the following image would not be valid, so you may need to be careful if changing the order of calculations during golfing:
  • [![A scene with significant areas of inaccurate speckles obscuring the intended image](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)](https://codegolf.codidact.com/uploads/1sa7i6z0cv2kgyrmw2tj2ytr1ps1)
  • This exaggerated inaccuracy was achieved by adding and subtracting 1000 to the distance calculation, exceeding the available precision of 32 bit floating point and causing some distances to appear in the wrong order. Note that this same approach causes no inaccuracy with 64 bit floating point, so if your language supports both you may have more leeway for rearranging calculations if you use 64 bit floating point.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#14: Post edited by user avatar trichoplax‭ · 2024-07-26T14:33:50Z (4 months ago)
Add 32 bit floating point comparison output for final test case
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • Output using 32 bit floating point rather than 64 bit floating point:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)](https://codegolf.codidact.com/uploads/7uuxjxq7a9oxc9q0jf2bi8l68qk3)
  • The inaccuracies in the 32 bit floating point version are subtle. You may need to click on the image to open it full size to see the occasional inaccurate pixel. For example, around the inside of the holes in the 8 on the eightball.
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#13: Post edited by user avatar trichoplax‭ · 2024-07-26T13:14:40Z (4 months ago)
Clarify wordings
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the output as a PNG file.
  • The `png` crate can be installed using `cargo add png`. The `png` crate is not required for the `grey_values` function, only for saving as a PNG file.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit this page. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#12: Post edited by user avatar trichoplax‭ · 2024-07-26T12:43:44Z (4 months ago)
Remove negative zero from test case inputs
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, -0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [-0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, -0, 1, 85],
  • [0, 0.001, -0, 1, 106],
  • [-0.000707, 0.000707, -0, 1, 127],
  • [-0.001, 0, -0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [-0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [-0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [-0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, -0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, 0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, 0, 1, 85],
  • [0, 0.001, 0, 1, 106],
  • [-0.000707, 0.000707, 0, 1, 127],
  • [-0.001, 0, 0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, 0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#11: Post edited by user avatar trichoplax‭ · 2024-07-26T12:38:58Z (4 months ago)
Add back in animation section now that there is more space
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, -0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [-0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, -0, 1, 85],
  • [0, 0.001, -0, 1, 106],
  • [-0.000707, 0.000707, -0, 1, 127],
  • [-0.001, 0, -0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [-0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [-0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [-0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, -0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, -0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [-0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, -0, 1, 85],
  • [0, 0.001, -0, 1, 106],
  • [-0.000707, 0.000707, -0, 1, 127],
  • [-0.001, 0, -0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [-0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [-0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [-0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, -0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Explanatory animations
  • The example images were produced from plain grey spheres with no surface pattern or texture. The following animations show how they break down into their component plain spheres.
  • <details>
  • <summary>Animations</summary>
  • TODO
  • </details>
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#10: Post edited by user avatar trichoplax‭ · 2024-07-26T12:34:36Z (4 months ago)
Make test case images links to full size
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • ![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • ![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, -0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [-0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • ![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, -0, 1, 85],
  • [0, 0.001, -0, 1, 106],
  • [-0.000707, 0.000707, -0, 1, 127],
  • [-0.001, 0, -0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [-0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • ![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • ![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [-0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [-0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • ![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, -0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • ![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • ![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • values = grey_values(test_case)
  • rows = [values[row * 1024:row * 1024 + 1024] for row in range(1024)]
  • with open('combination_scene.png', 'wb') as file:
  • w = png.Writer(1024, 1024, greyscale=True)
  • w.write(file, rows)
  • ```
  • ### Rust
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files.
  • The `png` crate can be installed using `cargo add png`.
  • ```rust
  • fn grey_values(spheres: &Vec<[f32; 5]>) -> Vec<u8> {
  • let mut values = vec![];
  • for y_pixel in 0..1024 {
  • for x_pixel in 0..1024 {
  • let x_ray = x_pixel as f32 / 512.0 - 1.0;
  • let y_ray = y_pixel as f32 / 512.0 - 1.0;
  • let mut min_distance = f32::INFINITY;
  • let mut hit_grey_value = 0;
  • for sphere in spheres {
  • let [
  • x_sphere, y_sphere, z_sphere, radius, grey
  • ] = *sphere;
  • let a = (
  • (x_ray - x_sphere).powf(2.0)
  • + (y_ray - y_sphere).powf(2.0)
  • ).sqrt();
  • if a < radius {
  • let distance = z_sphere - (
  • radius.powf(2.0) - a.powf(2.0)
  • ).sqrt();
  • if distance < min_distance {
  • min_distance = distance;
  • hit_grey_value = grey as u8;
  • }
  • }
  • }
  • values.push(hit_grey_value);
  • }
  • }
  • values
  • }
  • fn main() {
  • let test_case: Vec<[f32; 5]> = vec![
  • [0.0, 0.0, 1000.0, 2.0, 192.0],
  • [-2.0, 2.0, 100.0, 3.0, 98.0],
  • [2.0, 2.0, 100.0, 3.0, 98.0],
  • [0.5, -0.5, 500.0, 0.2, 255.0],
  • [-0.349469, -0.1, 20.000279, 0.6, 64.0],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85.0],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106.0],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127.0],
  • [-0.350531, -0.1, 19.999721, 0.6, 148.0],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169.0],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190.0],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211.0],
  • [0.20032, 0.349824, 15.000456, 0.3, 0.0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0.0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0.0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0.0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0.0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0.0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0.0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0.0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0.0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0.0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0.0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0.0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255.0],
  • [0.199571, 0.349795, 15.000366, 0.3, 255.0],
  • [0.200429, 0.350205, 14.999634, 0.3, 255.0],
  • [0.199571, 0.349581, 14.999995, 0.3, 255.0],
  • [0.200524, 0.349747, 15.000146, 0.3, 255.0],
  • [0.200196, 0.349509, 15.000283, 0.3, 255.0],
  • [0.199804, 0.350491, 14.999717, 0.3, 255.0],
  • [0.199476, 0.350253, 14.999854, 0.3, 255.0],
  • [0.200138, 0.349578, 14.999597, 0.3, 255.0],
  • [0.200138, 0.350138, 15.000567, 0.3, 255.0],
  • [0.199862, 0.349862, 14.999433, 0.3, 255.0],
  • [0.199862, 0.350422, 15.000403, 0.3, 255.0],
  • [0.200488, 0.350136, 15.000321, 0.3, 255.0],
  • [0.199957, 0.349751, 15.000544, 0.3, 255.0],
  • [0.200488, 0.34979, 14.999721, 0.3, 255.0],
  • [0.199957, 0.349404, 14.999944, 0.3, 255.0],
  • [0.200043, 0.350596, 15.000056, 0.3, 255.0],
  • [0.199512, 0.35021, 15.000279, 0.3, 255.0],
  • [0.200043, 0.350249, 14.999456, 0.3, 255.0],
  • [0.199512, 0.349864, 14.999679, 0.3, 255.0],
  • [-0.5, 0.500442, 10.000442, 0.2, 0.0],
  • [-0.5, 0.5, 10.0, 0.1995, 255.0],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0.0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0.0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255.0],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255.0],
  • [0.6, 0.5, 10.0, 0.25, 210.0],
  • [0.600122, 0.4999, 10.000122, 0.25, 210.0],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50.0],
  • [0.595856, 0.505256, 9.991271, 0.24, 100.0],
  • [0.597973, 0.507461, 9.992175, 0.24, 210.0],
  • [0.595362, 0.507926, 9.993945, 0.24, 100.0],
  • [0.594618, 0.509055, 9.996832, 0.24, 210.0],
  • [0.592382, 0.506906, 9.996092, 0.24, 100.0],
  • [0.590438, 0.504882, 9.997604, 0.24, 210.0],
  • [0.591035, 0.503605, 9.994744, 0.24, 100.0],
  • [0.59121, 0.500708, 9.993425, 0.24, 210.0],
  • [0.593181, 0.502585, 9.991764, 0.24, 100.0],
  • [0.595867, 0.502302, 9.990069, 0.24, 210.0],
  • ];
  • let values = grey_values(&test_case);
  • let path = std::path::Path::new("combination_scene.png");
  • let file = std::fs::File::create(path).unwrap();
  • let w = &mut std::io::BufWriter::new(file);
  • let mut encoder = png::Encoder::new(w, 1024, 1024);
  • encoder.set_color(png::ColorType::Grayscale);
  • encoder.set_depth(png::BitDepth::Eight);
  • let mut writer = encoder.write_header().unwrap();
  • writer.write_image_data(&values).unwrap();
  • }
  • ```
  • ## Test cases
  • The output images may be scaled down to fit. Click on any image to open it full size.
  • ### Single sphere
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 128],
  • ]
  • ```
  • Output:
  • [![Grey circle on a black background](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)](https://codegolf.codidact.com/uploads/jlru163pa2pgm7aaq70b2tff16uf)
  • ### Two separated spheres
  • Input:
  • ```text
  • [
  • [0, -0.55, 0, 0.4, 100],
  • [0, 0.55, 0, 0.4, 200],
  • ]
  • ```
  • Output:
  • [![Light and dark grey circles on a black background](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)](https://codegolf.codidact.com/uploads/8yw7agli78y65kiomb1u5yebs7c5)
  • ### Ring of spheres
  • Input:
  • ```text
  • [
  • [0.75, 0, 0, 0.25, 64],
  • [0.53033, 0.375, -0.375, 0.25, 185],
  • [0, 0.53033, -0.53033, 0.25, 64],
  • [-0.53033, 0.375, -0.375, 0.25, 185],
  • [-0.75, 0, -0, 0.25, 64],
  • [-0.53033, -0.375, 0.375, 0.25, 185],
  • [-0, -0.53033, 0.53033, 0.25, 64],
  • [0.53033, -0.375, 0.375, 0.25, 185],
  • ]
  • ```
  • Output:
  • [![Eight circles in alternating light and dark grey in a ring some overlapping](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)](https://codegolf.codidact.com/uploads/0lsenynig27au0sryf3ruozt1uwh)
  • ### Beach ball
  • Input:
  • ```text
  • [
  • [0.001, 0, 0, 1, 64],
  • [0.000707, 0.000707, -0, 1, 85],
  • [0, 0.001, -0, 1, 106],
  • [-0.000707, 0.000707, -0, 1, 127],
  • [-0.001, 0, -0, 1, 148],
  • [-0.000707, -0.000707, 0, 1, 169],
  • [-0, -0.001, 0, 1, 190],
  • [0.000707, -0.000707, 0, 1, 211],
  • ]
  • ```
  • Output:
  • [![A circle with eight sectors in different shades of grey](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)](https://codegolf.codidact.com/uploads/51a3ni0cfwsqcdw5b49gy6282qno)
  • ### Football
  • Input:
  • ```text
  • [
  • [0, 0.000058, 0.001947, 1, 0],
  • [0, -0.001715, 0.000923, 1, 0],
  • [0, 0.001715, -0.000923, 1, 0],
  • [0, -0.000058, -0.001947, 1, 0],
  • [0.001657, -0.000512, 0.000887, 1, 0],
  • [-0.001657, -0.000512, 0.000887, 1, 0],
  • [0.001657, 0.000512, -0.000887, 1, 0],
  • [-0.001657, 0.000512, -0.000887, 1, 0],
  • [0.001024, 0.001435, 0.000829, 1, 0],
  • [-0.001024, 0.001435, 0.000829, 1, 0],
  • [0.001024, -0.001435, -0.000829, 1, 0],
  • [-0.001024, -0.001435, -0.000829, 1, 0],
  • [0.001868, 0.000618, 0.000357, 1, 255],
  • [-0.001868, 0.000618, 0.000357, 1, 255],
  • [0.001868, -0.000618, -0.000357, 1, 255],
  • [-0.001868, -0.000618, -0.000357, 1, 255],
  • [0.000714, -0.000934, 0.001618, 1, 255],
  • [-0.000714, -0.000934, 0.001618, 1, 255],
  • [0.000714, 0.000934, -0.001618, 1, 255],
  • [-0.000714, 0.000934, -0.001618, 1, 255],
  • [0, -0.001975, -0.000316, 1, 255],
  • [0, 0.001261, 0.001552, 1, 255],
  • [0, -0.001261, -0.001552, 1, 255],
  • [0, 0.001975, 0.000316, 1, 255],
  • [0.001155, 0.000423, 0.001577, 1, 255],
  • [-0.001155, 0.000423, 0.001577, 1, 255],
  • [0.001155, -0.001577, 0.000423, 1, 255],
  • [-0.001155, -0.001577, 0.000423, 1, 255],
  • [0.001155, 0.001577, -0.000423, 1, 255],
  • [-0.001155, 0.001577, -0.000423, 1, 255],
  • [0.001155, -0.000423, -0.001577, 1, 255],
  • [-0.001155, -0.000423, -0.001577, 1, 255],
  • ]
  • ```
  • Output:
  • [![Black pentagons on a white ball](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)](https://codegolf.codidact.com/uploads/bmrbsw65p59imvwu8ssd42uvvx1r)
  • ### Eight ball
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 128],
  • [0, 0, 0.003125, 1, 0],
  • [0, 0, 0, 0.9975, 255],
  • [0, 0.000547, -0.002573, 0.995, 0],
  • [-0, -0.000547, -0.002573, 0.995, 0],
  • [0, 0.001071, -0.005037, 0.9925, 255],
  • [-0, -0.001071, -0.005037, 0.9925, 255],
  • ]
  • ```
  • Output:
  • [![A ball with the number 8 in a white circle](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)](https://codegolf.codidact.com/uploads/dvl68crpmy62rqt94mwanditt4wc)
  • ### Star ball
  • Input:
  • ```text
  • [
  • [0, 0, 0, 1, 210],
  • [0, 0, 0.0008, 1, 210],
  • [0, 0, 0.0004, 1.00012, 50],
  • [0.012969, -0, -0.042045, 0.96, 100],
  • [0.016397, 0.011913, -0.039054, 0.96, 210],
  • [0.004008, 0.012334, -0.042045, 0.96, 100],
  • [-0.006263, 0.019276, -0.039054, 0.96, 210],
  • [-0.010492, 0.007623, -0.042045, 0.96, 100],
  • [-0.020268, 0, -0.039054, 0.96, 210],
  • [-0.010492, -0.007623, -0.042045, 0.96, 100],
  • [-0.006263, -0.019276, -0.039054, 0.96, 210],
  • [0.004008, -0.012334, -0.042045, 0.96, 100],
  • [0.016397, -0.011913, -0.039054, 0.96, 210],
  • ]
  • ```
  • Output:
  • [![A light grey ball with a dark grey five pointed star](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)](https://codegolf.codidact.com/uploads/f3wdgwru6j35ue72x25mazxikfx2)
  • ### Combination scene
  • Input:
  • ```text
  • [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],
  • [-0.349469, -0.1, 20.000279, 0.6, 64],
  • [-0.349775, -0.100273, 20.000485, 0.6, 85],
  • [-0.350214, -0.100386, 20.000407, 0.6, 106],
  • [-0.350527, -0.100273, 20.000091, 0.6, 127],
  • [-0.350531, -0.1, 19.999721, 0.6, 148],
  • [-0.350225, -0.099727, 19.999515, 0.6, 169],
  • [-0.349786, -0.099614, 19.999593, 0.6, 190],
  • [-0.349473, -0.099727, 19.999909, 0.6, 211],
  • [0.20032, 0.349824, 15.000456, 0.3, 0],
  • [0.20032, 0.349517, 14.999924, 0.3, 0],
  • [0.19968, 0.350483, 15.000076, 0.3, 0],
  • [0.19968, 0.350176, 14.999544, 0.3, 0],
  • [0.200578, 0.350073, 14.999958, 0.3, 0],
  • [0.199817, 0.349519, 15.000277, 0.3, 0],
  • [0.200183, 0.350481, 14.999723, 0.3, 0],
  • [0.199422, 0.349927, 15.000042, 0.3, 0],
  • [0.200235, 0.35042, 15.000332, 0.3, 0],
  • [0.199765, 0.350078, 15.000529, 0.3, 0],
  • [0.200235, 0.349922, 14.999471, 0.3, 0],
  • [0.199765, 0.34958, 14.999668, 0.3, 0],
  • [0.200429, 0.350419, 15.000005, 0.3, 255],
  • [0.199571, 0.349795, 15.000366, 0.3, 255],
  • [0.200429, 0.350205, 14.999634, 0.3, 255],
  • [0.199571, 0.349581, 14.999995, 0.3, 255],
  • [0.200524, 0.349747, 15.000146, 0.3, 255],
  • [0.200196, 0.349509, 15.000283, 0.3, 255],
  • [0.199804, 0.350491, 14.999717, 0.3, 255],
  • [0.199476, 0.350253, 14.999854, 0.3, 255],
  • [0.200138, 0.349578, 14.999597, 0.3, 255],
  • [0.200138, 0.350138, 15.000567, 0.3, 255],
  • [0.199862, 0.349862, 14.999433, 0.3, 255],
  • [0.199862, 0.350422, 15.000403, 0.3, 255],
  • [0.200488, 0.350136, 15.000321, 0.3, 255],
  • [0.199957, 0.349751, 15.000544, 0.3, 255],
  • [0.200488, 0.34979, 14.999721, 0.3, 255],
  • [0.199957, 0.349404, 14.999944, 0.3, 255],
  • [0.200043, 0.350596, 15.000056, 0.3, 255],
  • [0.199512, 0.35021, 15.000279, 0.3, 255],
  • [0.200043, 0.350249, 14.999456, 0.3, 255],
  • [0.199512, 0.349864, 14.999679, 0.3, 255],
  • [-0.5, 0.500442, 10.000442, 0.2, 0],
  • [-0.5, 0.5, 10, 0.1995, 255],
  • [-0.499966, 0.49971, 9.999563, 0.199, 0],
  • [-0.500034, 0.499563, 9.99971, 0.199, 0],
  • [-0.499934, 0.499432, 9.999144, 0.1985, 255],
  • [-0.500066, 0.499144, 9.999432, 0.1985, 255],
  • [0.6, 0.5, 10, 0.25, 210],
  • [0.600122, 0.4999, 10.000122, 0.25, 210],
  • [0.600061, 0.49995, 10.000061, 0.25003, 50],
  • [0.595856, 0.505256, 9.991271, 0.24, 100],
  • [0.597973, 0.507461, 9.992175, 0.24, 210],
  • [0.595362, 0.507926, 9.993945, 0.24, 100],
  • [0.594618, 0.509055, 9.996832, 0.24, 210],
  • [0.592382, 0.506906, 9.996092, 0.24, 100],
  • [0.590438, 0.504882, 9.997604, 0.24, 210],
  • [0.591035, 0.503605, 9.994744, 0.24, 100],
  • [0.59121, 0.500708, 9.993425, 0.24, 210],
  • [0.593181, 0.502585, 9.991764, 0.24, 100],
  • [0.595867, 0.502302, 9.990069, 0.24, 210],
  • ]
  • ```
  • Output:
  • [![A beachball, football, eightball, and starball with the sun behind hills in the background](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)](https://codegolf.codidact.com/uploads/usv9npm1t5bjpcaknn6rbzgkqwmi)
  • ## Scoring
  • This is a [code golf challenge](https://codegolf.codidact.com/categories/49/tags/4274). Your score is the number of bytes in your code. Lowest score for each language wins.
  • > Explanations are optional, but I'm more likely to upvote answers that have one.
#9: Post edited by user avatar trichoplax‭ · 2024-07-26T12:12:11Z (4 months ago)
Add test case outputs
  • Given a list of spheres, output an image.
  • Produce a greyscale [orthographic](https://en.wikipedia.org/wiki/Parallel_projection#Orthographic_projection) (parallel projection - no perspective) ray caster, that renders only plain grey spheres with no lighting.
  • ## Input
  • - A list of spheres, each consisting of:
  • - The coordinates of its centre $(x,y,z)$.
  • - Its radius $r$.
  • - Its greyscale value $g$, an integer from $0$ to $255$ (both inclusive).
  • - Only the greyscale value will be restricted to integer values. The coordinates and radius may have fractional values.
  • ## Output
  • - An image $1024$ by $1024$ pixels with greyscale values from $0$ to $255$.
  • - The image covers x and y values from $-1$ to $1$.
  • - A sphere's greyscale value is applied to every visible part of it. There is no variation over the surface of the sphere, so it appears flat like a disk.
  • - You may output whatever image format is convenient, including simple text formats such as [PGM](https://en.wikipedia.org/wiki/Netpbm#PGM_example) or a list of pixel greyscale values. If you want to include an example of your output as an image in your answer, the image formats that Codidact supports are PNG, JPEG, and GIF. You could output PGM and then convert to PNG to upload to your answer. If you output just the greyscale values as a list of numbers from $0$ to $255$, I've made a `[ TODO page to convert these to greyscale PNG]`.
  • - You are free to output an image reflected or rotated by a multiple of 90 degrees. However, the z axis is required to be increasing in the forwards direction (into the image) otherwise some of the test cases will look incorrect.
  • ## Calculating a pixel value
  • Imagine a ray passing into the image through a pixel, perpendicular to the image plane. If it hits a sphere, it shows the greyscale value of the first sphere it hits. Otherwise, it shows a value of zero (black).
  • For example, for each pixel:
  • - The pixel $x,y$ coordinates will each be from $0$ to $1023$. Scale these to the range $-1$ to $1$ (for example, divide by $512$ and subtract $1$).
  • - If these scaled $x,y$ coordinates are within the radius $r$ of the $x,y$ coordinates of a sphere's centre, the ray intersects that sphere (the $z$ coordinate is not relevant to this test because the image is an orthographic projection - the rays are all parallel to the $z$ axis). The distance $a$ of the ray from the sphere's centre can be calculated as follows:
  • $$a=\sqrt{(x_{ray}-x_{sphere})^2+(y_{ray}-y_{sphere})^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - For each sphere that the ray intersects, find the $z$ coordinate of the nearest intersection (there will often be $2$ per sphere, $1$ on the front of the sphere and $1$ on the back of the sphere, as the ray passes through - only the nearest is relevant). This can be calculated as follows:
  • - The distance $d$ to the nearest intersection point can be calculated from the $z$ coordinate of the sphere's centre, the radius $r$, and the distance $a$ from the $x,y$ coordinates of the sphere's centre to the scaled $x,y$ coordinates of the pixel:
  • $$d=z_{sphere}-\sqrt{r_{sphere}^2-a^2}$$
  • ```text
  • CONSIDER A DIAGRAM HERE
  • ```
  • - Of all the spheres that the ray intersects, the sphere with the intersection point with the smallest $z$ coordinate $d$ is the one to display. Set the pixel to the greyscale value of this sphere.
  • - If the ray did not intersect any spheres, set the pixel to zero (black).
  • ## Example implementations
  • These are ungolfed code to supplement understanding of the requirements. You are free to golf these or implement your own approach that matches the test cases up to reflection & rotation.
  • The Python implementation uses 64 bit floating point. The Rust implementation uses 32 bit floating point to show the difference in output. I've tried to optimise the test cases for compatibility with 32 bit floating point, so the differences are restricted to being a pixel out in some places.
  • ### Python
  • The function `grey_values` would be sufficient as an entry to this challenge. The rest of the code saves the outputs as PNG files. The function itself has no dependence on the `import png` so it would not be required in an entry.
  • Instructions for installing the `png` package can be found on its [project page on PyPI](https://pypi.org/project/pypng/).
  • ```python
  • def grey_values(spheres):
  • values = []
  • for y_pixel in range(1024):
  • for x_pixel in range(1024):
  • x_ray, y_ray = x_pixel / 512 - 1, y_pixel / 512 - 1
  • min_distance, hit_grey_value = None, 0
  • for sphere in spheres:
  • x_sphere, y_sphere, z_sphere, radius, grey = sphere
  • a = (
  • (x_ray - x_sphere) ** 2 + (y_ray - y_sphere) ** 2
  • ) ** 0.5
  • if a < radius:
  • distance = z_sphere - (radius ** 2 - a ** 2) ** 0.5
  • if min_distance is None or distance < min_distance:
  • min_distance = distance
  • hit_grey_value = grey
  • values.append(hit_grey_value)
  • return values
  • import png
  • test_case = [
  • [0, 0, 1000, 2, 192],
  • [-2, 2, 100, 3, 98],
  • [2, 2, 100, 3, 98],
  • [0.5, -0.5, 500, 0.2, 255],