56 Commits

Author SHA1 Message Date
78ed28a334 Split crate into lib and bin chunks
I'm going to be doing some silly WASM and ES Module stuff here shortly,
so I need a cdylib. This means I'll need the crate to actually have a
library in it.
2025-11-11 17:09:24 -06:00
6614b32969 Delete the example images 2025-11-11 17:09:15 -06:00
49569528fa Mark v1, release under AGPL-3.0 license
I'm marking v1 here before I make any more changes. I would have liked
to make v1 the last change I made when finishing the Ray Tracing in One
Weekend book, but I did not.

The project shall be AGPL v3-only. I'm indicating this both in a LICENSE
file and in the Cargo.toml's project.license table because not all tools
understand both pathways.
v1.0.0
2025-11-11 17:01:47 -06:00
309931b7f6 Replace util ctor's with constants
I don't need functions to create new vectors, I just want a constant
that I can clone. The compiler probably does the same thing in both
cases, but this is more semantically accurate to that goal.
2025-08-20 10:31:03 -05:00
83fa670d35 Remove these extra brackets
I don't know why I put them there, and I'm not going to `git bisect` to
figure it out. They're just going away.
2025-08-20 10:15:30 -05:00
46f6784256 Run fixes recommended by cargo-clippy 2025-08-19 18:26:41 -05:00
fcb9ad2dd2 Check-in a default Cargo .gitignore
I guess I didn't use `cargo new` or `cargo init` at any point. Strange;
2025-08-19 18:23:08 -05:00
34a828fe67 autoformat the project 2025-08-19 18:21:51 -05:00
05add1efca Fix imports and Uniform creation usage in rand
Rand v0.9 made the `Uniform::new()` function fallible, but I'm going to
keep assuming these always work and just `.unwrap()` the Result.

It also renamed the distribution module, for some reason.
2025-03-01 12:57:57 -06:00
8548e4e322 Update dependency versions
It's been a couple years and there are new versions of these libraries.
Bump and relax constraints (I don't need exact patch levels, that's what
a lockfile is for).
2025-03-01 12:56:36 -06:00
c8726a4d9a Bump edition to 2024 in Cargo.toml 2025-03-01 12:32:33 -06:00
a701b9407b Line renderer in terms of tile renderer
Done and done.
2023-09-25 11:25:42 -07:00
72f154510f Proper tile rendering
The Tile can now render a region of height > 1px!

I'm gonna rewrite the render_line() function to operate in terms of the
render_tile() function. A line is, after all, just a tile of height 1px.
2023-09-25 11:19:33 -07:00
3250f8e580 Hook up the new renderer
The threading code is gone, now. We're back to just having a single
loop to drive the whole thing.

Along with this, I realized that the Distrs container thing wasn't
actually being used. It's a real pain to cart it around, and very few
things actually use it.

TODO: Reinstate the small wiggle done by the uv mapping routine. This
version no longer nudges the coordinate, so I expect there to be some
small visual differences.
2023-09-25 10:50:47 -07:00
f03c6280a7 Renderer 2, now with 100% less threading tools
I've rewritten the renderer to see if I can make a better model the
second time around. I was having a rough time untangling parts and
refactoring it piece-by-piece.

Next is to hook up the new rendering parts into a single-threaded
build. Once the parts work again, I can look into thread pooling
machinery.
2023-09-25 08:20:27 -07:00
60b4407573 New Scene struct
The scene is more than just a list of hittables. It's any and all
hittables (so the list, yeah), and also the camera(s!) in the world.

This doens't compile, however. More work will need to be done to
untangle the other things that could previously see these scattered
components.
2023-09-23 14:40:34 -07:00
7c43c3fb82 Rewrite hittable list hit method using iter magic
The loop can go away completely and be replaced with an iterator. Yay
for Rust iterators!
2023-09-23 13:26:42 -07:00
4be7ba54bb Relocate world generation function 2023-09-23 13:07:40 -07:00
515f5b866a Fix: hit record selection mechanism
Because of the mutable record being used in the loop, the previous
version had a somewhat obscured way to track the nearest collision.

Switching to an optional (so I can have a non-optional Material in it)
means I'm not interrogating that value.... So it gets to be explicit
again.

I'll refactor the entire for-loop into an iterator with the min()
adapter at some point. For now: Material lifetimes!
2023-09-23 09:27:21 -07:00
bdc396accf Material references... but bad ordering
It looks like I messed up the preference for the HitRecords. The
geometry bounces correctly, but the record that sticks is not
necessarily the one closest to the camera.
2023-09-22 18:21:12 -07:00
4ce43e12af Gathered up the scene components
After nearly a month of not touching the project, I've finally finished
collecting the scene parts. :l

With that, the rearrange is complete. On to the next thing!
2023-09-17 12:16:33 -05:00
76233f82a4 Condensing the rendering components
All the rendering bits together... except for the ones I missed. Never
mind those. This one has section headers so I can try to stay organized.
I'm gonna need to actually *do* things in this file going forward.
2023-08-19 20:37:51 -05:00
9badea407d Rect in primitives, collect the tests
There's the rectangle!

Also the tests. Cargo doesn't complain, but my YcmCompleter (so
rust-analyze, I think) does. The tests are all one big slab again.
2023-08-19 20:01:33 -05:00
f5eae46f17 Condensing the primitives
Group the informational types together. `vec3.rs` was renamed, and
the Ray implementation was copied into it. The Rect (and possibly a
Point) struct will be moved in, next. It's bad to have a `misc` or
`util` section, but "primitives" doesn't really do it for me, either.

My thought is that the sections will be:
- Primitives
- Renderer
- Scene Components

The renderer section would cover the image description and generation.
Image size and pixel sample count, but also things like the tile size,
render command dispatching, and file write-out. Anything to do with
producing the render.

The scene components section covers anything that goes in the render.
Obvious parts are the spheres and their materials, but this will also
include the camera. After all, it exists "in the world", and there could
be multiple.
2023-08-19 19:28:43 -05:00
809d7b678b Clean up some leftovers
Quickly sweeping up a few bits and pieces that got left around. This
seems like an okay way to load the codebase into my brain before doing
the big rearranging.
2023-08-19 18:44:57 -05:00
fc8f9e0e15 Starting to get a hold of the Tile struct
Iterators are turning my brain to mush. I'm trying far too hard to
leverage existing iterator tooling. The closures all over the place make
saying the type names basically impossible. For chaining in the middle
of a function, this is fine. But I need to stick it in a `struct Tile{}`
and move it between threads.

This commit is a save point for my own sanity, more than anything else.
The tile struct exists and compiles. The only changed part is the
extraction of the pixel sampling loop into a named function (instead of
an nameless closuuuruurreeee)
2023-08-19 17:12:28 -05:00
601beb10a0 Control those Uniform distributions
I wanted to make the Uniform's into `const`s that live at the global
scope. Rust can do global const, but the `Uniform::new()` function
can't be used in that context.

As an intermediate to a *helpful* solution, I've just pushed them into a
struct. One less parameter to name even though it's the same stuff. The
compiler should be smart enough to hoist the initialization outside the
function and leave them around, but maybe not. After all, the function
isn't defined to work in such a way with the `const` keyword :v
2023-08-19 08:55:29 -05:00
4430b7c0bf Render frame function does the iterator thing!
The many, many nested for loops don't feel right in a language that lets
you `let x = for...` to assign the results of the loop directly to a
variable. The logic hasn't changed (and I'm pretty sure the compiler
emits the same code), but it feels better now.

I'm now equipped to go over the rest of the project and rewrite the
loops. Hopefully a more ergonomic way to dispatch to the threads arises
as a result. I shall see.
2023-08-19 08:55:27 -05:00
adaf277cba feat: Shutdown propogation and thread count arg
The Stop command will be handled specially by the Dispatcher to act as a
sort of broadcast into the thread pool. Instead of asking the caller to
feed in the appropriate number of stop commands, a single one will queue
up a stop for all threads. Important for the ergonomics of having a
variable number of threads (instead of the earlier magic constant).
Moreover, it's necessary if the dispatcher stops using the round-robin
style assignment.

The pool size is specifiable as an argument to Dispatcher::new().
2023-06-25 14:29:35 -05:00
9873c5596d feat: Output ordering!
As results come from the dispatcher('s return channel) they are pushed
into a vector to be reordered. They're sorted in reverse-order so that
they can be popped from the vector. Upon receipt and buffering of a
scanline, a loop checks the tail of the buffer to see if it's the
next-to-write element. Since the tail is popped off, this loop can run
until this condition is not met.
2023-06-25 12:11:30 -05:00
995cfdf391 feat: Dispatcher constructor separates render_rx
The dispatcher no longer owns the render results message channel, and
instead passes it out as a separate item during construction.
2023-06-25 09:14:41 -05:00
65185c7996 fail: Threads want full ownership, do another way
Saving for reference more than anything. The threads take ownership of
the data (the closures do, but whatever). Moving the return channel out
of the dispatcher means the dispatcher can't be moved into the feeder
thread.

I see a few solutions from here:
1. Proxy the return channel with another channel. Give the whole
   dispatcher to the feeder thread and hook up another output channel.
   Have the feeder unload the return and pass it through.
2. Rewrite the dispatcher constructor to pass a tuple of the dispatcher
   minus it's return channel, and the return channel now as a separate
   object. This could let them have independent lifetimes and then I can
   pass them around like I'm trying to do.
3. Have main do all the job feeding, result unloading, and
   recompositing. Don't have a feeder and collector thread, and just
   have main bounce between loading a few, and unloading a few.
2023-06-25 09:00:47 -05:00
a4a389c10d feat: Job dispatcher hooked up*
The job dispatcher has been hooked up and four threads are rendering the
scene. There's a super important caveat, though: The job submission is
blocking and will prevent the main thread from continuing to the result
collection. The threads will be unable to contiinue without having
output space, though. Since the buffers are only size 1, this will cause
4 scanlines to be rendered, and then nothing else to be done. Deadlock.
Bumping the input buffer to 100 lets the submission loop fill up the
workload and then move on to collecting.

There's also no scanline sorting, so everything gets <<wiggly>>.
2023-06-24 20:56:28 -05:00
d77655af12 fix: Increment submit_job counter
Gotta increment that counter to submit jobs to each thread. Big dummy.
2023-06-24 20:43:53 -05:00
1d7f075e0d feat: Dispatcher struct for handling jobs
It's getting fiddly and awful keeping all the thread control pieces all
over the place. Existing ThreadPool implementations seem a little weird,
and this is a learning experience. I'll just make my own!

The dispatcher constructor only creates the threads and sets up their IO
channels. It has another method for serial submission of jobs.

The job allocation follows a round-robin selection, but I have some
concerns about starvation doing this -- consider a long running scanline
render. It would make the submission call block and other threads could
become available for that job. But they'll be ignored, since the
round-robin assignment doesn't have any skip mechanisms.
2023-06-24 20:05:11 -05:00
497ea94fbf feat: Thread job dispatching!
There are now two threads: The main thread (obv) and a single worker
thread to render lines. There's a render context struct to keep track of
the many, many arguments to the `render_line()` function. Jobs are
submitted through a channel to the thread, and results are returned
through another. Next up is to make a bunch of threads and collect
across them.
2023-06-24 16:24:28 -05:00
814a65859a Hittables as enum instead of trait objects
I hit an issue implementing the threading where the use of trait objects
got real angry about lifetimes. The variants of geometry could be
described as a runtime variable (thus requiring dynamic dispatch), but
I'm not gonna do that. Instead: Enums!
2023-06-24 11:36:23 -05:00
d97b4ced83 Feat: Single-line rendering function
Laying the groundwork for setting up threading. Gotta be able to run
parts of the render to get multiple things working on the whole.
2023-06-23 19:52:43 -05:00
6dd6befed0 chore: Turn down the image resolution for testing
For some reason I feel it necessary to make this its own commit. I might
be for real losing my mind.
2023-06-23 19:52:36 -05:00
086a2d01ee doc: Save the images for fun and games
1. Set discord status to "DM for ball pics"
2. receive requests for ball pics
3. share ball pics
4. *make more ball pics*
2023-06-23 19:15:33 -05:00
0b31f66bbf fix: Corrected the distributions
I had been carrying the distributions around with the SmallRng object so
that I can provide whatever bounds needed. It turns out that the book
only takes advantage of this a couple of times.

The functions accepting a Uniform struct no longer do. They instead
construct their own temporary one -- hopefully the optimizer can see
through the loops and construct it just once. :l

The change exposed all the uses of the distributions (duh!) and now they
are correct. The effect is most pronounced on the dielectric spheres.
2023-06-06 20:47:32 -05:00
0c9949bbb8 Feat: (Pseudo) Random world generation
Final render! The world is pseudorandomly generated. The glass ball in
the middle doens't come out quite right, though. I think there are bad
random ranges being passed around. Things are using (-1,1) when they
should be [0,1). Bug fix incoming.
2023-06-06 20:16:28 -05:00
b6ec544b3e Feat: Simulate depth-of-field
And with that, I'm on to the final render. Chapter 12 closed!
2023-06-06 19:14:17 -05:00
b7ec69dc7f Feat: Camera positioning
The camera can now be fully described in 3D space. Close of Chapter 11.
2023-06-06 18:37:26 -05:00
b0efc9a248 Feat: Wide-angle camera 2023-06-06 18:17:39 -05:00
7ee6da0234 Feat: Total internal reflection, Schlick's approx.
Completion of Chapter 10: Dielectrics.
2023-06-06 18:06:59 -05:00
4f14a166a6 Fix: Nearest root within tolerance
I found the fucker. The rays were bouncing incorrectly because I wasn't
holding a value the way I should have. I thought I was being clever and
using Rusts's ability to redeclare variables. But, no. That value is
meant to be modified when the first conditional passes. The outcomes are
3 possibilities, where one is an early return. Shadowing the value that
  way meant I was giving back garbage.

I don't know why this didn't have any obviously bad effects prior to
making the dielectric material.
2023-06-06 17:48:46 -05:00
0822096a3a chore: Unit convertor by value, clean stale code
The Vec3::as_unit() function accepts input by reference. It passes back
out a copy, however, and some inputs are even temporaries. I bet the
optimizer can see through this game and will do the right thing if I
give it a value instead. It should perform copy elision as appropriate,
even if I'm missing out on Rust's move semantics here.

Remove some old, unused, and unuseable functions.
2023-06-06 17:43:29 -05:00
4ea2208208 Feat: Rudimentary dielectric... but broken
Dielectric material matching Raytracing in a Weekend book chapter 10.2.
But it isn't right. The spheres turn into solid black holes.

As I'm making the commit, I've found the problem. I'm separating it out
so I can tag it and explore the code a bit more. See the step-by-step
changes, and such.
2023-06-06 17:39:09 -05:00
ef5e3fa388 fix: IT WAS THE GAMMA CORRECTION CODE
aslodkghlkijasdfhgjlaskdjf
2023-06-04 18:34:01 -05:00