Very big change, preparing for gpu integration
This commit is contained in:
parent
392b0a6850
commit
8bcc2fb0c8
104 changed files with 7870 additions and 4533 deletions
30
Cargo.toml
30
Cargo.toml
|
|
@ -3,28 +3,52 @@ name = "pbrt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
use_f64 = []
|
||||||
|
cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ]
|
||||||
|
use_nvtx = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
bitflags = "2.10.0"
|
bitflags = "2.10.0"
|
||||||
bumpalo = "3.19.0"
|
bumpalo = "3.19.0"
|
||||||
|
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||||
enum_dispatch = "0.3.13"
|
enum_dispatch = "0.3.13"
|
||||||
exr = "1.73.0"
|
exr = "1.73.0"
|
||||||
|
flate2 = "1.1.5"
|
||||||
|
gpu = "0.2.3"
|
||||||
half = "2.7.1"
|
half = "2.7.1"
|
||||||
image_rs = { package = "image", version = "0.25.8" }
|
image_rs = { package = "image", version = "0.25.8" }
|
||||||
indicatif = "0.18.3"
|
indicatif = "0.18.3"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
log = "0.4.29"
|
||||||
|
memmap2 = "0.9.9"
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
num-integer = "0.1.46"
|
num-integer = "0.1.46"
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
|
nvtx = "1.3.0"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
parking_lot = "0.12.5"
|
||||||
|
paste = "1.0.15"
|
||||||
qoi = "0.4.1"
|
qoi = "0.4.1"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
rayon = "1.11.0"
|
rayon = "1.11.0"
|
||||||
smallvec = "1.15.1"
|
smallvec = "1.15.1"
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
|
unicode-normalization = "0.1.25"
|
||||||
|
wgpu = "27.0.1"
|
||||||
|
shared = { path = "shared" }
|
||||||
|
kernel = { path = "kernels" }
|
||||||
|
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
||||||
|
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
|
||||||
|
|
||||||
[features]
|
[build-dependencies]
|
||||||
default = []
|
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
||||||
use_f64 = []
|
cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["kernels", "shared"]
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
excessive_precision = "allow"
|
excessive_precision = "allow"
|
||||||
|
|
|
||||||
BIN
data/aces_coeffs.dat
Normal file
BIN
data/aces_coeffs.dat
Normal file
Binary file not shown.
BIN
data/aces_scale.dat
Normal file
BIN
data/aces_scale.dat
Normal file
Binary file not shown.
BIN
data/dcip3_coeffs.dat
Normal file
BIN
data/dcip3_coeffs.dat
Normal file
Binary file not shown.
BIN
data/dcip3_scale.dat
Normal file
BIN
data/dcip3_scale.dat
Normal file
Binary file not shown.
BIN
data/rec2020_coeffs.dat
Normal file
BIN
data/rec2020_coeffs.dat
Normal file
Binary file not shown.
BIN
data/rec2020_scale.dat
Normal file
BIN
data/rec2020_scale.dat
Normal file
Binary file not shown.
BIN
data/srgb_coeffs.dat
Normal file
BIN
data/srgb_coeffs.dat
Normal file
Binary file not shown.
BIN
data/srgb_scale.dat
Normal file
BIN
data/srgb_scale.dat
Normal file
Binary file not shown.
10
kernels/Cargo.toml
Normal file
10
kernels/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "kernels"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
447
kernels/src/lib.rs
Normal file
447
kernels/src/lib.rs
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
use cuda_std::prelude::*;
|
||||||
|
|
||||||
|
pub mod wavefront;
|
||||||
|
pub mod workitem;
|
||||||
|
|
||||||
|
use cust::context::{CacheConfig, CurrentContext, ResourceLimit};
|
||||||
|
use cust::device::DeviceAttribute;
|
||||||
|
use cust::memory::{DeviceCopy, DeviceMemory};
|
||||||
|
use cust::prelude::*;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::geometry::{Normal, Point, Vector};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::{PBRTOptions, get_options};
|
||||||
|
use crate::impl_gpu_traits;
|
||||||
|
use crate::impl_math_gpu_traits;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::interval::Interval;
|
||||||
|
|
||||||
|
pub use workitem::{
|
||||||
|
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
|
||||||
|
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
|
||||||
|
SubsurfaceScatterQueue,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[repr(C, align(16))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
pub struct Float4 {
|
||||||
|
pub v: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Vec4 = Vector<Float, 4>;
|
||||||
|
|
||||||
|
impl From<Vec4> for Float4 {
|
||||||
|
#[inline]
|
||||||
|
fn from(vec: Vector<f32, 4>) -> Self {
|
||||||
|
Self { v: vec.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Float4> for Vec4 {
|
||||||
|
#[inline]
|
||||||
|
fn from(storage: Float4) -> Self {
|
||||||
|
Vector(storage.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_math_gpu_traits!(Vector);
|
||||||
|
impl_math_gpu_traits!(Normal);
|
||||||
|
impl_math_gpu_traits!(Point);
|
||||||
|
impl_gpu_traits!(Interval);
|
||||||
|
impl_gpu_traits!(Float4);
|
||||||
|
impl_gpu_traits!(SampledSpectrum);
|
||||||
|
impl_gpu_traits!(SampledWavelengths);
|
||||||
|
|
||||||
|
struct KernelStats {
|
||||||
|
description: String,
|
||||||
|
num_launches: usize,
|
||||||
|
sum_ms: f32,
|
||||||
|
min_ms: f32,
|
||||||
|
max_ms: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KernelStats {
|
||||||
|
fn new(description: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
description: description.to_string(),
|
||||||
|
num_launches: 0,
|
||||||
|
sum_ms: 0.0,
|
||||||
|
min_ms: 0.0,
|
||||||
|
max_ms: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfilerEvent {
|
||||||
|
start: Event,
|
||||||
|
stop: Event,
|
||||||
|
active: bool,
|
||||||
|
stats: Option<Arc<Mutex<KernelStats>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfilerEvent {
|
||||||
|
fn new() -> Result<Self, cust::error::CudaError> {
|
||||||
|
let start = Event::new(EventFlags::DEFAULT)?;
|
||||||
|
let stop = Event::new(EventFlags::DEFAULT)?;
|
||||||
|
Ok(Self {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
active: false,
|
||||||
|
stats: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync(&mut self) {
|
||||||
|
if !self.active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.stop.synchronize().is_ok() {
|
||||||
|
// Check timing between start and stop
|
||||||
|
match self.stop.elapsed_time_f32(&self.start) {
|
||||||
|
Ok(ms) => {
|
||||||
|
if let Some(stats_arc) = &self.stats {
|
||||||
|
let mut stats = stats_arc.lock();
|
||||||
|
stats.num_launches += 1;
|
||||||
|
if stats.num_launches == 1 {
|
||||||
|
stats.sum_ms = ms;
|
||||||
|
stats.min_ms = ms;
|
||||||
|
stats.max_ms = ms;
|
||||||
|
} else {
|
||||||
|
stats.sum_ms += ms;
|
||||||
|
stats.min_ms = stats.min_ms.min(ms);
|
||||||
|
stats.max_ms = stats.max_ms.max(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Failed to get elapsed time: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Profiler Manager ---
|
||||||
|
|
||||||
|
struct Profiler {
|
||||||
|
kernel_stats: Vec<Arc<Mutex<KernelStats>>>,
|
||||||
|
event_pool: Vec<ProfilerEvent>,
|
||||||
|
pool_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Profiler {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
kernel_stats: Vec::new(),
|
||||||
|
event_pool: Vec::new(),
|
||||||
|
pool_offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares an event from the pool.
|
||||||
|
/// Returns a mutable reference to the event, valid as long as the borrow of self.
|
||||||
|
fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent {
|
||||||
|
// Grow pool if empty or needed (simple heuristic)
|
||||||
|
if self.event_pool.is_empty() {
|
||||||
|
for _ in 0..128 {
|
||||||
|
if let Ok(e) = ProfilerEvent::new() {
|
||||||
|
self.event_pool.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pool_offset >= self.event_pool.len() {
|
||||||
|
self.pool_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = self.pool_offset;
|
||||||
|
self.pool_offset += 1;
|
||||||
|
|
||||||
|
let pe = &mut self.event_pool[idx];
|
||||||
|
|
||||||
|
if pe.active {
|
||||||
|
pe.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.active = true;
|
||||||
|
pe.stats = None;
|
||||||
|
|
||||||
|
// Find or create stats
|
||||||
|
let mut found = None;
|
||||||
|
for s in &self.kernel_stats {
|
||||||
|
if s.lock().description == description {
|
||||||
|
found = Some(s.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.is_none() {
|
||||||
|
let new_stats = Arc::new(Mutex::new(KernelStats::new(description)));
|
||||||
|
self.kernel_stats.push(new_stats.clone());
|
||||||
|
found = Some(new_stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.stats = found;
|
||||||
|
pe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GpuState {
|
||||||
|
context: Context,
|
||||||
|
stream: Stream,
|
||||||
|
profiler: Profiler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpuState {
|
||||||
|
fn init(device_index: u32) -> Result<Self, Box<dyn Error>> {
|
||||||
|
cust::init(CudaFlags::empty())?;
|
||||||
|
|
||||||
|
let device = Device::get_device(device_index)?;
|
||||||
|
|
||||||
|
let name = device.name().unwrap_or_else(|_| "Unknown".into());
|
||||||
|
let memory = device.total_memory().unwrap_or(0);
|
||||||
|
let memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||||
|
|
||||||
|
let major = device
|
||||||
|
.get_attribute(DeviceAttribute::ComputeCapabilityMajor)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let minor = device
|
||||||
|
.get_attribute(DeviceAttribute::ComputeCapabilityMinor)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Selected GPU: {} ({:.2} GB, SM {}.{})",
|
||||||
|
name,
|
||||||
|
memory_gb,
|
||||||
|
major,
|
||||||
|
minor
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_unified = device
|
||||||
|
.get_attribute(DeviceAttribute::UnifiedAddressing)
|
||||||
|
.unwrap_or(0);
|
||||||
|
if has_unified == 0 {
|
||||||
|
panic!("Selected GPU does not support unified addressing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = Context::new(device)?;
|
||||||
|
|
||||||
|
CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?;
|
||||||
|
let stack_size = CurrentContext::get_resource_limit(ResourceLimit::StackSize)?;
|
||||||
|
log::info!("Reset stack size to {}", stack_size);
|
||||||
|
|
||||||
|
CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?;
|
||||||
|
CurrentContext::set_cache_config(CacheConfig::PreferL1)?;
|
||||||
|
|
||||||
|
let stream = Stream::new(StreamFlags::DEFAULT, None)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
stream,
|
||||||
|
profiler: Profiler::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GPU_STATE: Mutex<Option<GpuState>> = Mutex::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_init() {
|
||||||
|
if !get_options().use_gpu {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let device_id = get_options().gpu_device.unwrap_or(0);
|
||||||
|
log::info!("Initializing GPU Device {}", device_id);
|
||||||
|
|
||||||
|
match GpuState::init(device_id) {
|
||||||
|
Ok(state) => {
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::name_thread("MAIN_THREAD");
|
||||||
|
*GPU_STATE.lock() = Some(state);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Failed to initialize GPU: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_thread_init() {
|
||||||
|
if let Some(state) = GPU_STATE.lock().as_ref() {
|
||||||
|
if let Err(e) = CurrentContext::set_current(&state.context) {
|
||||||
|
log::error!("Failed to set CUDA context for thread: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_wait() {
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
if let Some(state) = guard.as_mut() {
|
||||||
|
if let Err(e) = state.stream.synchronize() {
|
||||||
|
log::error!("GPU Wait failed: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Launches a parallel for loop on the GPU.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `description`: Name for profiling.
|
||||||
|
/// * `n_items`: Total items (threads).
|
||||||
|
/// * `function`: Compiled kernel function handle.
|
||||||
|
/// * `params`: Kernel parameters (must be DeviceCopy).
|
||||||
|
pub fn gpu_parallel_for<T: DeviceCopy>(
|
||||||
|
description: &str,
|
||||||
|
n_items: i32,
|
||||||
|
function: &Function,
|
||||||
|
params: &T,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::range_push(description);
|
||||||
|
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
let state = guard.as_mut().expect("GPU not initialized");
|
||||||
|
|
||||||
|
let (_, block_size) = match function.suggested_launch_configuration(0, 0.into()) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => panic!(
|
||||||
|
"Failed to calculate launch config for {}: {:?}",
|
||||||
|
description, e
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
log::debug!("[{}] Block size: {}", description, block_size);
|
||||||
|
|
||||||
|
let grid_size = (n_items as u32 + block_size - 1) / block_size;
|
||||||
|
|
||||||
|
let stream = &state.stream;
|
||||||
|
let profiler = &mut state.profiler;
|
||||||
|
|
||||||
|
// Save the index we are about to use so we can retrieve the STOP event later
|
||||||
|
let event_idx = profiler.pool_offset;
|
||||||
|
|
||||||
|
{
|
||||||
|
let pe = profiler.prepare(description);
|
||||||
|
if let Err(e) = pe.start.record(stream) {
|
||||||
|
log::error!("Failed to record start event: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let params_ptr = params as *const T as *mut c_void;
|
||||||
|
let n_items_ptr = &n_items as *const i32 as *mut c_void;
|
||||||
|
let args = [params_ptr, n_items_ptr];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let Err(e) =
|
||||||
|
state
|
||||||
|
.stream
|
||||||
|
.launch(function, (grid_size, 1, 1), (block_size, 1, 1), 0, &args)
|
||||||
|
{
|
||||||
|
panic!("CUDA Launch failed for {}: {:?}", description, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the specific event we just set up.
|
||||||
|
// Pool_offset was incremented in prepare().
|
||||||
|
// If event_idx was the one used, the event is at event_idx.
|
||||||
|
if event_idx < profiler.event_pool.len() {
|
||||||
|
let pe = &mut profiler.event_pool[event_idx];
|
||||||
|
if let Err(e) = pe.stop.record(stream) {
|
||||||
|
log::error!("Failed to record stop event: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let _ = state.stream.synchronize();
|
||||||
|
|
||||||
|
#[cfg(feature = "use_nvtx")]
|
||||||
|
nvtx::range_pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_kernel_stats() {
|
||||||
|
let mut guard = GPU_STATE.lock();
|
||||||
|
if let Some(state) = guard.as_mut() {
|
||||||
|
let _ = state.stream.synchronize();
|
||||||
|
|
||||||
|
// Process all pending events
|
||||||
|
for pe in &mut state.profiler.event_pool {
|
||||||
|
if pe.active {
|
||||||
|
pe.sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_ms = 0.0;
|
||||||
|
for s in &state.profiler.kernel_stats {
|
||||||
|
total_ms += s.lock().sum_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Wavefront Kernel Profile:");
|
||||||
|
for s in &state.profiler.kernel_stats {
|
||||||
|
let stats = s.lock();
|
||||||
|
let percent = if total_ms > 0.0 {
|
||||||
|
100.0 * stats.sum_ms / total_ms
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
" {:<45} {:5} launches {:9.2} ms / {:5.1}% (avg {:6.3})",
|
||||||
|
stats.description,
|
||||||
|
stats.num_launches,
|
||||||
|
stats.sum_ms,
|
||||||
|
percent,
|
||||||
|
if stats.num_launches > 0 {
|
||||||
|
stats.sum_ms / stats.num_launches as f32
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("\nTotal: {:.2} ms", total_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_memset<T: DeviceCopy>(dst: &mut DeviceSlice<T>, value: u8) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = dst.as_raw_ptr(); // Returns CUdeviceptr (u64)
|
||||||
|
let len = dst.len() * std::mem::size_of::<T>();
|
||||||
|
|
||||||
|
// We need the `cust::external::cuda` or equivalent sys crate function
|
||||||
|
|
||||||
|
log::warn!("gpu_memset requested but raw memset not exposed via safe cust API yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_gpu_traits {
|
||||||
|
($name:ty) => {
|
||||||
|
unsafe impl cust::memory::DeviceCopy for $name {}
|
||||||
|
unsafe impl bytemuck::Zeroable for $name {}
|
||||||
|
unsafe impl bytemuck::Pod for $name {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_math_gpu_traits {
|
||||||
|
($Struct:ident) => {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
unsafe impl<T, const N: usize> cust::memory::DeviceCopy for $Struct<T, N> where
|
||||||
|
T: cust::memory::DeviceCopy + Copy
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, const N: usize> bytemuck::Zeroable for $Struct<T, N> where
|
||||||
|
T: bytemuck::Zeroable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, const N: usize> bytemuck::Pod for $Struct<T, N> where T: bytemuck::Pod {}
|
||||||
|
};
|
||||||
|
}
|
||||||
47
kernels/src/wavefront/mod.rs
Normal file
47
kernels/src/wavefront/mod.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use image_rs::Pixel;
|
||||||
|
|
||||||
|
use crate::camera::Camera;
|
||||||
|
use crate::core::film::Film;
|
||||||
|
use crate::core::filter::Filter;
|
||||||
|
use crate::core::sampler::Sampler;
|
||||||
|
use crate::core::scene::BasicScene;
|
||||||
|
use crate::lights::Light;
|
||||||
|
use crate::lights::LightSampler;
|
||||||
|
use crate::{
|
||||||
|
EscapedRayQueue, GetBSSRDFAndProbeRayQueue, HitAreaLightQueue, MaterialEvalQueue,
|
||||||
|
MediumSampleQueue, MediumScatterQueue, PixelSampleStateStorage, RayQueue, ShadowRayQueue,
|
||||||
|
SubsurfaceScatterQueue,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct WavefrontPathIntegrator {
|
||||||
|
pub film: Film,
|
||||||
|
pub filter: Filter,
|
||||||
|
pub sampler: Sampler,
|
||||||
|
pub camera: Arc<Camera>,
|
||||||
|
pub light_sampler: LightSampler,
|
||||||
|
pub infinite_lights: Option<Vec<Arc<Light>>>,
|
||||||
|
pub max_depth: i32,
|
||||||
|
pub samples_per_pixel: i32,
|
||||||
|
pub regularize: bool,
|
||||||
|
pub scanlines_per_pixel: i32,
|
||||||
|
pub max_queue_size: i32,
|
||||||
|
pub pixel_sample_state: PixelSampleStateStorage,
|
||||||
|
pub ray_queue: [RayQueue; 2],
|
||||||
|
pub hit_area_light_queue: HitAreaLightQueue,
|
||||||
|
pub shadow_ray_queue: ShadowRayQueue,
|
||||||
|
pub escaped_ray_queue: Option<EscapedRayQueue>,
|
||||||
|
pub basic_material_queue: Option<MaterialEvalQueue>,
|
||||||
|
pub universal_material_queue: Option<MaterialEvalQueue>,
|
||||||
|
pub medium_sample_queue: Option<MediumSampleQueue>,
|
||||||
|
pub medium_scatter_queue: Option<MediumScatterQueue>,
|
||||||
|
pub bssrf_queue: Option<GetBSSRDFAndProbeRayQueue>,
|
||||||
|
pub subsurface_queue: Option<SubsurfaceScatterQueue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
impl WavefrontPathIntegrator {
|
||||||
|
pub fn new(scene: BasicScene) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
535
kernels/src/workitem.rs
Normal file
535
kernels/src/workitem.rs
Normal file
|
|
@ -0,0 +1,535 @@
|
||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
use super::Float4;
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::geometry::{Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Vector3f};
|
||||||
|
use crate::lights::LightSampleContext;
|
||||||
|
use crate::soa_struct;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use cust::memory::{CopyDestination, DeviceMemory};
|
||||||
|
use cust::prelude::*;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! soa_struct {
|
||||||
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
pub struct $name:ident {
|
||||||
|
$(
|
||||||
|
pub $field:ident : $type:ty
|
||||||
|
),* $(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
$(#[$outer])*
|
||||||
|
pub struct $name {
|
||||||
|
capacity: u32,
|
||||||
|
pub count: cust::memory::DeviceBuffer<u32>,
|
||||||
|
$(
|
||||||
|
pub $field: cust::memory::DeviceBuffer<$type>,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
impl $name {
|
||||||
|
pub fn new(capacity: usize) -> cust::error::CudaResult<Self> {
|
||||||
|
use cust::memory::DeviceBuffer;
|
||||||
|
Ok(Self {
|
||||||
|
capacity: capacity as u32,
|
||||||
|
count: DeviceBuffer::zeroed(1)?,
|
||||||
|
$(
|
||||||
|
$field: DeviceBuffer::zeroed(capacity)?,
|
||||||
|
)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> cust::error::CudaResult<u32> {
|
||||||
|
let mut host_count = [0u32; 1];
|
||||||
|
self.count.copy_to(&mut host_count)?;
|
||||||
|
Ok(host_count[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) -> cust::error::CudaResult<()> {
|
||||||
|
self.count.copy_from(&[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the View name
|
||||||
|
pub fn as_view(&mut self) -> paste::paste! { [<$name View>] } {
|
||||||
|
paste::paste! {
|
||||||
|
[<$name View>] {
|
||||||
|
capacity: self.capacity,
|
||||||
|
count: self.count.as_device_ptr().as_mut_ptr(),
|
||||||
|
$(
|
||||||
|
$field: self.$field.as_device_ptr().as_raw() as *mut $type,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::paste! {
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct [<$name View>] {
|
||||||
|
pub capacity: u32,
|
||||||
|
pub count: *mut u32,
|
||||||
|
$(
|
||||||
|
pub $field: *mut $type,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl cust::memory::DeviceCopy for [<$name View>] {}
|
||||||
|
|
||||||
|
impl [<$name View>] {
|
||||||
|
// The raw push that fills every field
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push(&self, $( $field : $type ),* ) -> Option<u32> {
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
let index = unsafe {
|
||||||
|
let counter_ptr = self.count as *mut AtomicU32;
|
||||||
|
(*counter_ptr).fetch_add(1, Ordering::Relaxed)
|
||||||
|
};
|
||||||
|
|
||||||
|
if index >= self.capacity {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
$(
|
||||||
|
*self.$field.add(index as usize) = $field;
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn size(&self) -> u32 {
|
||||||
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
unsafe {
|
||||||
|
(*(self.count as *const AtomicU32)).load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub fn [<$field _ptr>](&self) -> *mut $type {
|
||||||
|
self.$field
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesDirect {
|
||||||
|
pub u: Point2f,
|
||||||
|
pub uc: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesIndirect {
|
||||||
|
pub uc: Float,
|
||||||
|
pub rr: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamplesSubsurface {
|
||||||
|
pub uc: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RaySamples {
|
||||||
|
pub direct: RaySamplesDirect,
|
||||||
|
pub indirect: RaySamplesIndirect,
|
||||||
|
pub have_subsurface: bool,
|
||||||
|
pub subsurface: RaySamplesSubsurface,
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct RayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct PixelSampleStateStorage {
|
||||||
|
pub p_pixel: Point2i,
|
||||||
|
pub l: SampledSpectrum,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub filter_weight: Float,
|
||||||
|
pub visible_surface: u32,
|
||||||
|
pub camera_ray_weight: SampledSpectrum,
|
||||||
|
|
||||||
|
pub rs_direct_packed: Float4,
|
||||||
|
pub rs_indirect_packed: Float4,
|
||||||
|
pub rs_subsurface_packed: Float4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct EscapedRayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct HitAreaLightQueue {
|
||||||
|
pub area_light_id: u32, // Light ID
|
||||||
|
pub p: Point3f,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub depth: i32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct ShadowRayQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub ld: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct GetBSSRDFAndProbeRayQueue {
|
||||||
|
pub material_id: u32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub p: Point3f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct SubsurfaceScatterQueue {
|
||||||
|
pub p0: Point3f,
|
||||||
|
pub p1: Point3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub material_id: u32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MediumSampleQueue {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
|
||||||
|
pub ctx_pi: Point3f,
|
||||||
|
pub ctx_n: Normal3f,
|
||||||
|
pub ctx_ns: Normal3f,
|
||||||
|
|
||||||
|
pub specular_bounce: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
|
||||||
|
pub area_light_id: u32,
|
||||||
|
pub pi: Point3fi,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub dpdu: Vector3f,
|
||||||
|
pub dpdv: Vector3f,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub material_id: u32,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub dpdvs: Vector3f,
|
||||||
|
pub dndus: Normal3f,
|
||||||
|
pub dndvs: Normal3f,
|
||||||
|
pub face_index: i32,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MaterialEvalQueue {
|
||||||
|
pub material_id: u32,
|
||||||
|
pub pi: Point3fi,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub dpdu: Vector3f,
|
||||||
|
pub dpdv: Vector3f,
|
||||||
|
pub time: Float,
|
||||||
|
pub depth: i32,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub dpdus: Vector3f,
|
||||||
|
pub dpdvs: Vector3f,
|
||||||
|
pub dndus: Normal3f,
|
||||||
|
pub dndvs: Normal3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub face_index: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub any_non_specular_bounces: u32,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub mi_inside: u32,
|
||||||
|
pub mi_outside: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soa_struct! {
|
||||||
|
pub struct MediumScatterQueue {
|
||||||
|
pub p: Point3f,
|
||||||
|
pub depth: usize,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub wo: Vector3f,
|
||||||
|
pub time: Float,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub pixel_index: usize,
|
||||||
|
|
||||||
|
// ID
|
||||||
|
pub phase_function: u32,
|
||||||
|
pub medium: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RayWorkItem {
|
||||||
|
pub ray: Ray,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub prev_intr_ctx: LightSampleContext,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub specular_bounce: bool,
|
||||||
|
pub any_non_specular_bounces: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct EscapedRayWorkItem {
|
||||||
|
pub ray_o: Point3f,
|
||||||
|
pub ray_d: Vector3f,
|
||||||
|
pub depth: i32,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
pub beta: SampledSpectrum,
|
||||||
|
pub specular_bounce: bool,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub prev_intr_ctx: LightSampleContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ShadowRayWorkItem {
|
||||||
|
pub ray: Ray,
|
||||||
|
pub t_max: Float,
|
||||||
|
pub lambda: SampledWavelengths,
|
||||||
|
pub ld: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub pixel_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RayQueueView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push_work_item(&self, item: RayWorkItem) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
self.push(
|
||||||
|
item.ray.o,
|
||||||
|
item.ray.d,
|
||||||
|
item.depth,
|
||||||
|
item.lambda,
|
||||||
|
item.pixel_index,
|
||||||
|
item.beta,
|
||||||
|
item.r_u,
|
||||||
|
item.r_l,
|
||||||
|
item.prev_intr_ctx.pi.into(),
|
||||||
|
item.prev_intr_ctx.n,
|
||||||
|
item.prev_intr_ctx.ns,
|
||||||
|
item.eta_scale,
|
||||||
|
if item.specular_bounce { 1 } else { 0 },
|
||||||
|
if item.any_non_specular_bounces { 1 } else { 0 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EscapedRayQueueView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn push_work_item(&self, r: &RayWorkItem) -> Option<u32> {
|
||||||
|
unsafe {
|
||||||
|
self.push(
|
||||||
|
r.ray.o,
|
||||||
|
r.ray.d,
|
||||||
|
r.depth,
|
||||||
|
r.lambda,
|
||||||
|
r.pixel_index,
|
||||||
|
r.beta,
|
||||||
|
if r.specular_bounce { 1 } else { 0 },
|
||||||
|
r.r_u,
|
||||||
|
r.r_l,
|
||||||
|
r.prev_intr_ctx.pi.into(),
|
||||||
|
r.prev_intr_ctx.n,
|
||||||
|
r.prev_intr_ctx.ns,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelSampleStateStorageView {
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn get_samples(&self, index: u32) -> RaySamples {
|
||||||
|
let i = index as usize;
|
||||||
|
|
||||||
|
let (dir, ind, ss) = unsafe {
|
||||||
|
(
|
||||||
|
*self.rs_direct_packed.add(i),
|
||||||
|
*self.rs_indirect_packed.add(i),
|
||||||
|
*self.rs_subsurface_packed.add(i),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let direct_u = Point2f::new(dir.v[0], dir.v[1]);
|
||||||
|
let direct_uc = dir.v[2];
|
||||||
|
let flags = dir.v[3] as i32;
|
||||||
|
let have_subsurface = (flags & 1) != 0;
|
||||||
|
|
||||||
|
let indirect_uc = ind.v[0];
|
||||||
|
let indirect_rr = ind.v[1];
|
||||||
|
let indirect_u = Point2f::new(ind.v[2], ind.v[3]);
|
||||||
|
|
||||||
|
let subsurface_uc = ss.v[0];
|
||||||
|
let subsurface_u = Point2f::new(ss.v[1], ss.v[2]);
|
||||||
|
|
||||||
|
RaySamples {
|
||||||
|
direct: RaySamplesDirect {
|
||||||
|
u: direct_u,
|
||||||
|
uc: direct_uc,
|
||||||
|
},
|
||||||
|
indirect: RaySamplesIndirect {
|
||||||
|
uc: indirect_uc,
|
||||||
|
rr: indirect_rr,
|
||||||
|
u: indirect_u,
|
||||||
|
},
|
||||||
|
have_subsurface,
|
||||||
|
subsurface: RaySamplesSubsurface {
|
||||||
|
uc: subsurface_uc,
|
||||||
|
u: subsurface_u,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "use_gpu")]
|
||||||
|
pub unsafe fn set_samples(&self, index: u32, rs: RaySamples) {
|
||||||
|
if index >= self.capacity {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = index as usize;
|
||||||
|
|
||||||
|
let flags = if rs.have_subsurface { 1.0 } else { 0.0 };
|
||||||
|
let dir = Float4 {
|
||||||
|
v: [rs.direct.u.0[0], rs.direct.u.0[1], rs.direct.uc, flags],
|
||||||
|
};
|
||||||
|
|
||||||
|
let ind = Float4 {
|
||||||
|
v: [
|
||||||
|
rs.indirect.uc,
|
||||||
|
rs.indirect.rr,
|
||||||
|
rs.indirect.u.0[0],
|
||||||
|
rs.indirect.u.0[1],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*self.rs_direct_packed.add(i) = dir;
|
||||||
|
*self.rs_indirect_packed.add(i) = ind;
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.have_subsurface {
|
||||||
|
let ss = Float4 {
|
||||||
|
v: [
|
||||||
|
rs.subsurface.uc,
|
||||||
|
rs.subsurface.u.0[0],
|
||||||
|
rs.subsurface.u.0[1],
|
||||||
|
0.0,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
*self.rs_subsurface_packed.add(i) = ss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
shared/Cargo.toml
Normal file
6
shared/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "shared"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
@ -9,27 +9,33 @@ pub use realistic::RealisticCamera;
|
||||||
pub use spherical::SphericalCamera;
|
pub use spherical::SphericalCamera;
|
||||||
|
|
||||||
use crate::core::film::{Film, FilmTrait};
|
use crate::core::film::{Film, FilmTrait};
|
||||||
use crate::core::interaction::Interaction;
|
use crate::core::geometry::{
|
||||||
use crate::core::medium::Medium;
|
|
||||||
use crate::core::pbrt::{Float, RenderingCoordinateSystem, lerp};
|
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::geometry::{
|
|
||||||
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::interaction::Interaction;
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::RenderingCoordinateSystem;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::image::ImageMetadata;
|
use crate::image::ImageMetadata;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::math::lerp;
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CameraRay {
|
pub struct CameraRay {
|
||||||
pub ray: Ray,
|
pub ray: Ray,
|
||||||
pub weight: SampledSpectrum,
|
pub weight: SampledSpectrum,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CameraWiSample {
|
pub struct CameraWiSample {
|
||||||
wi_spec: SampledSpectrum,
|
wi_spec: SampledSpectrum,
|
||||||
|
|
@ -40,10 +46,10 @@ pub struct CameraWiSample {
|
||||||
p_lens: Interaction,
|
p_lens: Interaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CameraTransform {
|
pub struct CameraTransform {
|
||||||
render_from_camera: AnimatedTransform,
|
pub render_from_camera: AnimatedTransform,
|
||||||
world_from_render: Transform<Float>,
|
pub world_from_render: Transform,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTransform {
|
impl CameraTransform {
|
||||||
|
|
@ -80,7 +86,7 @@ impl CameraTransform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn camera_from_world(&self, time: Float) -> Transform<Float> {
|
pub fn camera_from_world(&self, time: Float) -> Transform {
|
||||||
(self.world_from_render * self.render_from_camera.interpolate(time)).inverse()
|
(self.world_from_render * self.render_from_camera.interpolate(time)).inverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,12 +115,49 @@ impl CameraTransform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CameraBaseParameters {
|
||||||
|
pub camera_transform: CameraTransform,
|
||||||
|
pub shutter_open: Float,
|
||||||
|
pub shutter_close: Float,
|
||||||
|
pub film: Arc<Film>,
|
||||||
|
pub medium_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraBaseParameters {
|
||||||
|
pub fn new(
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Arc<Medium>,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let mut shutter_open = params.get_one_float("shutteropen", 0.);
|
||||||
|
let mut shutter_close = params.get_one_float("shutterclose", 1.);
|
||||||
|
if shutter_close < shutter_open {
|
||||||
|
eprint!(
|
||||||
|
"{}: Shutter close time {} < shutter open {}. Swapping",
|
||||||
|
loc, shutter_close, shutter_open
|
||||||
|
);
|
||||||
|
std::mem::swap(&mut shutter_open, &mut shutter_close);
|
||||||
|
}
|
||||||
|
CameraBaseParameters {
|
||||||
|
camera_transform: Arc::new(camera_transform.clone()),
|
||||||
|
shutter_open,
|
||||||
|
shutter_close,
|
||||||
|
film,
|
||||||
|
medium: Some(medium),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CameraBase {
|
pub struct CameraBase {
|
||||||
pub camera_transform: CameraTransform,
|
pub camera_transform: CameraTransform,
|
||||||
pub shutter_open: Float,
|
pub shutter_open: Float,
|
||||||
pub shutter_close: Float,
|
pub shutter_close: Float,
|
||||||
pub film: Film,
|
pub film: Arc<Film>,
|
||||||
pub medium: Option<Arc<Medium>>,
|
pub medium: Option<Arc<Medium>>,
|
||||||
pub min_pos_differential_x: Vector3f,
|
pub min_pos_differential_x: Vector3f,
|
||||||
pub min_pos_differential_y: Vector3f,
|
pub min_pos_differential_y: Vector3f,
|
||||||
|
|
@ -124,18 +167,32 @@ pub struct CameraBase {
|
||||||
|
|
||||||
impl CameraBase {
|
impl CameraBase {
|
||||||
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
||||||
let camera_from_world: Transform<Float> =
|
let camera_from_world: Transform =
|
||||||
self.camera_transform.camera_from_world(self.shutter_open);
|
self.camera_transform.camera_from_world(self.shutter_open);
|
||||||
|
|
||||||
metadata.camera_from_world = Some(camera_from_world.get_matrix());
|
metadata.camera_from_world = Some(camera_from_world.get_matrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(p: CameraBaseParameters) -> Self {
|
||||||
|
Self {
|
||||||
|
camera_transform: p.camera_transform.as_ref().clone(),
|
||||||
|
shutter_open: p.shutter_open,
|
||||||
|
shutter_close: p.shutter_close,
|
||||||
|
film: p.film.clone(),
|
||||||
|
medium: p.medium,
|
||||||
|
min_pos_differential_x: Vector3f::default(),
|
||||||
|
min_pos_differential_y: Vector3f::default(),
|
||||||
|
min_dir_differential_x: Vector3f::default(),
|
||||||
|
min_dir_differential_y: Vector3f::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait CameraTrait {
|
pub trait CameraTrait {
|
||||||
fn base(&self) -> &CameraBase;
|
fn base(&self) -> &CameraBase;
|
||||||
fn get_film(&self) -> Film {
|
fn get_film(&self) -> &Film {
|
||||||
self.base().film.clone()
|
&self.base().film
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolution(&self) -> Point2i {
|
fn resolution(&self) -> Point2i {
|
||||||
|
|
@ -260,6 +317,39 @@ pub enum Camera {
|
||||||
Realistic(RealisticCamera),
|
Realistic(RealisticCamera),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn create(
|
||||||
|
name: &str,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
medium: Medium,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"perspective" => {
|
||||||
|
let camera =
|
||||||
|
PerspectiveCamera::create(params, camera_transform, film, medium, loc)?;
|
||||||
|
Ok(Camera::Perspective(camera))
|
||||||
|
}
|
||||||
|
"orthographic" => {
|
||||||
|
let camera =
|
||||||
|
OrthographicCamera::create(params, camera_transform, film, medium, loc)?;
|
||||||
|
Ok(Camera::Orthographic(camera))
|
||||||
|
}
|
||||||
|
"realistic" => {
|
||||||
|
let camera = RealisticCamera::create(params, camera_transform, film, medium, loc)?;
|
||||||
|
Ok(Camera::Realistic(camera))
|
||||||
|
}
|
||||||
|
"spherical" => {
|
||||||
|
let camera = SphericalCamera::create(params, camera_transform, film, medium, loc)?;
|
||||||
|
Ok(Camera::Spherical(camera))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Camera type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LensElementInterface {
|
pub struct LensElementInterface {
|
||||||
pub curvature_radius: Float,
|
pub curvature_radius: Float,
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait};
|
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::{Film, FilmTrait};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::geometry::{
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::get_options;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct OrthographicCamera {
|
pub struct OrthographicCamera {
|
||||||
pub base: CameraBase,
|
pub base: CameraBase,
|
||||||
pub screen_from_camera: Transform<Float>,
|
pub screen_from_camera: TransformGeneric<Float>,
|
||||||
pub camera_from_raster: Transform<Float>,
|
pub camera_from_raster: TransformGeneric<Float>,
|
||||||
pub raster_from_screen: Transform<Float>,
|
pub raster_from_screen: TransformGeneric<Float>,
|
||||||
pub screen_from_raster: Transform<Float>,
|
pub screen_from_raster: TransformGeneric<Float>,
|
||||||
pub lens_radius: Float,
|
pub lens_radius: Float,
|
||||||
pub focal_distance: Float,
|
pub focal_distance: Float,
|
||||||
pub dx_camera: Vector3f,
|
pub dx_camera: Vector3f,
|
||||||
|
|
@ -29,23 +34,21 @@ impl OrthographicCamera {
|
||||||
lens_radius: Float,
|
lens_radius: Float,
|
||||||
focal_distance: Float,
|
focal_distance: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ndc_from_screen: Transform<Float> = Transform::scale(
|
let ndc_from_screen: TransformGeneric<Float> = TransformGeneric::scale(
|
||||||
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
||||||
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
||||||
1.,
|
1.,
|
||||||
) * Transform::translate(Vector3f::new(
|
) * TransformGeneric::translate(
|
||||||
-screen_window.p_min.x(),
|
Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.),
|
||||||
-screen_window.p_max.y(),
|
);
|
||||||
0.,
|
let raster_from_ndc = TransformGeneric::scale(
|
||||||
));
|
|
||||||
let raster_from_ndc = Transform::scale(
|
|
||||||
base.film.full_resolution().x() as Float,
|
base.film.full_resolution().x() as Float,
|
||||||
-base.film.full_resolution().y() as Float,
|
-base.film.full_resolution().y() as Float,
|
||||||
1.,
|
1.,
|
||||||
);
|
);
|
||||||
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
||||||
let screen_from_raster = raster_from_screen.inverse();
|
let screen_from_raster = raster_from_screen.inverse();
|
||||||
let screen_from_camera = Transform::orthographic(0., 1.);
|
let screen_from_camera = TransformGeneric::orthographic(0., 1.);
|
||||||
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
||||||
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
|
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
|
||||||
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
|
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
|
||||||
|
|
@ -66,6 +69,49 @@ impl OrthographicCamera {
|
||||||
dy_camera,
|
dy_camera,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Medium,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!("{}: screenwindow param must have four values", loc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::new(base, screen, lens_radius, focal_distance))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for OrthographicCamera {
|
impl CameraTrait for OrthographicCamera {
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait};
|
use super::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||||
use crate::camera;
|
use crate::camera::CameraBaseParameters;
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::{Film, FilmTrait};
|
||||||
use crate::core::filter::FilterTrait;
|
use crate::core::filter::FilterTrait;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::geometry::{
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::get_options;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::Transform;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PerspectiveCamera {
|
pub struct PerspectiveCamera {
|
||||||
pub base: CameraBase,
|
pub base: CameraBase,
|
||||||
pub screen_from_camera: Transform<Float>,
|
pub screen_from_camera: Transform,
|
||||||
pub camera_from_raster: Transform<Float>,
|
pub camera_from_raster: Transform,
|
||||||
pub raster_from_screen: Transform<Float>,
|
pub raster_from_screen: Transform,
|
||||||
pub screen_from_raster: Transform<Float>,
|
pub screen_from_raster: Transform,
|
||||||
pub lens_radius: Float,
|
pub lens_radius: Float,
|
||||||
pub focal_distance: Float,
|
pub focal_distance: Float,
|
||||||
pub dx_camera: Vector3f,
|
pub dx_camera: Vector3f,
|
||||||
|
|
@ -28,12 +33,14 @@ pub struct PerspectiveCamera {
|
||||||
impl PerspectiveCamera {
|
impl PerspectiveCamera {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
screen_from_camera: &Transform<Float>,
|
fov: Float,
|
||||||
screen_window: Bounds2f,
|
screen_window: Bounds2f,
|
||||||
lens_radius: Float,
|
lens_radius: Float,
|
||||||
focal_distance: Float,
|
focal_distance: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ndc_from_screen: Transform<Float> = Transform::scale(
|
let screen_from_camera =
|
||||||
|
Transform::perspective(fov, 1e-2, 1000.).expect("Could not create perspective");
|
||||||
|
let ndc_from_screen = Transform::scale(
|
||||||
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
||||||
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
||||||
1.,
|
1.,
|
||||||
|
|
@ -59,9 +66,10 @@ impl PerspectiveCamera {
|
||||||
let w_corner_camera =
|
let w_corner_camera =
|
||||||
(camera_from_raster.apply_to_point(p_corner) - Point3f::new(0., 0., 0.)).normalize();
|
(camera_from_raster.apply_to_point(p_corner) - Point3f::new(0., 0., 0.)).normalize();
|
||||||
let cos_total_width = w_corner_camera.z();
|
let cos_total_width = w_corner_camera.z();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
screen_from_camera: *screen_from_camera,
|
screen_from_camera,
|
||||||
camera_from_raster,
|
camera_from_raster,
|
||||||
raster_from_screen,
|
raster_from_screen,
|
||||||
screen_from_raster,
|
screen_from_raster,
|
||||||
|
|
@ -72,6 +80,50 @@ impl PerspectiveCamera {
|
||||||
cos_total_width,
|
cos_total_width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Medium,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!("{}: screenwindow param must have four values", loc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fov = params.get_one_float("fov", 90.);
|
||||||
|
Ok(Self::new(base, fov, screen, lens_radius, focal_distance))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for PerspectiveCamera {
|
impl CameraTrait for PerspectiveCamera {
|
||||||
|
|
@ -1,21 +1,32 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait, ExitPupilSample, LensElementInterface};
|
use super::{
|
||||||
use crate::core::film::FilmTrait;
|
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample,
|
||||||
use crate::core::pbrt::{Float, lerp};
|
LensElementInterface,
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2i, Vector3f, VectorLike,
|
|
||||||
};
|
};
|
||||||
use crate::image::Image;
|
use crate::PI;
|
||||||
|
use crate::core::film::{Film, FilmTrait};
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::sampler::CameraSample;
|
||||||
|
use crate::core::scattering::refract;
|
||||||
|
use crate::image::{Image, PixelFormat};
|
||||||
|
use crate::spectra::color::SRGB;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::math::{quadratic, square};
|
use crate::utils::error::FileLoc;
|
||||||
use crate::utils::scattering::refract;
|
use crate::utils::file::read_float_file;
|
||||||
|
use crate::utils::math::{lerp, quadratic, square};
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RealisticCamera {
|
pub struct RealisticCamera {
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
aperture_image: Image,
|
aperture_image: Option<Image>,
|
||||||
element_interface: Vec<LensElementInterface>,
|
element_interface: Vec<LensElementInterface>,
|
||||||
physical_extent: Bounds2f,
|
physical_extent: Bounds2f,
|
||||||
exit_pupil_bounds: Vec<Bounds2f>,
|
exit_pupil_bounds: Vec<Bounds2f>,
|
||||||
|
|
@ -23,12 +34,11 @@ pub struct RealisticCamera {
|
||||||
|
|
||||||
impl RealisticCamera {
|
impl RealisticCamera {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
&self,
|
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
lens_params: Vec<Float>,
|
lens_params: Vec<Float>,
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
aperture_image: Image,
|
aperture_image: Option<Image>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let aspect =
|
let aspect =
|
||||||
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
||||||
|
|
@ -68,7 +78,7 @@ impl RealisticCamera {
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let r0 = (i as Float / n_samples as Float) * half_diag;
|
let r0 = (i as Float / n_samples as Float) * half_diag;
|
||||||
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag;
|
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag;
|
||||||
self.bound_exit_pupil(r0, r1)
|
Self::compute_exit_pupil_bounds(&element_interface, r0, r1)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
@ -99,10 +109,14 @@ impl RealisticCamera {
|
||||||
self.element_interface.last().unwrap().aperture_radius
|
self.element_interface.last().unwrap().aperture_radius
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
fn compute_exit_pupil_bounds(
|
||||||
|
elements: &[LensElementInterface],
|
||||||
|
film_x_0: Float,
|
||||||
|
film_x_1: Float,
|
||||||
|
) -> Bounds2f {
|
||||||
let mut pupil_bounds = Bounds2f::default();
|
let mut pupil_bounds = Bounds2f::default();
|
||||||
let n_samples = 1024 * 1024;
|
let n_samples = 1024 * 1024;
|
||||||
let rear_radius = self.rear_element_radius();
|
let rear_radius = elements.last().unwrap().aperture_radius;
|
||||||
let proj_rear_bounds = Bounds2f::from_points(
|
let proj_rear_bounds = Bounds2f::from_points(
|
||||||
Point2f::new(-1.5 * rear_radius, -1.5 * rear_radius),
|
Point2f::new(-1.5 * rear_radius, -1.5 * rear_radius),
|
||||||
Point2f::new(1.5 * rear_radius, 1.5 * rear_radius),
|
Point2f::new(1.5 * rear_radius, 1.5 * rear_radius),
|
||||||
|
|
@ -134,7 +148,6 @@ impl RealisticCamera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return degenerate bounds if no rays made it through the lens system
|
|
||||||
if pupil_bounds.is_degenerate() {
|
if pupil_bounds.is_degenerate() {
|
||||||
print!(
|
print!(
|
||||||
"Unable to find exit pupil in x = {},{} on film.",
|
"Unable to find exit pupil in x = {},{} on film.",
|
||||||
|
|
@ -143,7 +156,12 @@ impl RealisticCamera {
|
||||||
return pupil_bounds;
|
return pupil_bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt())
|
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt());
|
||||||
|
pupil_bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||||
|
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersect_spherical_element(
|
pub fn intersect_spherical_element(
|
||||||
|
|
@ -291,6 +309,185 @@ impl RealisticCamera {
|
||||||
|
|
||||||
Some(ExitPupilSample { p_pupil, pdf })
|
Some(ExitPupilSample { p_pupil, pdf })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Medium,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let aperture_diameter = params.get_one_float("aperturediameter", 1.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 10.);
|
||||||
|
let lens_file = params.get_one_string("lensfile", "");
|
||||||
|
|
||||||
|
if lens_file.is_empty() {
|
||||||
|
return Err(format!("{}: No lens file supplied", loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
if lens_params.len() % 4 != 0 {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
|
||||||
|
loc,
|
||||||
|
lens_params.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let builtin_res = 256;
|
||||||
|
let rasterize = |vert: &[Point2f]| -> Image {
|
||||||
|
let mut image = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = image.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
// Map pixel to [-1, 1] range
|
||||||
|
let p = Point2f::new(
|
||||||
|
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
||||||
|
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut winding_number = 0;
|
||||||
|
// Winding number test against edges
|
||||||
|
for i in 0..vert.len() {
|
||||||
|
let i1 = (i + 1) % vert.len();
|
||||||
|
let v_i = vert[i];
|
||||||
|
let v_i1 = vert[i1];
|
||||||
|
|
||||||
|
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
|
||||||
|
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
|
||||||
|
|
||||||
|
if v_i.y() <= p.y() {
|
||||||
|
if v_i1.y() > p.y() && e > 0.0 {
|
||||||
|
winding_number += 1;
|
||||||
|
}
|
||||||
|
} else if v_i1.y() <= p.y() && e < 0.0 {
|
||||||
|
winding_number -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image.set_channel(
|
||||||
|
Point2i::new(x, y),
|
||||||
|
0,
|
||||||
|
if winding_number == 0 { 0.0 } else { 1.0 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image
|
||||||
|
};
|
||||||
|
|
||||||
|
let aperture_name = params.get_one_string("aperture", "");
|
||||||
|
let mut aperture_image: Option<Image> = None;
|
||||||
|
|
||||||
|
if !aperture_name.is_empty() {
|
||||||
|
match aperture_name.as_str() {
|
||||||
|
"gaussian" => {
|
||||||
|
let mut img = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
let res = img.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
let uv = Point2f::new(
|
||||||
|
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
||||||
|
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
||||||
|
);
|
||||||
|
let r2 = square(uv.x()) + square(uv.y());
|
||||||
|
let sigma2 = 1.0;
|
||||||
|
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
|
||||||
|
img.set_channel(Point2i::new(x, y), 0, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(img);
|
||||||
|
}
|
||||||
|
"square" => {
|
||||||
|
let mut img = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
let low = (0.25 * builtin_res as Float) as i32;
|
||||||
|
let high = (0.75 * builtin_res as Float) as i32;
|
||||||
|
for y in low..high {
|
||||||
|
for x in low..high {
|
||||||
|
img.set_channel(Point2i::new(x, y), 0, 4.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(img);
|
||||||
|
}
|
||||||
|
"pentagon" => {
|
||||||
|
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
|
||||||
|
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
|
||||||
|
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
||||||
|
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
||||||
|
let mut vert = [
|
||||||
|
Point2f::new(0.0, 1.0),
|
||||||
|
Point2f::new(s1, c1),
|
||||||
|
Point2f::new(s2, -c2),
|
||||||
|
Point2f::new(-s2, -c2),
|
||||||
|
Point2f::new(-s1, c1),
|
||||||
|
];
|
||||||
|
for v in vert.iter_mut() {
|
||||||
|
*v = Point2f::from(Vector2f::from(*v) * 0.8);
|
||||||
|
}
|
||||||
|
aperture_image = Some(rasterize(&vert));
|
||||||
|
}
|
||||||
|
"star" => {
|
||||||
|
let mut vert = Vec::with_capacity(10);
|
||||||
|
for i in 0..10 {
|
||||||
|
let r = if i % 2 == 1 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
|
||||||
|
};
|
||||||
|
let angle = PI * i as Float / 5.0;
|
||||||
|
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
|
||||||
|
}
|
||||||
|
vert.reverse();
|
||||||
|
aperture_image = Some(rasterize(&vert));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Ok(im) = Image::read(Path::new(&aperture_name), None) {
|
||||||
|
if im.image.n_channels() > 1 {
|
||||||
|
let mut mono =
|
||||||
|
Image::new(PixelFormat::F32, im.image.resolution(), &["Y"], SRGB);
|
||||||
|
let res = mono.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
let avg =
|
||||||
|
im.image.get_channels_default(Point2i::new(x, y)).average();
|
||||||
|
mono.set_channel(Point2i::new(x, y), 0, avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(mono);
|
||||||
|
} else {
|
||||||
|
aperture_image = Some(im.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::new(
|
||||||
|
base,
|
||||||
|
lens_params,
|
||||||
|
focal_distance,
|
||||||
|
aperture_diameter,
|
||||||
|
aperture_image,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for RealisticCamera {
|
impl CameraTrait for RealisticCamera {
|
||||||
135
shared/src/camera/spherical.rs
Normal file
135
shared/src/camera/spherical.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
|
||||||
|
use crate::core::film::{Film, FilmTrait};
|
||||||
|
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::get_options;
|
||||||
|
use crate::core::pbrt::{Float, PI};
|
||||||
|
use crate::core::sampler::CameraSample;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Mapping {
|
||||||
|
EquiRectangular,
|
||||||
|
EqualArea,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SphericalCamera {
|
||||||
|
pub base: CameraBase,
|
||||||
|
pub screen: Bounds2f,
|
||||||
|
pub lens_radius: Float,
|
||||||
|
pub focal_distance: Float,
|
||||||
|
pub mapping: Mapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SphericalCamera {
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Medium,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e30);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!("{}: screenwindow param must have four values", loc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = params.get_one_string("mapping", "equalarea");
|
||||||
|
let mapping = match m.as_str() {
|
||||||
|
"equal_area" => Mapping::EqualArea,
|
||||||
|
"equirectangular" => Mapping::EquiRectangular,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: unknown mapping for spherical camera at {}",
|
||||||
|
m, loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
mapping,
|
||||||
|
base,
|
||||||
|
screen,
|
||||||
|
lens_radius,
|
||||||
|
focal_distance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraTrait for SphericalCamera {
|
||||||
|
fn base(&self) -> &CameraBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||||
|
self.base.init_metadata(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_ray(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
_lamdba: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
|
// Compute spherical camera ray direction
|
||||||
|
let mut uv = Point2f::new(
|
||||||
|
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
||||||
|
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
|
||||||
|
);
|
||||||
|
let dir: Vector3f;
|
||||||
|
if self.mapping == Mapping::EquiRectangular {
|
||||||
|
// Compute ray direction using equirectangular mapping
|
||||||
|
let theta = PI * uv[1];
|
||||||
|
let phi = 2. * PI * uv[0];
|
||||||
|
dir = spherical_direction(theta.sin(), theta.cos(), phi);
|
||||||
|
} else {
|
||||||
|
// Compute ray direction using equal area mapping
|
||||||
|
uv = wrap_equal_area_square(&mut uv);
|
||||||
|
dir = equal_area_square_to_sphere(uv);
|
||||||
|
}
|
||||||
|
std::mem::swap(&mut dir.y(), &mut dir.z());
|
||||||
|
|
||||||
|
let ray = Ray::new(
|
||||||
|
Point3f::new(0., 0., 0.),
|
||||||
|
dir,
|
||||||
|
Some(self.sample_time(sample.time)),
|
||||||
|
self.base().medium.clone(),
|
||||||
|
);
|
||||||
|
Some(CameraRay {
|
||||||
|
ray: self.render_from_camera(&ray, &mut None),
|
||||||
|
weight: SampledSpectrum::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
||||||
use crate::core::pbrt::{Float, find_interval};
|
use crate::core::pbrt::{Float, find_interval};
|
||||||
use crate::core::primitive::PrimitiveTrait;
|
use crate::core::primitive::PrimitiveTrait;
|
||||||
use crate::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
|
||||||
use crate::shapes::ShapeIntersection;
|
use crate::shapes::ShapeIntersection;
|
||||||
use crate::utils::math::encode_morton_3;
|
use crate::utils::math::encode_morton_3;
|
||||||
use crate::utils::math::next_float_down;
|
use crate::utils::math::next_float_down;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::core::bxdf::BSDF;
|
use crate::core::bxdf::BSDF;
|
||||||
use crate::core::interaction::{InteractionData, ShadingGeometry, SurfaceInteraction};
|
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
|
||||||
|
use crate::core::interaction::{InteractionData, Shadinggeom, SurfaceInteraction};
|
||||||
use crate::core::pbrt::{Float, PI};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
|
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
||||||
use crate::utils::math::{catmull_rom_weights, square};
|
use crate::utils::math::{catmull_rom_weights, square};
|
||||||
|
|
@ -10,10 +10,10 @@ use enum_dispatch::enum_dispatch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BSSRDFSample<'a> {
|
pub struct BSSRDFSample {
|
||||||
pub sp: SampledSpectrum,
|
pub sp: SampledSpectrum,
|
||||||
pub pdf: SampledSpectrum,
|
pub pdf: SampledSpectrum,
|
||||||
pub sw: BSDF<'a>,
|
pub sw: BSDF,
|
||||||
pub wo: Vector3f,
|
pub wo: Vector3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
|
||||||
dpdv: ssi.dpdv,
|
dpdv: ssi.dpdv,
|
||||||
dndu: Normal3f::zero(),
|
dndu: Normal3f::zero(),
|
||||||
dndv: Normal3f::zero(),
|
dndv: Normal3f::zero(),
|
||||||
shading: ShadingGeometry {
|
shading: Shadinggeom {
|
||||||
n: ssi.ns,
|
n: ssi.ns,
|
||||||
dpdu: ssi.dpdus,
|
dpdu: ssi.dpdus,
|
||||||
dpdv: ssi.dpdvs,
|
dpdv: ssi.dpdvs,
|
||||||
|
|
@ -138,27 +138,27 @@ pub struct BSSRDFProbeSegment {
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
|
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
|
||||||
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
|
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
|
||||||
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample<'_>;
|
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(BSSRDFTrait)]
|
#[enum_dispatch(BSSRDFTrait)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum BSSRDF<'a> {
|
pub enum BSSRDF {
|
||||||
Tabulated(TabulatedBSSRDF<'a>),
|
Tabulated(TabulatedBSSRDF),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TabulatedBSSRDF<'a> {
|
pub struct TabulatedBSSRDF {
|
||||||
po: Point3f,
|
po: Point3f,
|
||||||
wo: Vector3f,
|
wo: Vector3f,
|
||||||
ns: Normal3f,
|
ns: Normal3f,
|
||||||
eta: Float,
|
eta: Float,
|
||||||
sigma_t: SampledSpectrum,
|
sigma_t: SampledSpectrum,
|
||||||
rho: SampledSpectrum,
|
rho: SampledSpectrum,
|
||||||
table: &'a BSSRDFTable,
|
table: Arc<BSSRDFTable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TabulatedBSSRDF<'a> {
|
impl TabulatedBSSRDF {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
po: Point3f,
|
po: Point3f,
|
||||||
wo: Vector3f,
|
wo: Vector3f,
|
||||||
|
|
@ -166,7 +166,7 @@ impl<'a> TabulatedBSSRDF<'a> {
|
||||||
eta: Float,
|
eta: Float,
|
||||||
sigma_a: &SampledSpectrum,
|
sigma_a: &SampledSpectrum,
|
||||||
sigma_s: &SampledSpectrum,
|
sigma_s: &SampledSpectrum,
|
||||||
table: &'a BSSRDFTable,
|
table: Arc<BSSRDFTable>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let sigma_t = *sigma_a + *sigma_s;
|
let sigma_t = *sigma_a + *sigma_s;
|
||||||
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
|
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
|
||||||
|
|
@ -299,7 +299,7 @@ impl<'a> TabulatedBSSRDF<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BSSRDFTrait for TabulatedBSSRDF<'a> {
|
impl BSSRDFTrait for TabulatedBSSRDF {
|
||||||
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment> {
|
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment> {
|
||||||
let f = if u1 < 0.25 {
|
let f = if u1 < 0.25 {
|
||||||
Frame::from_x(self.ns.into())
|
Frame::from_x(self.ns.into())
|
||||||
|
|
@ -321,7 +321,7 @@ impl<'a> BSSRDFTrait for TabulatedBSSRDF<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample<'_> {
|
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,32 +7,31 @@ use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
|
use crate::core::geometry::{
|
||||||
use crate::core::options::get_options;
|
|
||||||
use crate::core::pbrt::{Float, INV_2_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, clamp_t};
|
|
||||||
use crate::geometry::{
|
|
||||||
Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
|
Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
|
||||||
spherical_direction, spherical_theta,
|
spherical_direction, spherical_theta,
|
||||||
};
|
};
|
||||||
use crate::spectra::{
|
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
|
||||||
N_SPECTRUM_SAMPLES, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
use crate::core::options::get_options;
|
||||||
|
use crate::core::pbrt::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
|
||||||
|
use crate::core::scattering::{
|
||||||
|
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
|
||||||
|
refract,
|
||||||
|
};
|
||||||
|
use crate::spectra::{
|
||||||
|
N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum,
|
||||||
|
SampledWavelengths,
|
||||||
};
|
};
|
||||||
use crate::utils::color::RGB;
|
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use crate::utils::hash::hash_buffer;
|
use crate::utils::hash::hash_buffer;
|
||||||
use crate::utils::math::{
|
use crate::utils::math::{
|
||||||
fast_exp, i0, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, square,
|
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
|
||||||
trimmed_logistic,
|
square, trimmed_logistic,
|
||||||
};
|
};
|
||||||
use crate::utils::rng::Rng;
|
use crate::utils::rng::Rng;
|
||||||
use crate::utils::sampling::{
|
use crate::utils::sampling::{
|
||||||
PiecewiseLinear2D, cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere,
|
PiecewiseLinear2D, cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere,
|
||||||
sample_exponential, sample_trimmed_logistic, sample_uniform_hemisphere, uniform_hemisphere_pdf,
|
sample_exponential, sample_trimmed_logistic, sample_uniform_hemisphere, uniform_hemisphere_pdf,
|
||||||
};
|
};
|
||||||
use crate::utils::scattering::{
|
|
||||||
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
|
|
||||||
refract,
|
|
||||||
};
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -209,12 +208,6 @@ impl ThinDielectricBxDF {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct CoatedDiffuseBxDF;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct CoatedConductorBxDF;
|
|
||||||
|
|
||||||
static P_MAX: usize = 3;
|
static P_MAX: usize = 3;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HairBxDF {
|
pub struct HairBxDF {
|
||||||
|
|
@ -494,17 +487,19 @@ pub enum BxDF {
|
||||||
Conductor(ConductorBxDF),
|
Conductor(ConductorBxDF),
|
||||||
Measured(MeasuredBxDF),
|
Measured(MeasuredBxDF),
|
||||||
Hair(HairBxDF),
|
Hair(HairBxDF),
|
||||||
|
CoatedDiffuse(CoatedDiffuseBxDF),
|
||||||
|
CoatedConductor(CoatedConductorBxDF),
|
||||||
// DiffuseTransmission(DiffuseTransmissionBxDF),
|
// DiffuseTransmission(DiffuseTransmissionBxDF),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct BSDF<'a> {
|
pub struct BSDF {
|
||||||
bxdf: Option<&'a mut dyn BxDFTrait>,
|
bxdf: Option<BxDF>,
|
||||||
shading_frame: Frame,
|
shading_frame: Frame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BSDF<'a> {
|
impl BSDF {
|
||||||
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Option<&'a mut dyn BxDFTrait>) -> Self {
|
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Option<BxDF>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bxdf,
|
bxdf,
|
||||||
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
|
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
|
||||||
|
|
@ -1724,7 +1719,7 @@ where
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
z = clamp_t(zp, 0.0, self.thickness);
|
z = clamp(zp, 0.0, self.thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
if z == exit_z {
|
if z == exit_z {
|
||||||
|
|
@ -1861,158 +1856,10 @@ where
|
||||||
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
|
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
|
||||||
let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
|
let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
|
||||||
let mut rng = Rng::new_with_offset(hash0, hash1);
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
||||||
let mut r = || -> Float { rng.uniform::<Float>().min(ONE_MINUS_EPSILON) };
|
|
||||||
let f_args = FArgs {
|
|
||||||
mode,
|
|
||||||
sample_flags: BxDFReflTransFlags::TRANSMISSION,
|
|
||||||
};
|
|
||||||
|
|
||||||
let refl_args = FArgs {
|
|
||||||
mode,
|
|
||||||
sample_flags: BxDFReflTransFlags::REFLECTION,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let inters = (enter_interface, exit_interface, non_exit_interface);
|
||||||
for _ in 0..self.n_samples {
|
for _ in 0..self.n_samples {
|
||||||
// Sample random walk through layers to estimate BSDF value
|
f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
|
||||||
let mut uc = r();
|
|
||||||
let Some(wos) = enter_interface
|
|
||||||
.sample_f(wo, uc, Point2f::new(r(), r()), f_args)
|
|
||||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
uc = r();
|
|
||||||
let Some(wis) = exit_interface
|
|
||||||
.sample_f(wo, uc, Point2f::new(r(), r()), f_args)
|
|
||||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Declare state for random walk through BSDF layers
|
|
||||||
let mut beta = wos.f * abs_cos_theta(wos.wi) / wos.pdf;
|
|
||||||
let mut z = if entered_top { self.thickness } else { 0. };
|
|
||||||
let mut w = wos.wi;
|
|
||||||
let phase = HGPhaseFunction::new(self.g);
|
|
||||||
|
|
||||||
for depth in 0..self.max_depth {
|
|
||||||
if depth > 3 && beta.max_component_value() < 0.25 {
|
|
||||||
let q = (1. - beta.max_component_value()).max(0.);
|
|
||||||
if r() < q {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
beta /= 1. - q;
|
|
||||||
}
|
|
||||||
// Account for media between layers and possibly scatter
|
|
||||||
if !self.albedo.is_black() {
|
|
||||||
z = if z == self.thickness {
|
|
||||||
0.
|
|
||||||
} else {
|
|
||||||
self.thickness
|
|
||||||
};
|
|
||||||
beta *= self.tr(self.thickness, w);
|
|
||||||
} else {
|
|
||||||
let sigma_t = 1.;
|
|
||||||
let dz = sample_exponential(r(), sigma_t / w.z().abs());
|
|
||||||
let zp = if w.z() > 0. { z + dz } else { z - dz };
|
|
||||||
if 0. < zp && zp < self.thickness {
|
|
||||||
// Handle scattering event in layered BSDF medium
|
|
||||||
let mut wt = 1.;
|
|
||||||
if exit_interface.flags().is_specular() {
|
|
||||||
wt = power_heuristic(1, wis.pdf, 1, phase.pdf(-w, wis.wi));
|
|
||||||
}
|
|
||||||
f += beta
|
|
||||||
* self.albedo
|
|
||||||
* phase.p(-wi, -wis.wi)
|
|
||||||
* wt
|
|
||||||
* self.tr(zp - exit_z, wis.wi)
|
|
||||||
* wis.f
|
|
||||||
/ wis.pdf;
|
|
||||||
|
|
||||||
// Sample phase function and update layered path state
|
|
||||||
let u = Point2f::new(r(), r());
|
|
||||||
let Some(ps) = phase
|
|
||||||
.sample_p(-w, u)
|
|
||||||
.filter(|s| s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
beta *= self.albedo * ps.p / ps.pdf;
|
|
||||||
w = ps.wi;
|
|
||||||
z = zp;
|
|
||||||
|
|
||||||
if z < exit_z && w.z() > 0.
|
|
||||||
|| z > exit_z && w.z() < 0. && exit_interface.flags().is_specular()
|
|
||||||
{
|
|
||||||
let f_exit = exit_interface.f(-w, -wi, mode);
|
|
||||||
if !f_exit.is_black() {
|
|
||||||
let exit_pdf = exit_interface.pdf(-w, wi, f_args);
|
|
||||||
let wt = power_heuristic(1, ps.pdf, 1, exit_pdf);
|
|
||||||
f += beta * self.tr(zp - exit_z, ps.wi) * f_exit * wt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
z = clamp_t(zp, 0., self.thickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for scattering at appropriate interface
|
|
||||||
if z == exit_z {
|
|
||||||
let uc = r();
|
|
||||||
let u = Point2f::new(r(), r());
|
|
||||||
let Some(bs) = exit_interface
|
|
||||||
.sample_f(-w, uc, u, refl_args)
|
|
||||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
|
|
||||||
w = bs.wi;
|
|
||||||
} else {
|
|
||||||
if !non_exit_interface.flags().is_specular() {
|
|
||||||
let mut wt = 1.;
|
|
||||||
if exit_interface.flags().is_specular() {
|
|
||||||
wt = power_heuristic(
|
|
||||||
1,
|
|
||||||
wis.pdf,
|
|
||||||
1,
|
|
||||||
non_exit_interface.pdf(-w, -wis.wi, refl_args),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
f += beta
|
|
||||||
* non_exit_interface.f(-w, -wis.wi, mode)
|
|
||||||
* abs_cos_theta(wis.wi)
|
|
||||||
* wt
|
|
||||||
* self.tr(self.thickness, wis.wi)
|
|
||||||
* wis.f
|
|
||||||
/ wis.pdf;
|
|
||||||
}
|
|
||||||
// Sample new direction using BSDF at _nonExitInterface_
|
|
||||||
let uc = r();
|
|
||||||
let u = Point2f::new(r(), r());
|
|
||||||
let Some(bs) = non_exit_interface
|
|
||||||
.sample_f(-w, uc, u, refl_args)
|
|
||||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
|
|
||||||
w = bs.wi;
|
|
||||||
|
|
||||||
if !exit_interface.flags().is_specular() {
|
|
||||||
let f_exit = exit_interface.f(-w, wi, mode);
|
|
||||||
if !f_exit.is_black() {
|
|
||||||
let mut wt = 1.;
|
|
||||||
if non_exit_interface.flags().is_specular() {
|
|
||||||
let exit_pdf = exit_interface.pdf(-w, wi, f_args);
|
|
||||||
wt = power_heuristic(1, bs.pdf, 1, exit_pdf);
|
|
||||||
}
|
|
||||||
f += beta * self.tr(self.thickness, bs.wi) * f_exit * wt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f / self.n_samples as Float
|
f / self.n_samples as Float
|
||||||
|
|
@ -2020,16 +1867,251 @@ where
|
||||||
|
|
||||||
fn sample_f(
|
fn sample_f(
|
||||||
&self,
|
&self,
|
||||||
_wo: Vector3f,
|
mut wo: Vector3f,
|
||||||
_uc: Float,
|
uc: Float,
|
||||||
_u: Point2f,
|
u: Point2f,
|
||||||
_f_args: FArgs,
|
f_args: FArgs,
|
||||||
) -> Option<BSDFSample> {
|
) -> Option<BSDFSample> {
|
||||||
todo!()
|
let mut flip_wi = false;
|
||||||
|
if TWO_SIDED && wo.z() < 0. {
|
||||||
|
wo = -wo;
|
||||||
|
flip_wi = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
|
// Sample BSDF at entrance interface to get initial direction w
|
||||||
todo!()
|
let entered_top = TWO_SIDED || wo.z() > 0.;
|
||||||
|
let bs_raw = if entered_top {
|
||||||
|
self.top.sample_f(wo, uc, u, f_args)
|
||||||
|
} else {
|
||||||
|
self.bottom.sample_f(wo, uc, u, f_args)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bs = bs_raw.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)?;
|
||||||
|
|
||||||
|
if bs.is_reflective() {
|
||||||
|
if flip_wi {
|
||||||
|
bs.wi = -bs.wi;
|
||||||
|
}
|
||||||
|
bs.pdf_is_proportional = true;
|
||||||
|
return Some(bs);
|
||||||
|
}
|
||||||
|
let mut w = bs.wi;
|
||||||
|
let mut specular_path = bs.is_specular();
|
||||||
|
|
||||||
|
// Declare RNG for layered BSDF sampling
|
||||||
|
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
|
||||||
|
let hash1 = hash_buffer(&[uc, u.x(), u.y()], 0);
|
||||||
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
||||||
|
|
||||||
|
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
||||||
|
|
||||||
|
// Declare common variables for layered BSDF sampling
|
||||||
|
let mut f = bs.f * abs_cos_theta(bs.wi);
|
||||||
|
let mut pdf = bs.pdf;
|
||||||
|
let mut z = if entered_top { self.thickness } else { 0. };
|
||||||
|
let phase = HGPhaseFunction::new(self.g);
|
||||||
|
|
||||||
|
for depth in 0..self.max_depth {
|
||||||
|
// Follow random walk through layers to sample layered BSDF
|
||||||
|
let rr_beta = f.max_component_value() / pdf;
|
||||||
|
if depth > 3 && rr_beta < 0.25 {
|
||||||
|
let q = (1. - rr_beta).max(0.);
|
||||||
|
if r() < q {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
pdf *= 1. - q;
|
||||||
|
}
|
||||||
|
if w.z() < 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.albedo.is_black() {
|
||||||
|
let sigma_t = 1.;
|
||||||
|
let dz = sample_exponential(r(), sigma_t / abs_cos_theta(w));
|
||||||
|
let zp = if w.z() > 0. { z + dz } else { z - dz };
|
||||||
|
if zp > 0. && zp < self.thickness {
|
||||||
|
let Some(ps) = phase
|
||||||
|
.sample_p(-wo, Point2f::new(r(), r()))
|
||||||
|
.filter(|s| s.pdf == 0. && s.wi.z() == 0.)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
f *= self.albedo * ps.p;
|
||||||
|
pdf *= ps.pdf;
|
||||||
|
specular_path = false;
|
||||||
|
w = ps.wi;
|
||||||
|
z = zp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
z = clamp(zp, 0., self.thickness);
|
||||||
|
} else {
|
||||||
|
// Advance to the other layer interface
|
||||||
|
z = if z == self.thickness {
|
||||||
|
0.
|
||||||
|
} else {
|
||||||
|
self.thickness
|
||||||
|
};
|
||||||
|
f *= self.tr(self.thickness, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
let interface = if z == 0. {
|
||||||
|
TopOrBottom::Bottom(&self.bottom)
|
||||||
|
} else {
|
||||||
|
TopOrBottom::Top(&self.top)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sample interface BSDF to determine new path direction
|
||||||
|
let bs = interface
|
||||||
|
.sample_f(-w, r(), Point2f::new(r(), r()), f_args)
|
||||||
|
.filter(|s| s.f.is_black() && s.pdf == 0. && s.wi.z() == 0.)?;
|
||||||
|
f *= bs.f;
|
||||||
|
pdf *= bs.pdf;
|
||||||
|
specular_path &= bs.is_specular();
|
||||||
|
w = bs.wi;
|
||||||
|
|
||||||
|
// Return BSDFSample if path has left the layers
|
||||||
|
if bs.is_transmissive() {
|
||||||
|
let mut flags = if same_hemisphere(wo, w) {
|
||||||
|
BxDFFlags::REFLECTION
|
||||||
|
} else {
|
||||||
|
BxDFFlags::TRANSMISSION
|
||||||
|
};
|
||||||
|
flags |= if specular_path {
|
||||||
|
BxDFFlags::SPECULAR
|
||||||
|
} else {
|
||||||
|
BxDFFlags::GLOSSY
|
||||||
|
};
|
||||||
|
|
||||||
|
if flip_wi {
|
||||||
|
w = -w;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(BSDFSample::new(f, w, pdf, flags, 1., true));
|
||||||
|
}
|
||||||
|
|
||||||
|
f *= abs_cos_theta(bs.wi);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, mut wo: Vector3f, mut wi: Vector3f, f_args: FArgs) -> Float {
|
||||||
|
if TWO_SIDED && wo.z() < 0. {
|
||||||
|
wo = -wo;
|
||||||
|
wi = -wi;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash0 = hash_buffer(&[get_options().seed as Float, wi.x(), wi.y(), wi.z()], 0);
|
||||||
|
let hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0);
|
||||||
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
||||||
|
|
||||||
|
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
||||||
|
|
||||||
|
let entered_top = TWO_SIDED || wo.z() > 0.;
|
||||||
|
let refl_args = FArgs {
|
||||||
|
mode: f_args.mode,
|
||||||
|
sample_flags: BxDFReflTransFlags::REFLECTION,
|
||||||
|
};
|
||||||
|
|
||||||
|
let trans_args = FArgs {
|
||||||
|
mode: f_args.mode,
|
||||||
|
sample_flags: BxDFReflTransFlags::TRANSMISSION,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pdf_sum = 0.;
|
||||||
|
if same_hemisphere(wo, wi) {
|
||||||
|
pdf_sum += if entered_top {
|
||||||
|
self.n_samples as Float * self.top.pdf(wo, wi, refl_args)
|
||||||
|
} else {
|
||||||
|
self.n_samples as Float * self.bottom.pdf(wo, wi, refl_args)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..self.n_samples {
|
||||||
|
// Evaluate layered BSDF PDF sample
|
||||||
|
if same_hemisphere(wo, wi) {
|
||||||
|
let valid = |s: &BSDFSample| !s.f.is_black() && s.pdf > 0.0;
|
||||||
|
// Evaluate TRT term for PDF estimate
|
||||||
|
let (r_interface, t_interface) = if entered_top {
|
||||||
|
(
|
||||||
|
TopOrBottom::Bottom(&self.bottom),
|
||||||
|
TopOrBottom::Top(&self.top),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
TopOrBottom::Top(&self.top),
|
||||||
|
TopOrBottom::Bottom(&self.bottom),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (Some(wos), Some(wis)) = (
|
||||||
|
t_interface
|
||||||
|
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
|
||||||
|
.filter(valid),
|
||||||
|
t_interface
|
||||||
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
||||||
|
.filter(valid),
|
||||||
|
) {
|
||||||
|
if !t_interface.flags().is_non_specular() {
|
||||||
|
pdf_sum += r_interface.pdf(-wos.wi, -wis.wi, f_args);
|
||||||
|
} else if let Some(rs) = r_interface
|
||||||
|
.sample_f(-wos.wi, r(), Point2f::new(r(), r()), f_args)
|
||||||
|
.filter(valid)
|
||||||
|
{
|
||||||
|
if !r_interface.flags().is_non_specular() {
|
||||||
|
pdf_sum += t_interface.pdf(-rs.wi, wi, trans_args);
|
||||||
|
} else {
|
||||||
|
let r_pdf = r_interface.pdf(-wos.wi, -wis.wi, f_args);
|
||||||
|
let t_pdf = t_interface.pdf(-rs.wi, wi, f_args);
|
||||||
|
|
||||||
|
pdf_sum += power_heuristic(1, wis.pdf, 1, r_pdf) * r_pdf;
|
||||||
|
pdf_sum += power_heuristic(1, rs.pdf, 1, t_pdf) * t_pdf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Evaluate TT term for PDF estimate>
|
||||||
|
let valid = |s: &BSDFSample| {
|
||||||
|
!s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective()
|
||||||
|
};
|
||||||
|
let (to_interface, ti_interface) = if entered_top {
|
||||||
|
(
|
||||||
|
TopOrBottom::Top(&self.top),
|
||||||
|
TopOrBottom::Bottom(&self.bottom),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
TopOrBottom::Bottom(&self.bottom),
|
||||||
|
TopOrBottom::Top(&self.top),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(wos) = to_interface
|
||||||
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
||||||
|
.filter(valid)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(wis) = to_interface
|
||||||
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
||||||
|
.filter(valid)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if to_interface.flags().is_specular() {
|
||||||
|
pdf_sum += ti_interface.pdf(-wos.wi, wi, f_args);
|
||||||
|
} else if ti_interface.flags().is_specular() {
|
||||||
|
pdf_sum += to_interface.pdf(wo, -wis.wi, f_args);
|
||||||
|
} else {
|
||||||
|
pdf_sum += (to_interface.pdf(wo, -wis.wi, f_args)
|
||||||
|
+ ti_interface.pdf(-wos.wi, wi, f_args))
|
||||||
|
/ 2.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lerp(0.9, INV_4_PI, pdf_sum / self.n_samples as Float)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn regularize(&mut self) {
|
fn regularize(&mut self) {
|
||||||
|
|
@ -2041,3 +2123,6 @@ where
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type CoatedDiffuseBxDF = LayeredBxDF<DielectricBxDF, DiffuseBxDF, true>;
|
||||||
|
pub type CoatedConductorBxDF = LayeredBxDF<DielectricBxDF, ConductorBxDF, true>;
|
||||||
|
|
@ -1,26 +1,31 @@
|
||||||
|
use crate::camera::CameraTransform;
|
||||||
use crate::core::filter::{Filter, FilterTrait};
|
use crate::core::filter::{Filter, FilterTrait};
|
||||||
use crate::core::interaction::SurfaceInteraction;
|
use crate::core::geometry::{
|
||||||
use crate::core::pbrt::Float;
|
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Vector2f, Vector2fi,
|
|
||||||
Vector2i, Vector3f,
|
Vector2i, Vector3f,
|
||||||
};
|
};
|
||||||
|
use crate::core::interaction::SurfaceInteraction;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
use crate::image::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
|
use crate::image::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
|
||||||
use crate::spectra::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance};
|
||||||
|
use crate::spectra::data::generate_cie_d;
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
ConstantSpectrum, DenselySampledSpectrum, N_SPECTRUM_SAMPLES, SampledSpectrum,
|
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||||
SampledWavelengths, Spectrum, SpectrumTrait, cie_x, cie_y, cie_z,
|
PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum,
|
||||||
|
SpectrumProvider, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum,
|
||||||
};
|
};
|
||||||
use crate::utils::AtomicFloat;
|
use crate::utils::AtomicFloat;
|
||||||
use crate::utils::color::{MatrixMulColor, RGB, SRGB, Triplet, XYZ, white_balance};
|
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
use crate::utils::math::SquareMatrix;
|
use crate::utils::error::FileLoc;
|
||||||
use crate::utils::math::linear_least_squares;
|
use crate::utils::math::linear_least_squares;
|
||||||
|
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::sampling::VarianceEstimator;
|
use crate::utils::sampling::VarianceEstimator;
|
||||||
use crate::utils::transform::AnimatedTransform;
|
use crate::utils::transform::AnimatedTransform;
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::sync::{Arc, atomic::AtomicUsize, atomic::Ordering};
|
use std::path::Path;
|
||||||
|
use std::sync::{Arc, OnceLock, atomic::AtomicUsize, atomic::Ordering};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
@ -96,19 +101,26 @@ impl RGBFilm {
|
||||||
pixels: Arc::new(pixels_array),
|
pixels: Arc::new(pixels_array),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
pub fn create(
|
||||||
pub struct GBufferBFilm {
|
params: &ParameterDictionary,
|
||||||
pub base: FilmBase,
|
exposure_time: Float,
|
||||||
output_from_render: AnimatedTransform,
|
filter: Filter,
|
||||||
apply_inverse: bool,
|
_camera_transform: Option<CameraTransform>,
|
||||||
pixels: Arc<Array2D<GBufferPixel>>,
|
loc: &FileLoc,
|
||||||
colorspace: RGBColorSpace,
|
) -> Result<Self, String> {
|
||||||
max_component_value: Float,
|
let colorspace = params.color_space.as_ref().unwrap();
|
||||||
write_fp16: bool,
|
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
|
||||||
filter_integral: Float,
|
let write_fp16 = params.get_one_bool("savefp16", true);
|
||||||
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
||||||
|
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
|
||||||
|
Ok(RGBFilm::new(
|
||||||
|
film_base,
|
||||||
|
&colorspace,
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -128,19 +140,229 @@ struct GBufferPixel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SpectralFilm {
|
pub struct GBufferFilm {
|
||||||
pub base: FilmBase,
|
pub base: FilmBase,
|
||||||
output_from_render: AnimatedTransform,
|
output_from_render: AnimatedTransform,
|
||||||
pixels: Array2D<SpectralPixel>,
|
apply_inverse: bool,
|
||||||
|
pixels: Arc<Array2D<GBufferPixel>>,
|
||||||
|
colorspace: RGBColorSpace,
|
||||||
|
max_component_value: Float,
|
||||||
|
write_fp16: bool,
|
||||||
|
filter_integral: Float,
|
||||||
|
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl GBufferFilm {
|
||||||
struct SpectralPixel;
|
pub fn new(
|
||||||
|
base: &FilmBase,
|
||||||
|
output_from_render: &AnimatedTransform,
|
||||||
|
apply_inverse: bool,
|
||||||
|
colorspace: &RGBColorSpace,
|
||||||
|
max_component_value: Float,
|
||||||
|
write_fp16: bool,
|
||||||
|
) -> Self {
|
||||||
|
assert!(!base.pixel_bounds.is_empty());
|
||||||
|
let output_rgbf_from_sensor_rgb =
|
||||||
|
colorspace.rgb_from_xyz * base.sensor.as_ref().unwrap().xyz_from_sensor_rgb;
|
||||||
|
let filter_integral = base.filter.integral();
|
||||||
|
let pixels = Array2D::new(base.pixel_bounds);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base: base.clone(),
|
||||||
|
output_from_render: output_from_render.clone(),
|
||||||
|
apply_inverse,
|
||||||
|
pixels: pixels.into(),
|
||||||
|
colorspace: colorspace.clone(),
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
filter_integral,
|
||||||
|
output_rgbf_from_sensor_rgb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
exposure_time: Float,
|
||||||
|
filter: Filter,
|
||||||
|
camera_transform: Option<CameraTransform>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let colorspace = params.color_space.as_ref().unwrap();
|
||||||
|
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
|
||||||
|
let write_fp16 = params.get_one_bool("savefp16", true);
|
||||||
|
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
||||||
|
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
|
||||||
|
|
||||||
|
if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) {
|
||||||
|
return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let coords_system = params.get_one_string("coordinatesystem", "camera");
|
||||||
|
let mut apply_inverse = false;
|
||||||
|
let camera_transform = camera_transform
|
||||||
|
.ok_or_else(|| "GBufferFilm requires a camera_transform".to_string())?;
|
||||||
|
let output_from_render = if coords_system == "camera" {
|
||||||
|
apply_inverse = true;
|
||||||
|
camera_transform.render_from_camera
|
||||||
|
} else if coords_system == "world" {
|
||||||
|
AnimatedTransform::from_transform(&camera_transform.world_from_render)
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: unknown coordinate system for GBufferFilm. (Expecting camera
|
||||||
|
or world",
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(GBufferFilm::new(
|
||||||
|
&film_base,
|
||||||
|
&output_from_render,
|
||||||
|
apply_inverse,
|
||||||
|
colorspace,
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SpectralPixel {
|
||||||
|
pub rgb_sum: [AtomicFloat; 3],
|
||||||
|
pub rgb_weigh_sum: AtomicFloat,
|
||||||
|
pub rgb_splat: [AtomicFloat; 3],
|
||||||
|
pub bucket_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SpectralPixelView<'a> {
|
||||||
|
pub metadata: &'a SpectralPixel,
|
||||||
|
pub bucket_sums: &'a [f64],
|
||||||
|
pub weight_sums: &'a [f64],
|
||||||
|
pub bucket_splats: &'a [AtomicFloat],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SpectralFilm {
|
||||||
|
pub base: FilmBase,
|
||||||
|
pub colorspace: RGBColorSpace,
|
||||||
|
pub lambda_min: Float,
|
||||||
|
pub lambda_max: Float,
|
||||||
|
pub n_buckets: usize,
|
||||||
|
pub max_component_value: Float,
|
||||||
|
pub write_fp16: bool,
|
||||||
|
pub filter_integral: Float,
|
||||||
|
pub pixels: Array2D<SpectralPixel>,
|
||||||
|
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
|
pub bucket_sums: Vec<f64>,
|
||||||
|
pub weight_sums: Vec<f64>,
|
||||||
|
pub bucket_splats: Vec<AtomicFloat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpectralFilm {
|
||||||
|
pub fn new(
|
||||||
|
base: &FilmBase,
|
||||||
|
lambda_min: Float,
|
||||||
|
lambda_max: Float,
|
||||||
|
n_buckets: usize,
|
||||||
|
colorspace: &RGBColorSpace,
|
||||||
|
max_component_value: Float,
|
||||||
|
write_fp16: bool,
|
||||||
|
) -> Self {
|
||||||
|
assert!(!base.pixel_bounds.is_empty());
|
||||||
|
let sensor = &base.sensor;
|
||||||
|
let output_rgbf_from_sensor_rgb =
|
||||||
|
colorspace.rgb_from_xyz * sensor.as_ref().unwrap().xyz_from_sensor_rgb;
|
||||||
|
let n_pixels = base.pixel_bounds.area() as usize;
|
||||||
|
let total_bucket_count = n_pixels * n_buckets;
|
||||||
|
let bucket_sums = vec![0.0; total_bucket_count];
|
||||||
|
let weight_sums = vec![0.0; total_bucket_count];
|
||||||
|
let filter_integral = base.filter.integral();
|
||||||
|
let bucket_splats: Vec<AtomicFloat> = (0..total_bucket_count)
|
||||||
|
.map(|_| AtomicFloat::new(0.0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
|
||||||
|
for i in 0..n_pixels {
|
||||||
|
pixels.get_linear_mut(i).bucket_offset = i * n_buckets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base: base.clone(),
|
||||||
|
lambda_min,
|
||||||
|
lambda_max,
|
||||||
|
n_buckets,
|
||||||
|
pixels,
|
||||||
|
bucket_sums,
|
||||||
|
weight_sums,
|
||||||
|
bucket_splats,
|
||||||
|
colorspace: colorspace.clone(),
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
filter_integral,
|
||||||
|
output_rgbf_from_sensor_rgb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
exposure_time: Float,
|
||||||
|
filter: Filter,
|
||||||
|
_camera_transform: Option<CameraTransform>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let colorspace = params.color_space.as_ref().unwrap();
|
||||||
|
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY);
|
||||||
|
let write_fp16 = params.get_one_bool("savefp16", true);
|
||||||
|
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
||||||
|
let film_base = FilmBase::new(params, filter, Some(sensor), loc);
|
||||||
|
|
||||||
|
if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) {
|
||||||
|
return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_buckets = params.get_one_int("nbuckets", 16) as usize;
|
||||||
|
let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float);
|
||||||
|
let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float);
|
||||||
|
if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SpectralFilm::new(
|
||||||
|
&film_base,
|
||||||
|
lambda_min,
|
||||||
|
lambda_max,
|
||||||
|
n_buckets,
|
||||||
|
colorspace,
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView {
|
||||||
|
let metadata = &self.pixels[p];
|
||||||
|
let start = metadata.bucket_offset;
|
||||||
|
let end = start + self.n_buckets;
|
||||||
|
|
||||||
|
SpectralPixelView {
|
||||||
|
metadata,
|
||||||
|
bucket_sums: &self.bucket_sums[start..end],
|
||||||
|
weight_sums: &self.weight_sums[start..end],
|
||||||
|
bucket_splats: &self.bucket_splats[start..end],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const N_SWATCH_REFLECTANCES: usize = 24;
|
const N_SWATCH_REFLECTANCES: usize = 24;
|
||||||
|
static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> = Lazy::new(|| {
|
||||||
static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> =
|
std::array::from_fn(|i| {
|
||||||
Lazy::new(|| std::array::from_fn(|_| Spectrum::Constant(ConstantSpectrum::new(0.8))));
|
let raw_data = crate::core::cie::SWATCHES_RAW[i];
|
||||||
|
let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false);
|
||||||
|
Spectrum::PiecewiseLinear(pls)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PixelSensor {
|
pub struct PixelSensor {
|
||||||
|
|
@ -153,22 +375,30 @@ pub struct PixelSensor {
|
||||||
|
|
||||||
impl PixelSensor {
|
impl PixelSensor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
r: &Spectrum,
|
r: Arc<Spectrum>,
|
||||||
g: &Spectrum,
|
g: Arc<Spectrum>,
|
||||||
b: &Spectrum,
|
b: Arc<Spectrum>,
|
||||||
output_colorspace: RGBColorSpace,
|
output_colorspace: Arc<RGBColorSpace>,
|
||||||
sensor_illum: &Spectrum,
|
sensor_illum: Option<Arc<Spectrum>>,
|
||||||
imaging_ratio: Float,
|
imaging_ratio: Float,
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
let r_bar = DenselySampledSpectrum::from_spectrum(r, LAMBDA_MIN, LAMBDA_MAX);
|
// As seen in usages of this constructos, sensor_illum can be null
|
||||||
let g_bar = DenselySampledSpectrum::from_spectrum(g, LAMBDA_MIN, LAMBDA_MAX);
|
// Going with the colorspace's own illuminant, but this might not be the right choice
|
||||||
let b_bar = DenselySampledSpectrum::from_spectrum(b, LAMBDA_MIN, LAMBDA_MAX);
|
// TODO: Test this
|
||||||
|
let illum: &Spectrum = match &sensor_illum {
|
||||||
|
Some(arc_illum) => &**arc_illum,
|
||||||
|
None => &output_colorspace.illuminant,
|
||||||
|
};
|
||||||
|
|
||||||
|
let r_bar = DenselySampledSpectrum::from_spectrum(&r);
|
||||||
|
let g_bar = DenselySampledSpectrum::from_spectrum(&g);
|
||||||
|
let b_bar = DenselySampledSpectrum::from_spectrum(&b);
|
||||||
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
||||||
|
|
||||||
for i in 0..N_SWATCH_REFLECTANCES {
|
for i in 0..N_SWATCH_REFLECTANCES {
|
||||||
let rgb = Self::project_reflectance::<RGB>(
|
let rgb = Self::project_reflectance::<RGB>(
|
||||||
&SWATCH_REFLECTANCES[i],
|
&SWATCH_REFLECTANCES[i],
|
||||||
sensor_illum,
|
illum,
|
||||||
&Spectrum::DenselySampled(r_bar.clone()),
|
&Spectrum::DenselySampled(r_bar.clone()),
|
||||||
&Spectrum::DenselySampled(g_bar.clone()),
|
&Spectrum::DenselySampled(g_bar.clone()),
|
||||||
&Spectrum::DenselySampled(b_bar.clone()),
|
&Spectrum::DenselySampled(b_bar.clone()),
|
||||||
|
|
@ -179,8 +409,8 @@ impl PixelSensor {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
||||||
let sensor_white_g = sensor_illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
|
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
|
||||||
let sensor_white_y = sensor_illum.inner_product(cie_y());
|
let sensor_white_y = illum.inner_product(cie_y());
|
||||||
for i in 0..N_SWATCH_REFLECTANCES {
|
for i in 0..N_SWATCH_REFLECTANCES {
|
||||||
let s = SWATCH_REFLECTANCES[i].clone();
|
let s = SWATCH_REFLECTANCES[i].clone();
|
||||||
let xyz = Self::project_reflectance::<XYZ>(
|
let xyz = Self::project_reflectance::<XYZ>(
|
||||||
|
|
@ -207,13 +437,13 @@ impl PixelSensor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_white_balance(
|
pub fn new_with_white_balance(
|
||||||
output_colorspace: RGBColorSpace,
|
output_colorspace: Arc<RGBColorSpace>,
|
||||||
sensor_illum: Option<Spectrum>,
|
sensor_illum: Option<Arc<Spectrum>>,
|
||||||
imaging_ratio: Float,
|
imaging_ratio: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x(), LAMBDA_MIN, LAMBDA_MAX);
|
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x());
|
||||||
let g_bar = DenselySampledSpectrum::from_spectrum(cie_y(), LAMBDA_MIN, LAMBDA_MAX);
|
let g_bar = DenselySampledSpectrum::from_spectrum(cie_y());
|
||||||
let b_bar = DenselySampledSpectrum::from_spectrum(cie_z(), LAMBDA_MIN, LAMBDA_MAX);
|
let b_bar = DenselySampledSpectrum::from_spectrum(cie_z());
|
||||||
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
|
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
|
||||||
|
|
||||||
if let Some(illum) = sensor_illum {
|
if let Some(illum) = sensor_illum {
|
||||||
|
|
@ -233,6 +463,66 @@ impl PixelSensor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
output_colorspace: Arc<RGBColorSpace>,
|
||||||
|
exposure_time: Float,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let iso = params.get_one_float("iso", 100.);
|
||||||
|
let mut white_balance_temp = params.get_one_float("whitebalance", 0.);
|
||||||
|
let sensor_name = params.get_one_string("sensor", "cie1931");
|
||||||
|
if sensor_name != "cie1931" && white_balance_temp == 0. {
|
||||||
|
white_balance_temp = 6500.;
|
||||||
|
}
|
||||||
|
let imaging_ratio = exposure_time * iso / 100.;
|
||||||
|
|
||||||
|
let d_illum = if white_balance_temp == 0. {
|
||||||
|
generate_cie_d(6500.)
|
||||||
|
} else {
|
||||||
|
generate_cie_d(white_balance_temp)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. {
|
||||||
|
Some(Arc::new(Spectrum::DenselySampled(d_illum)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if sensor_name == "cie1931" {
|
||||||
|
return Ok(PixelSensor::new_with_white_balance(
|
||||||
|
output_colorspace,
|
||||||
|
sensor_illum,
|
||||||
|
imaging_ratio,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let r_opt = get_named_spectrum(&format!("{}_r", sensor_name));
|
||||||
|
let g_opt = get_named_spectrum(&format!("{}_g", sensor_name));
|
||||||
|
let b_opt = get_named_spectrum(&format!("{}_b", sensor_name));
|
||||||
|
if r_opt.is_none() || g_opt.is_none() || b_opt.is_none() {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: unknown sensor type '{}' (missing RGB spectral data)",
|
||||||
|
loc, sensor_name
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = Arc::new(r_opt.unwrap());
|
||||||
|
let g = Arc::new(g_opt.unwrap());
|
||||||
|
let b = Arc::new(b_opt.unwrap());
|
||||||
|
|
||||||
|
return PixelSensor::new(
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
output_colorspace.clone(),
|
||||||
|
sensor_illum,
|
||||||
|
imaging_ratio,
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn project_reflectance<T: Triplet>(
|
pub fn project_reflectance<T: Triplet>(
|
||||||
refl: &Spectrum,
|
refl: &Spectrum,
|
||||||
illum: &Spectrum,
|
illum: &Spectrum,
|
||||||
|
|
@ -328,29 +618,71 @@ pub struct FilmBase {
|
||||||
|
|
||||||
impl FilmBase {
|
impl FilmBase {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
full_resolution: Point2i,
|
params: &ParameterDictionary,
|
||||||
pixel_bounds: Bounds2i,
|
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
diagonal: Float,
|
|
||||||
sensor: Option<PixelSensor>,
|
sensor: Option<PixelSensor>,
|
||||||
filename: String,
|
loc: &FileLoc,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let x_res = params.get_one_int("xresolution", 1280);
|
||||||
|
let y_res = params.get_one_int("yresolution", 720);
|
||||||
|
|
||||||
|
if x_res <= 0 || y_res <= 0 {
|
||||||
|
eprintln!(
|
||||||
|
"{}: Film resolution must be > 0. Defaulting to 1280x720.",
|
||||||
|
loc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let full_resolution = Point2i::new(x_res.max(1), y_res.max(1));
|
||||||
|
|
||||||
|
let crop_data = params.get_float_array("cropwindow");
|
||||||
|
let crop = if crop_data.len() == 4 {
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::new(crop_data[0], crop_data[2]),
|
||||||
|
Point2f::new(crop_data[1], crop_data[3]),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(Point2f::zero(), Point2f::new(1.0, 1.0))
|
||||||
|
};
|
||||||
|
|
||||||
|
let p_min = Point2i::new(
|
||||||
|
(full_resolution.x() as Float * crop.p_min.x()).ceil() as i32,
|
||||||
|
(full_resolution.y() as Float * crop.p_min.y()).ceil() as i32,
|
||||||
|
);
|
||||||
|
let p_max = Point2i::new(
|
||||||
|
(full_resolution.x() as Float * crop.p_max.x()).ceil() as i32,
|
||||||
|
(full_resolution.y() as Float * crop.p_max.y()).ceil() as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pixel_bounds = Bounds2i::from_points(p_min, p_max);
|
||||||
|
|
||||||
|
if pixel_bounds.is_empty() {
|
||||||
|
eprintln!("{}: Film crop window results in empty pixel bounds.", loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rad = filter.radius();
|
||||||
|
let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32);
|
||||||
|
pixel_bounds = pixel_bounds.expand(expansion);
|
||||||
|
|
||||||
|
let diagonal_mm = params.get_one_float("diagonal", 35.0);
|
||||||
|
let filename = params.get_one_string("filename", "pbrt.exr");
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
full_resolution,
|
full_resolution,
|
||||||
pixel_bounds,
|
pixel_bounds,
|
||||||
filter,
|
filter,
|
||||||
diagonal: 0.001 * diagonal,
|
diagonal: diagonal_mm * 0.001,
|
||||||
sensor,
|
sensor,
|
||||||
filename,
|
filename,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[enum_dispatch]
|
||||||
pub trait FilmTrait: Sync {
|
pub trait FilmTrait: Sync {
|
||||||
fn base(&self) -> &FilmBase;
|
fn base(&self) -> &FilmBase;
|
||||||
fn base_mut(&mut self) -> &mut FilmBase;
|
fn base_mut(&mut self) -> &mut FilmBase;
|
||||||
fn add_sample(
|
fn add_sample(
|
||||||
&mut self,
|
&self,
|
||||||
_p_filme: Point2i,
|
_p_filme: Point2i,
|
||||||
_l: SampledSpectrum,
|
_l: SampledSpectrum,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
|
|
@ -487,13 +819,46 @@ pub trait FilmTrait: Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[enum_dispatch(FilmTrait)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Film {
|
pub enum Film {
|
||||||
RGB(RGBFilm),
|
RGB(RGBFilm),
|
||||||
GBuffer(GBufferBFilm),
|
GBuffer(GBufferFilm),
|
||||||
Spectral(SpectralFilm),
|
Spectral(SpectralFilm),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Film {
|
||||||
|
pub fn create(
|
||||||
|
name: &str,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
exposure_time: Float,
|
||||||
|
filter: Filter,
|
||||||
|
camera_transform: Option<CameraTransform>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"rgb" => {
|
||||||
|
let film = RGBFilm::create(params, exposure_time, filter, camera_transform, loc)?;
|
||||||
|
|
||||||
|
Ok(Film::RGB(film))
|
||||||
|
}
|
||||||
|
"gbuffer" => {
|
||||||
|
let film =
|
||||||
|
GBufferFilm::create(params, exposure_time, filter, camera_transform, loc)?;
|
||||||
|
|
||||||
|
Ok(Film::GBuffer(film))
|
||||||
|
}
|
||||||
|
"spectral" => {
|
||||||
|
let film =
|
||||||
|
SpectralFilm::create(params, exposure_time, filter, camera_transform, loc)?;
|
||||||
|
|
||||||
|
Ok(Film::Spectral(film))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FilmTrait for RGBFilm {
|
impl FilmTrait for RGBFilm {
|
||||||
fn base(&self) -> &FilmBase {
|
fn base(&self) -> &FilmBase {
|
||||||
&self.base
|
&self.base
|
||||||
|
|
@ -504,7 +869,7 @@ impl FilmTrait for RGBFilm {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_sample(
|
fn add_sample(
|
||||||
&mut self,
|
&self,
|
||||||
p_film: Point2i,
|
p_film: Point2i,
|
||||||
l: SampledSpectrum,
|
l: SampledSpectrum,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
|
|
@ -606,7 +971,7 @@ impl FilmTrait for RGBFilm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilmTrait for GBufferBFilm {
|
impl FilmTrait for GBufferFilm {
|
||||||
fn base(&self) -> &FilmBase {
|
fn base(&self) -> &FilmBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
@ -700,29 +1065,3 @@ impl FilmTrait for SpectralFilm {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilmTrait for Film {
|
|
||||||
fn base(&self) -> &FilmBase {
|
|
||||||
match self {
|
|
||||||
Film::RGB(film) => film.base(),
|
|
||||||
Film::GBuffer(film) => film.base(),
|
|
||||||
Film::Spectral(film) => film.base(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn base_mut(&mut self) -> &mut FilmBase {
|
|
||||||
match self {
|
|
||||||
Film::RGB(film) => film.base_mut(),
|
|
||||||
Film::GBuffer(film) => film.base_mut(),
|
|
||||||
Film::Spectral(film) => film.base_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uses_visible_surface(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Film::RGB(film) => film.uses_visible_surface(),
|
|
||||||
Film::GBuffer(film) => film.uses_visible_surface(),
|
|
||||||
Film::Spectral(film) => film.uses_visible_surface(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||||
use crate::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
|
|
@ -76,6 +78,34 @@ pub enum Filter {
|
||||||
Triangle(TriangleFilter),
|
Triangle(TriangleFilter),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Filter {
|
||||||
|
pub fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"box" => {
|
||||||
|
let filter = BoxFilter::create(¶ms, loc);
|
||||||
|
Ok(Filter::Box(filter))
|
||||||
|
}
|
||||||
|
"gaussian" => {
|
||||||
|
let filter = GaussianFilter::create(¶ms, loc);
|
||||||
|
Ok(Filter::Gaussian(filter))
|
||||||
|
}
|
||||||
|
"mitchell" => {
|
||||||
|
let filter = MitchellFilter::create(¶ms, loc);
|
||||||
|
Ok(Filter::Mitchell(filter))
|
||||||
|
}
|
||||||
|
"sinc" => {
|
||||||
|
let filter = LanczosSincFilter::create(¶ms, loc);
|
||||||
|
Ok(Filter::LanczosSinc(filter))
|
||||||
|
}
|
||||||
|
"triangle" => {
|
||||||
|
let filter = TriangleFilter::create(¶ms, loc);
|
||||||
|
Ok(Filter::Triangle(filter))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BoxFilter {
|
pub struct BoxFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
|
|
@ -85,6 +115,12 @@ impl BoxFilter {
|
||||||
pub fn new(radius: Vector2f) -> Self {
|
pub fn new(radius: Vector2f) -> Self {
|
||||||
Self { radius }
|
Self { radius }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||||
|
let xw = params.get_one_float("xradius", 0.5);
|
||||||
|
let yw = params.get_one_float("yradius", 0.5);
|
||||||
|
Self::new(Vector2f::new(xw, yw))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for BoxFilter {
|
impl FilterTrait for BoxFilter {
|
||||||
|
|
@ -141,6 +177,13 @@ impl GaussianFilter {
|
||||||
sampler,
|
sampler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||||
|
let xw = params.get_one_float("xradius", 1.5);
|
||||||
|
let yw = params.get_one_float("yradius", 1.5);
|
||||||
|
let sigma = params.get_one_float("sigma", 0.5);
|
||||||
|
Self::new(Vector2f::new(xw, yw), sigma)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for GaussianFilter {
|
impl FilterTrait for GaussianFilter {
|
||||||
|
|
@ -189,6 +232,14 @@ impl MitchellFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||||
|
let xw = params.get_one_float("xradius", 2.);
|
||||||
|
let yw = params.get_one_float("yradius", 2.);
|
||||||
|
let b = params.get_one_float("B", 1. / 3.);
|
||||||
|
let c = params.get_one_float("C", 1. / 3.);
|
||||||
|
Self::new(Vector2f::new(xw, yw), b, c)
|
||||||
|
}
|
||||||
|
|
||||||
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
|
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
|
||||||
let x = x.abs();
|
let x = x.abs();
|
||||||
if x <= 1.0 {
|
if x <= 1.0 {
|
||||||
|
|
@ -250,6 +301,13 @@ impl LanczosSincFilter {
|
||||||
sampler,
|
sampler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||||
|
let xw = params.get_one_float("xradius", 4.);
|
||||||
|
let yw = params.get_one_float("yradius", 4.);
|
||||||
|
let tau = params.get_one_float("tau", 3.);
|
||||||
|
Self::new(Vector2f::new(xw, yw), tau)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for LanczosSincFilter {
|
impl FilterTrait for LanczosSincFilter {
|
||||||
|
|
@ -299,6 +357,12 @@ impl TriangleFilter {
|
||||||
pub fn new(radius: Vector2f) -> Self {
|
pub fn new(radius: Vector2f) -> Self {
|
||||||
Self { radius }
|
Self { radius }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||||
|
let xw = params.get_one_float("xradius", 2.);
|
||||||
|
let yw = params.get_one_float("yradius", 2.);
|
||||||
|
Self::new(Vector2f::new(xw, yw))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for TriangleFilter {
|
impl FilterTrait for TriangleFilter {
|
||||||
|
|
@ -87,12 +87,12 @@ where
|
||||||
d.0.iter().fold(T::one(), |acc, &val| acc * val)
|
d.0.iter().fold(T::one(), |acc, &val| acc * val)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand(&self, delta: T) -> Self {
|
pub fn expand(&self, delta: impl Into<Vector<T, N>>) -> Self {
|
||||||
let mut p_min = self.p_min;
|
let v = delta.into();
|
||||||
let mut p_max = self.p_max;
|
Self {
|
||||||
p_min = p_min - Vector::fill(delta);
|
p_min: self.p_min - v,
|
||||||
p_max = p_max + Vector::fill(delta);
|
p_max: self.p_max + v,
|
||||||
Self { p_min, p_max }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
|
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
|
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
|
||||||
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
|
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DirectionCone {
|
pub struct DirectionCone {
|
||||||
|
|
@ -110,7 +110,7 @@ impl DirectionCone {
|
||||||
return DirectionCone::entire_sphere();
|
return DirectionCone::entire_sphere();
|
||||||
}
|
}
|
||||||
|
|
||||||
let w = Transform::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
|
let w = TransformGeneric::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
|
||||||
DirectionCone::new(w, theta_o.cos())
|
DirectionCone::new(w, theta_o.cos())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ pub use self::primitives::{
|
||||||
pub use self::ray::{Ray, RayDifferential};
|
pub use self::ray::{Ray, RayDifferential};
|
||||||
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, PI, clamp_t};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::{clamp, square};
|
||||||
use num_traits::Float as NumFloat;
|
use num_traits::Float as NumFloat;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -61,7 +61,7 @@ pub fn cos_phi(w: Vector3f) -> Float {
|
||||||
if sin_theta == 0. {
|
if sin_theta == 0. {
|
||||||
1.
|
1.
|
||||||
} else {
|
} else {
|
||||||
clamp_t(w.x() / sin_theta, -1., 1.)
|
clamp(w.x() / sin_theta, -1., 1.)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub fn sin_phi(w: Vector3f) -> Float {
|
||||||
if sin_theta == 0. {
|
if sin_theta == 0. {
|
||||||
0.
|
0.
|
||||||
} else {
|
} else {
|
||||||
clamp_t(w.y() / sin_theta, -1., 1.)
|
clamp(w.y() / sin_theta, -1., 1.)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spherical_theta(v: Vector3f) -> Float {
|
pub fn spherical_theta(v: Vector3f) -> Float {
|
||||||
clamp_t(v.z(), -1.0, 1.0).acos()
|
clamp(v.z(), -1.0, 1.0).acos()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spherical_phi(v: Vector3f) -> Float {
|
pub fn spherical_phi(v: Vector3f) -> Float {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::traits::{Sqrt, Tuple, VectorLike};
|
use super::traits::{Sqrt, Tuple, VectorLike};
|
||||||
use super::{Float, NumFloat, PI, clamp_t};
|
use super::{Float, NumFloat, PI};
|
||||||
use crate::utils::interval::Interval;
|
use crate::utils::interval::Interval;
|
||||||
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
|
use crate::utils::math::{clamp, difference_of_products, quadratic, safe_asin};
|
||||||
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
|
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
|
|
@ -10,12 +10,15 @@ use std::ops::{
|
||||||
};
|
};
|
||||||
|
|
||||||
// N-dimensional displacement
|
// N-dimensional displacement
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Vector<T, const N: usize>(pub [T; N]);
|
pub struct Vector<T, const N: usize>(pub [T; N]);
|
||||||
// N-dimensional location
|
// N-dimensional location
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Point<T, const N: usize>(pub [T; N]);
|
pub struct Point<T, const N: usize>(pub [T; N]);
|
||||||
// N-dimensional surface normal
|
// N-dimensional surface normal
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Normal<T, const N: usize>(pub [T; N]);
|
pub struct Normal<T, const N: usize>(pub [T; N]);
|
||||||
|
|
||||||
|
|
@ -55,6 +58,16 @@ macro_rules! impl_tuple_core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> From<T> for $Struct<T, N>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn from(scalar: T) -> Self {
|
||||||
|
Self([scalar; N])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N: usize> $Struct<f32, N> {
|
impl<const N: usize> $Struct<f32, N> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn floor(&self) -> $Struct<i32, N> {
|
pub fn floor(&self) -> $Struct<i32, N> {
|
||||||
|
|
@ -272,6 +285,23 @@ macro_rules! impl_float_vector_ops {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_tuple_conversions {
|
||||||
|
($Struct:ident) => {
|
||||||
|
impl<T: NumFloat> From<(T, T, T)> for $Struct<T, 3> {
|
||||||
|
fn from(tuple: (T, T, T)) -> Self {
|
||||||
|
Self([tuple.0, tuple.1, tuple.2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow converting (x, y) -> Vector<T, 2>
|
||||||
|
impl<T: NumFloat> From<(T, T)> for $Struct<T, 2> {
|
||||||
|
fn from(tuple: (T, T)) -> Self {
|
||||||
|
Self([tuple.0, tuple.1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_abs {
|
macro_rules! impl_abs {
|
||||||
($Struct:ident) => {
|
($Struct:ident) => {
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
impl<T, const N: usize> $Struct<T, N>
|
||||||
|
|
@ -351,6 +381,11 @@ impl_accessors!(Vector);
|
||||||
impl_accessors!(Point);
|
impl_accessors!(Point);
|
||||||
impl_accessors!(Normal);
|
impl_accessors!(Normal);
|
||||||
|
|
||||||
|
// Convert from tuple of Floats, for parsing issues
|
||||||
|
impl_tuple_conversions!(Vector);
|
||||||
|
impl_tuple_conversions!(Point);
|
||||||
|
impl_tuple_conversions!(Normal);
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
||||||
fn from(v: Vector<T, N>) -> Self {
|
fn from(v: Vector<T, N>) -> Self {
|
||||||
Self(v.0)
|
Self(v.0)
|
||||||
|
|
@ -448,9 +483,11 @@ pub type Vector3<T> = Vector<T, 3>;
|
||||||
pub type Vector3f = Vector3<Float>;
|
pub type Vector3f = Vector3<Float>;
|
||||||
pub type Vector3i = Vector3<i32>;
|
pub type Vector3i = Vector3<i32>;
|
||||||
pub type Vector3fi = Vector3<Interval>;
|
pub type Vector3fi = Vector3<Interval>;
|
||||||
|
pub type Normal2<T> = Normal<T, 2>;
|
||||||
pub type Normal3<T> = Normal<T, 3>;
|
pub type Normal3<T> = Normal<T, 3>;
|
||||||
pub type Normal3f = Normal3<Float>;
|
pub type Normal3f = Normal3<Float>;
|
||||||
pub type Normal3i = Normal3<i32>;
|
pub type Normal3i = Normal3<i32>;
|
||||||
|
pub type Vector4<T> = Vector<T, 4>;
|
||||||
|
|
||||||
impl<T: Copy> Vector2<T> {
|
impl<T: Copy> Vector2<T> {
|
||||||
pub fn new(x: T, y: T) -> Self {
|
pub fn new(x: T, y: T) -> Self {
|
||||||
|
|
@ -462,6 +499,12 @@ impl<T: Copy> Point2<T> {
|
||||||
Self([x, y])
|
Self([x, y])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: Copy> Normal2<T> {
|
||||||
|
pub fn new(x: T, y: T) -> Self {
|
||||||
|
Self([x, y])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Copy> Vector3<T> {
|
impl<T: Copy> Vector3<T> {
|
||||||
pub fn new(x: T, y: T, z: T) -> Self {
|
pub fn new(x: T, y: T, z: T) -> Self {
|
||||||
Self([x, y, z])
|
Self([x, y, z])
|
||||||
|
|
@ -477,6 +520,11 @@ impl<T: Copy> Normal3<T> {
|
||||||
Self([x, y, z])
|
Self([x, y, z])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: Copy> Vector4<T> {
|
||||||
|
pub fn new(x: T, y: T, z: T, w: T) -> Self {
|
||||||
|
Self([x, y, z, w])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Vector operations
|
// Vector operations
|
||||||
impl<T> Vector3<T>
|
impl<T> Vector3<T>
|
||||||
|
|
@ -786,7 +834,7 @@ impl OctahedralVector {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn encode(f: Float) -> u16 {
|
pub fn encode(f: Float) -> u16 {
|
||||||
(clamp_t((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
|
(clamp((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
use crate::camera::{Camera, CameraTrait};
|
use crate::camera::{Camera, CameraTrait};
|
||||||
use crate::core::bssrdf::BSSRDF;
|
use crate::core::bssrdf::BSSRDF;
|
||||||
use crate::core::bxdf::{BSDF, BxDFFlags, DiffuseBxDF};
|
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
use crate::core::material::{
|
use crate::core::material::{
|
||||||
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
||||||
};
|
};
|
||||||
use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
|
use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
|
||||||
use crate::core::options::get_options;
|
use crate::core::options::get_options;
|
||||||
use crate::core::pbrt::{Float, clamp_t};
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::{Sampler, SamplerTrait};
|
use crate::core::sampler::{Sampler, SamplerTrait};
|
||||||
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
|
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
|
||||||
use crate::geometry::{
|
|
||||||
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
|
||||||
};
|
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::lights::{Light, LightTrait};
|
use crate::lights::{Light, LightTrait};
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::math::{difference_of_products, square};
|
use crate::utils::math::{clamp, difference_of_products, square};
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
@ -172,7 +172,7 @@ impl InteractionTrait for SimpleInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct ShadingGeometry {
|
pub struct Shadinggeom {
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
pub dpdu: Vector3f,
|
pub dpdu: Vector3f,
|
||||||
pub dpdv: Vector3f,
|
pub dpdv: Vector3f,
|
||||||
|
|
@ -188,7 +188,7 @@ pub struct SurfaceInteraction {
|
||||||
pub dpdv: Vector3f,
|
pub dpdv: Vector3f,
|
||||||
pub dndu: Normal3f,
|
pub dndu: Normal3f,
|
||||||
pub dndv: Normal3f,
|
pub dndv: Normal3f,
|
||||||
pub shading: ShadingGeometry,
|
pub shading: Shadinggeom,
|
||||||
pub face_index: usize,
|
pub face_index: usize,
|
||||||
pub area_light: Option<Arc<Light>>,
|
pub area_light: Option<Arc<Light>>,
|
||||||
pub material: Option<Arc<Material>>,
|
pub material: Option<Arc<Material>>,
|
||||||
|
|
@ -273,22 +273,22 @@ impl SurfaceInteraction {
|
||||||
self.dvdy = difference_of_products(ata00, atb1y, ata01, atb0y) * inv_det;
|
self.dvdy = difference_of_products(ata00, atb1y, ata01, atb0y) * inv_det;
|
||||||
// Clamp derivatives
|
// Clamp derivatives
|
||||||
self.dudx = if self.dudx.is_finite() {
|
self.dudx = if self.dudx.is_finite() {
|
||||||
clamp_t(self.dudx, -1e8, 1e8)
|
clamp(self.dudx, -1e8, 1e8)
|
||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
};
|
};
|
||||||
self.dvdx = if self.dvdx.is_finite() {
|
self.dvdx = if self.dvdx.is_finite() {
|
||||||
clamp_t(self.dvdx, -1e8, 1e8)
|
clamp(self.dvdx, -1e8, 1e8)
|
||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
};
|
};
|
||||||
self.dudy = if self.dudy.is_finite() {
|
self.dudy = if self.dudy.is_finite() {
|
||||||
clamp_t(self.dudy, -1e8, 1e8)
|
clamp(self.dudy, -1e8, 1e8)
|
||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
};
|
};
|
||||||
self.dvdy = if self.dvdy.is_finite() {
|
self.dvdy = if self.dvdy.is_finite() {
|
||||||
clamp_t(self.dvdy, -1e8, 1e8)
|
clamp(self.dvdy, -1e8, 1e8)
|
||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
};
|
};
|
||||||
|
|
@ -304,14 +304,13 @@ impl SurfaceInteraction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bsdf<'a>(
|
pub fn get_bsdf(
|
||||||
&mut self,
|
&mut self,
|
||||||
r: &Ray,
|
r: &Ray,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
camera: &Camera,
|
camera: &Camera,
|
||||||
scratch: &'a Bump,
|
|
||||||
sampler: &mut Sampler,
|
sampler: &mut Sampler,
|
||||||
) -> Option<BSDF<'a>> {
|
) -> Option<BSDF> {
|
||||||
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
|
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
|
||||||
|
|
||||||
let material = {
|
let material = {
|
||||||
|
|
@ -332,13 +331,13 @@ impl SurfaceInteraction {
|
||||||
let normal_map = material.get_normal_map();
|
let normal_map = material.get_normal_map();
|
||||||
if displacement.is_some() || normal_map.is_some() {
|
if displacement.is_some() || normal_map.is_some() {
|
||||||
// This calls the function defined above
|
// This calls the function defined above
|
||||||
self.compute_bump_geometry(&tex_eval, displacement, normal_map);
|
self.compute_bump_geom(&tex_eval, displacement, normal_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bsdf = material.get_bxdf(&tex_eval, &ctx, lambda, scratch);
|
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda);
|
||||||
if get_options().force_diffuse {
|
if get_options().force_diffuse {
|
||||||
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
|
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
|
||||||
let diff_bxdf = scratch.alloc(DiffuseBxDF::new(r));
|
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
|
||||||
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf));
|
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf));
|
||||||
}
|
}
|
||||||
Some(bsdf)
|
Some(bsdf)
|
||||||
|
|
@ -350,7 +349,7 @@ impl SurfaceInteraction {
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
_camera: &Camera,
|
_camera: &Camera,
|
||||||
_scratch: &Bump,
|
_scratch: &Bump,
|
||||||
) -> Option<BSSRDF<'_>> {
|
) -> Option<BSSRDF> {
|
||||||
let material = {
|
let material = {
|
||||||
let root_mat = self.material.as_deref()?;
|
let root_mat = self.material.as_deref()?;
|
||||||
let mut active_mat: &Material = root_mat;
|
let mut active_mat: &Material = root_mat;
|
||||||
|
|
@ -368,17 +367,17 @@ impl SurfaceInteraction {
|
||||||
material.get_bssrdf(&tex_eval, &ctx, lambda)
|
material.get_bssrdf(&tex_eval, &ctx, lambda)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_bump_geometry(
|
fn compute_bump_geom(
|
||||||
&mut self,
|
&mut self,
|
||||||
tex_eval: &UniversalTextureEvaluator,
|
tex_eval: &UniversalTextureEvaluator,
|
||||||
displacement: Option<FloatTexture>,
|
displacement: Option<FloatTexture>,
|
||||||
normal_image: Option<&Image>,
|
normal_image: Option<Arc<Image>>,
|
||||||
) {
|
) {
|
||||||
let ctx = NormalBumpEvalContext::from(&*self);
|
let ctx = NormalBumpEvalContext::from(&*self);
|
||||||
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
||||||
bump_map(tex_eval, &disp, &ctx)
|
bump_map(tex_eval, &disp, &ctx)
|
||||||
} else if let Some(map) = normal_image {
|
} else if let Some(map) = normal_image {
|
||||||
normal_map(map, &ctx)
|
normal_map(map.as_ref(), &ctx)
|
||||||
} else {
|
} else {
|
||||||
(self.shading.dpdu, self.shading.dpdv)
|
(self.shading.dpdu, self.shading.dpdv)
|
||||||
};
|
};
|
||||||
|
|
@ -388,7 +387,7 @@ impl SurfaceInteraction {
|
||||||
ns = -ns;
|
ns = -ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_shading_geometry(ns, dpdu, dpdv, self.shading.dndu, self.shading.dndv, false);
|
self.set_shading_geom(ns, dpdu, dpdv, self.shading.dndu, self.shading.dndv, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_ray_with_differentials(
|
pub fn spawn_ray_with_differentials(
|
||||||
|
|
@ -544,7 +543,7 @@ impl SurfaceInteraction {
|
||||||
dpdv,
|
dpdv,
|
||||||
dndu,
|
dndu,
|
||||||
dndv,
|
dndv,
|
||||||
shading: ShadingGeometry {
|
shading: Shadinggeom {
|
||||||
n: shading_n,
|
n: shading_n,
|
||||||
dpdu,
|
dpdu,
|
||||||
dpdv,
|
dpdv,
|
||||||
|
|
@ -581,7 +580,7 @@ impl SurfaceInteraction {
|
||||||
si
|
si
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_shading_geometry(
|
pub fn set_shading_geom(
|
||||||
&mut self,
|
&mut self,
|
||||||
ns: Normal3f,
|
ns: Normal3f,
|
||||||
dpdus: Vector3f,
|
dpdus: Vector3f,
|
||||||
|
|
@ -4,16 +4,21 @@ use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::bssrdf::BSSRDF;
|
use crate::core::bssrdf::BSSRDF;
|
||||||
use crate::core::bxdf::{BSDF, DielectricBxDF, DiffuseBxDF};
|
use crate::core::bxdf::{
|
||||||
|
BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
|
||||||
|
};
|
||||||
|
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::interaction::{Interaction, ShadingGeometry, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext, TextureEvaluator};
|
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||||
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
use crate::core::texture::{
|
||||||
|
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||||
|
};
|
||||||
use crate::image::{Image, WrapMode, WrapMode2D};
|
use crate::image::{Image, WrapMode, WrapMode2D};
|
||||||
use crate::spectra::{SampledWavelengths, Spectrum, SpectrumTrait};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider};
|
||||||
use crate::utils::hash::hash_float;
|
use crate::utils::hash::hash_float;
|
||||||
use crate::utils::scattering::TrowbridgeReitzDistribution;
|
use crate::utils::math::clamp;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MaterialEvalContext {
|
pub struct MaterialEvalContext {
|
||||||
|
|
@ -47,7 +52,7 @@ pub struct NormalBumpEvalContext {
|
||||||
p: Point3f,
|
p: Point3f,
|
||||||
uv: Point2f,
|
uv: Point2f,
|
||||||
n: Normal3f,
|
n: Normal3f,
|
||||||
shading: ShadingGeometry,
|
shading: Shadinggeom,
|
||||||
dpdx: Vector3f,
|
dpdx: Vector3f,
|
||||||
dpdy: Vector3f,
|
dpdy: Vector3f,
|
||||||
// All 0
|
// All 0
|
||||||
|
|
@ -148,23 +153,22 @@ pub fn bump_map<T: TextureEvaluator>(
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
|
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
scratch: &'a Bump,
|
) -> BSDF;
|
||||||
) -> BSDF<'a>;
|
|
||||||
|
|
||||||
fn get_bssrdf<'a, T: TextureEvaluator>(
|
fn get_bssrdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>>;
|
) -> Option<BSSRDF>;
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
||||||
fn get_normal_map(&self) -> Option<&Image>;
|
fn get_normal_map(&self) -> Option<Arc<Image>>;
|
||||||
fn get_displacement(&self) -> Option<FloatTexture>;
|
fn get_displacement(&self) -> Option<FloatTexture>;
|
||||||
fn has_surface_scattering(&self) -> bool;
|
fn has_surface_scattering(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
@ -186,100 +190,354 @@ pub enum Material {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CoatedDiffuseMaterial;
|
pub struct CoatedDiffuseMaterial {
|
||||||
|
displacement: FloatTexture,
|
||||||
|
normal_map: Option<Arc<Image>>,
|
||||||
|
reflectance: SpectrumTexture,
|
||||||
|
albedo: SpectrumTexture,
|
||||||
|
u_roughness: FloatTexture,
|
||||||
|
v_roughness: FloatTexture,
|
||||||
|
thickness: FloatTexture,
|
||||||
|
g: FloatTexture,
|
||||||
|
eta: Spectrum,
|
||||||
|
remap_roughness: bool,
|
||||||
|
max_depth: usize,
|
||||||
|
n_samples: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoatedDiffuseMaterial {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
reflectance: SpectrumTexture,
|
||||||
|
u_roughness: FloatTexture,
|
||||||
|
v_roughness: FloatTexture,
|
||||||
|
thickness: FloatTexture,
|
||||||
|
albedo: SpectrumTexture,
|
||||||
|
g: FloatTexture,
|
||||||
|
eta: Spectrum,
|
||||||
|
displacement: FloatTexture,
|
||||||
|
normal_map: Option<Arc<Image>>,
|
||||||
|
remap_roughness: bool,
|
||||||
|
max_depth: usize,
|
||||||
|
n_samples: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
displacement,
|
||||||
|
normal_map,
|
||||||
|
reflectance,
|
||||||
|
albedo,
|
||||||
|
u_roughness,
|
||||||
|
v_roughness,
|
||||||
|
thickness,
|
||||||
|
g,
|
||||||
|
eta,
|
||||||
|
remap_roughness,
|
||||||
|
max_depth,
|
||||||
|
n_samples,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MaterialTrait for CoatedDiffuseMaterial {
|
impl MaterialTrait for CoatedDiffuseMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
&self,
|
||||||
|
tex_eval: &T,
|
||||||
|
ctx: &MaterialEvalContext,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> BSDF {
|
||||||
|
let r = SampledSpectrum::clamp(
|
||||||
|
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
|
||||||
|
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
|
||||||
|
|
||||||
|
if self.remap_roughness {
|
||||||
|
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
|
||||||
|
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
|
||||||
|
}
|
||||||
|
|
||||||
|
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
|
||||||
|
|
||||||
|
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
|
||||||
|
let mut sampled_eta = self.eta.evaluate(lambda[0]);
|
||||||
|
if self.eta.is_constant() {
|
||||||
|
let mut lambda = *lambda;
|
||||||
|
lambda.terminate_secondary_inplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampled_eta == 0. {
|
||||||
|
sampled_eta = 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = SampledSpectrum::clamp(
|
||||||
|
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
|
|
||||||
|
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
|
||||||
|
let bxdf = BxDF::CoatedDiffuse(CoatedDiffuseBxDF::new(
|
||||||
|
DielectricBxDF::new(sampled_eta, distrib),
|
||||||
|
DiffuseBxDF::new(r),
|
||||||
|
thick,
|
||||||
|
a,
|
||||||
|
gg,
|
||||||
|
self.max_depth,
|
||||||
|
self.n_samples,
|
||||||
|
));
|
||||||
|
|
||||||
|
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> Option<BSSRDF> {
|
||||||
) -> BSDF<'a> {
|
None
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn get_bssrdf<'a, T>(
|
|
||||||
&self,
|
|
||||||
_tex_eval: &T,
|
|
||||||
_ctx: &MaterialEvalContext,
|
|
||||||
_lambda: &SampledWavelengths,
|
|
||||||
) -> Option<BSSRDF<'a>> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
tex_eval.can_evaluate(
|
||||||
|
&[
|
||||||
|
&self.u_roughness,
|
||||||
|
&self.v_roughness,
|
||||||
|
&self.thickness,
|
||||||
|
&self.g,
|
||||||
|
],
|
||||||
|
&[&self.reflectance, &self.albedo],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
self.normal_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
todo!()
|
Some(self.displacement.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_surface_scattering(&self) -> bool {
|
fn has_surface_scattering(&self) -> bool {
|
||||||
todo!()
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CoatedConductorMaterial;
|
pub struct CoatedConductorMaterial {
|
||||||
|
displacement: FloatTexture,
|
||||||
|
normal_map: Option<Arc<Image>>,
|
||||||
|
interface_uroughness: FloatTexture,
|
||||||
|
interface_vroughness: FloatTexture,
|
||||||
|
thickness: FloatTexture,
|
||||||
|
interface_eta: Spectrum,
|
||||||
|
g: FloatTexture,
|
||||||
|
albedo: SpectrumTexture,
|
||||||
|
conductor_uroughness: FloatTexture,
|
||||||
|
conductor_vroughness: FloatTexture,
|
||||||
|
conductor_eta: Option<SpectrumTexture>,
|
||||||
|
k: Option<SpectrumTexture>,
|
||||||
|
reflectance: SpectrumTexture,
|
||||||
|
remap_roughness: bool,
|
||||||
|
max_depth: usize,
|
||||||
|
n_samples: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoatedConductorMaterial {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
displacement: FloatTexture,
|
||||||
|
normal_map: Option<Arc<Image>>,
|
||||||
|
interface_uroughness: FloatTexture,
|
||||||
|
interface_vroughness: FloatTexture,
|
||||||
|
thickness: FloatTexture,
|
||||||
|
interface_eta: Spectrum,
|
||||||
|
g: FloatTexture,
|
||||||
|
albedo: SpectrumTexture,
|
||||||
|
conductor_uroughness: FloatTexture,
|
||||||
|
conductor_vroughness: FloatTexture,
|
||||||
|
conductor_eta: Option<SpectrumTexture>,
|
||||||
|
k: Option<SpectrumTexture>,
|
||||||
|
reflectance: SpectrumTexture,
|
||||||
|
remap_roughness: bool,
|
||||||
|
max_depth: usize,
|
||||||
|
n_samples: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
displacement,
|
||||||
|
normal_map,
|
||||||
|
interface_uroughness,
|
||||||
|
interface_vroughness,
|
||||||
|
thickness,
|
||||||
|
interface_eta,
|
||||||
|
g,
|
||||||
|
albedo,
|
||||||
|
conductor_uroughness,
|
||||||
|
conductor_vroughness,
|
||||||
|
conductor_eta,
|
||||||
|
k,
|
||||||
|
reflectance,
|
||||||
|
remap_roughness,
|
||||||
|
max_depth,
|
||||||
|
n_samples,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MaterialTrait for CoatedConductorMaterial {
|
impl MaterialTrait for CoatedConductorMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
&self,
|
||||||
|
tex_eval: &T,
|
||||||
|
ctx: &MaterialEvalContext,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> BSDF {
|
||||||
|
let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx);
|
||||||
|
let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx);
|
||||||
|
|
||||||
|
if self.remap_roughness {
|
||||||
|
iurough = TrowbridgeReitzDistribution::roughness_to_alpha(iurough);
|
||||||
|
ivrough = TrowbridgeReitzDistribution::roughness_to_alpha(ivrough);
|
||||||
|
}
|
||||||
|
let interface_distrib = TrowbridgeReitzDistribution::new(iurough, ivrough);
|
||||||
|
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
|
||||||
|
|
||||||
|
let mut ieta = self.interface_eta.evaluate(lambda[0]);
|
||||||
|
if self.interface_eta.is_constant() {
|
||||||
|
let mut lambda = *lambda;
|
||||||
|
lambda.terminate_secondary_inplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ieta == 0. {
|
||||||
|
ieta = 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut ce, mut ck) = if let Some(eta_tex) = &self.conductor_eta {
|
||||||
|
let k_tex = self
|
||||||
|
.k
|
||||||
|
.as_ref()
|
||||||
|
.expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present");
|
||||||
|
let ce = tex_eval.evaluate_spectrum(eta_tex, ctx, lambda);
|
||||||
|
let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda);
|
||||||
|
(ce, ck)
|
||||||
|
} else {
|
||||||
|
let r = SampledSpectrum::clamp(
|
||||||
|
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
|
||||||
|
0.,
|
||||||
|
0.9999,
|
||||||
|
);
|
||||||
|
let ce = SampledSpectrum::new(1.0);
|
||||||
|
let one_minus_r = SampledSpectrum::new(1.) - r;
|
||||||
|
let ck = 2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt();
|
||||||
|
(ce, ck)
|
||||||
|
};
|
||||||
|
|
||||||
|
ce /= ieta;
|
||||||
|
ck /= ieta;
|
||||||
|
|
||||||
|
let mut curough = tex_eval.evaluate_float(&self.conductor_uroughness, ctx);
|
||||||
|
let mut cvrough = tex_eval.evaluate_float(&self.conductor_vroughness, ctx);
|
||||||
|
|
||||||
|
if self.remap_roughness {
|
||||||
|
curough = TrowbridgeReitzDistribution::roughness_to_alpha(curough);
|
||||||
|
cvrough = TrowbridgeReitzDistribution::roughness_to_alpha(cvrough);
|
||||||
|
}
|
||||||
|
|
||||||
|
let conductor_distrib = TrowbridgeReitzDistribution::new(curough, cvrough);
|
||||||
|
let a = SampledSpectrum::clamp(
|
||||||
|
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
|
||||||
|
0.,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
|
|
||||||
|
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
|
||||||
|
let bxdf = BxDF::CoatedConductor(CoatedConductorBxDF::new(
|
||||||
|
DielectricBxDF::new(ieta, interface_distrib),
|
||||||
|
ConductorBxDF::new(&conductor_distrib, ce, ck),
|
||||||
|
thick,
|
||||||
|
a,
|
||||||
|
gg,
|
||||||
|
self.max_depth,
|
||||||
|
self.n_samples,
|
||||||
|
));
|
||||||
|
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> Option<BSSRDF> {
|
||||||
) -> BSDF<'a> {
|
None
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
|
||||||
&self,
|
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
_tex_eval: &T,
|
let float_textures = [
|
||||||
_ctx: &MaterialEvalContext,
|
&self.interface_uroughness,
|
||||||
_lambda: &SampledWavelengths,
|
&self.interface_vroughness,
|
||||||
) -> Option<BSSRDF<'a>> {
|
&self.thickness,
|
||||||
todo!()
|
&self.g,
|
||||||
|
&self.conductor_uroughness,
|
||||||
|
&self.conductor_vroughness,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut spectrum_textures = Vec::with_capacity(4);
|
||||||
|
|
||||||
|
spectrum_textures.push(&self.albedo);
|
||||||
|
|
||||||
|
if let Some(eta) = &self.conductor_eta {
|
||||||
|
spectrum_textures.push(eta);
|
||||||
}
|
}
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
if let Some(k) = &self.k {
|
||||||
todo!()
|
spectrum_textures.push(k);
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
|
||||||
todo!()
|
if self.conductor_eta.is_none() {
|
||||||
|
spectrum_textures.push(&self.reflectance);
|
||||||
|
}
|
||||||
|
|
||||||
|
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
|
self.normal_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
todo!()
|
Some(self.displacement.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_surface_scattering(&self) -> bool {
|
fn has_surface_scattering(&self) -> bool {
|
||||||
todo!()
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ConductorMaterial;
|
pub struct ConductorMaterial;
|
||||||
impl MaterialTrait for ConductorMaterial {
|
impl MaterialTrait for ConductorMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -300,13 +558,12 @@ pub struct DielectricMaterial {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialTrait for DielectricMaterial {
|
impl MaterialTrait for DielectricMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
let mut sampled_eta = self.eta.evaluate(lambda[0]);
|
let mut sampled_eta = self.eta.evaluate(lambda[0]);
|
||||||
if !self.eta.is_constant() {
|
if !self.eta.is_constant() {
|
||||||
lambda.terminate_secondary();
|
lambda.terminate_secondary();
|
||||||
|
|
@ -325,17 +582,17 @@ impl MaterialTrait for DielectricMaterial {
|
||||||
}
|
}
|
||||||
|
|
||||||
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
|
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
|
||||||
let bxdf = scratch.alloc(DielectricBxDF::new(sampled_eta, distrib));
|
let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
|
||||||
|
|
||||||
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,8 +600,8 @@ impl MaterialTrait for DielectricMaterial {
|
||||||
tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[])
|
tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
self.normal_map.as_deref()
|
self.normal_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -363,24 +620,23 @@ pub struct DiffuseMaterial {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialTrait for DiffuseMaterial {
|
impl MaterialTrait for DiffuseMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
|
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
|
||||||
let bxdf = scratch.alloc(DiffuseBxDF::new(r));
|
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
|
||||||
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,12 +644,14 @@ impl MaterialTrait for DiffuseMaterial {
|
||||||
tex_eval.can_evaluate(&[], &[&self.reflectance])
|
tex_eval.can_evaluate(&[], &[&self.reflectance])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
self.normal_map.as_deref()
|
self.normal_map.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
Some(self.displacement.clone())
|
Some(self.displacement.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_surface_scattering(&self) -> bool {
|
fn has_surface_scattering(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -401,33 +659,35 @@ impl MaterialTrait for DiffuseMaterial {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DiffuseTransmissionMaterial;
|
pub struct DiffuseTransmissionMaterial;
|
||||||
impl MaterialTrait for DiffuseTransmissionMaterial {
|
impl MaterialTrait for DiffuseTransmissionMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
|
||||||
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_surface_scattering(&self) -> bool {
|
fn has_surface_scattering(&self) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
@ -435,28 +695,27 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HairMaterial;
|
pub struct HairMaterial;
|
||||||
impl MaterialTrait for HairMaterial {
|
impl MaterialTrait for HairMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -470,28 +729,27 @@ impl MaterialTrait for HairMaterial {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MeasuredMaterial;
|
pub struct MeasuredMaterial;
|
||||||
impl MaterialTrait for MeasuredMaterial {
|
impl MaterialTrait for MeasuredMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -504,28 +762,27 @@ impl MaterialTrait for MeasuredMaterial {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubsurfaceMaterial;
|
pub struct SubsurfaceMaterial;
|
||||||
impl MaterialTrait for SubsurfaceMaterial {
|
impl MaterialTrait for SubsurfaceMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -538,27 +795,26 @@ impl MaterialTrait for SubsurfaceMaterial {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ThinDielectricMaterial;
|
pub struct ThinDielectricMaterial;
|
||||||
impl MaterialTrait for ThinDielectricMaterial {
|
impl MaterialTrait for ThinDielectricMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -600,23 +856,22 @@ impl MixMaterial {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialTrait for MixMaterial {
|
impl MaterialTrait for MixMaterial {
|
||||||
fn get_bxdf<'a, T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
scratch: &'a Bump,
|
) -> BSDF {
|
||||||
) -> BSDF<'a> {
|
|
||||||
let chosen_mat = self.choose_material(tex_eval, ctx);
|
let chosen_mat = self.choose_material(tex_eval, ctx);
|
||||||
chosen_mat.get_bxdf(tex_eval, ctx, lambda, scratch)
|
chosen_mat.get_bsdf(tex_eval, ctx, lambda)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bssrdf<'a, T>(
|
fn get_bssrdf<T>(
|
||||||
&self,
|
&self,
|
||||||
_tex_eval: &T,
|
_tex_eval: &T,
|
||||||
_ctx: &MaterialEvalContext,
|
_ctx: &MaterialEvalContext,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<BSSRDF<'a>> {
|
) -> Option<BSSRDF> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -624,7 +879,7 @@ impl MaterialTrait for MixMaterial {
|
||||||
tex_eval.can_evaluate(&[&self.amount], &[])
|
tex_eval.can_evaluate(&[&self.amount], &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<&Image> {
|
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2,18 +2,18 @@ use bumpalo::Bump;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, INV_4_PI, PI, clamp_t};
|
use crate::core::geometry::{
|
||||||
use crate::geometry::{
|
|
||||||
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
|
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
|
||||||
};
|
};
|
||||||
|
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
||||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
|
||||||
};
|
};
|
||||||
use crate::utils::containers::SampledGrid;
|
use crate::utils::containers::SampledGrid;
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::{clamp, square};
|
||||||
use crate::utils::rng::Rng;
|
use crate::utils::rng::Rng;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PhaseFunctionSample {
|
pub struct PhaseFunctionSample {
|
||||||
|
|
@ -229,7 +229,7 @@ impl DDAMajorantIterator {
|
||||||
let grid_intersect = Vector3f::from(p_grid_start);
|
let grid_intersect = Vector3f::from(p_grid_start);
|
||||||
|
|
||||||
for axis in 0..3 {
|
for axis in 0..3 {
|
||||||
iter.voxel[axis] = clamp_t(
|
iter.voxel[axis] = clamp(
|
||||||
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
|
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
|
||||||
0,
|
0,
|
||||||
grid.res[axis] - 1,
|
grid.res[axis] - 1,
|
||||||
|
|
@ -424,11 +424,9 @@ impl HomogeneousMedium {
|
||||||
le_scale: Float,
|
le_scale: Float,
|
||||||
g: Float,
|
g: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut sigma_a_spec =
|
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
|
||||||
DenselySampledSpectrum::from_spectrum(&sigma_a, LAMBDA_MIN, LAMBDA_MAX);
|
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
|
||||||
let mut sigma_s_spec =
|
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
|
||||||
DenselySampledSpectrum::from_spectrum(&sigma_s, LAMBDA_MIN, LAMBDA_MAX);
|
|
||||||
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le, LAMBDA_MIN, LAMBDA_MAX);
|
|
||||||
|
|
||||||
sigma_a_spec.scale(sigma_scale);
|
sigma_a_spec.scale(sigma_scale);
|
||||||
sigma_s_spec.scale(sigma_scale);
|
sigma_s_spec.scale(sigma_scale);
|
||||||
|
|
@ -480,7 +478,7 @@ impl MediumTrait for HomogeneousMedium {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GridMedium {
|
pub struct GridMedium {
|
||||||
bounds: Bounds3f,
|
bounds: Bounds3f,
|
||||||
render_from_medium: Transform<Float>,
|
render_from_medium: TransformGeneric<Float>,
|
||||||
sigma_a_spec: DenselySampledSpectrum,
|
sigma_a_spec: DenselySampledSpectrum,
|
||||||
sigma_s_spec: DenselySampledSpectrum,
|
sigma_s_spec: DenselySampledSpectrum,
|
||||||
density_grid: SampledGrid<Float>,
|
density_grid: SampledGrid<Float>,
|
||||||
|
|
@ -496,7 +494,7 @@ impl GridMedium {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bounds: &Bounds3f,
|
bounds: &Bounds3f,
|
||||||
render_from_medium: &Transform<Float>,
|
render_from_medium: &TransformGeneric<Float>,
|
||||||
sigma_a: &Spectrum,
|
sigma_a: &Spectrum,
|
||||||
sigma_s: &Spectrum,
|
sigma_s: &Spectrum,
|
||||||
sigma_scale: Float,
|
sigma_scale: Float,
|
||||||
|
|
@ -506,11 +504,9 @@ impl GridMedium {
|
||||||
le: &Spectrum,
|
le: &Spectrum,
|
||||||
le_scale: SampledGrid<Float>,
|
le_scale: SampledGrid<Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut sigma_a_spec =
|
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a);
|
||||||
DenselySampledSpectrum::from_spectrum(sigma_a, LAMBDA_MIN, LAMBDA_MAX);
|
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s);
|
||||||
let mut sigma_s_spec =
|
let le_spec = DenselySampledSpectrum::from_spectrum(le);
|
||||||
DenselySampledSpectrum::from_spectrum(sigma_s, LAMBDA_MIN, LAMBDA_MAX);
|
|
||||||
let le_spec = DenselySampledSpectrum::from_spectrum(le, LAMBDA_MIN, LAMBDA_MAX);
|
|
||||||
sigma_a_spec.scale(sigma_scale);
|
sigma_a_spec.scale(sigma_scale);
|
||||||
sigma_s_spec.scale(sigma_scale);
|
sigma_s_spec.scale(sigma_scale);
|
||||||
|
|
||||||
|
|
@ -628,7 +624,7 @@ impl MediumTrait for GridMedium {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RGBGridMedium {
|
pub struct RGBGridMedium {
|
||||||
bounds: Bounds3f,
|
bounds: Bounds3f,
|
||||||
render_from_medium: Transform<Float>,
|
render_from_medium: TransformGeneric<Float>,
|
||||||
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
||||||
le_scale: Float,
|
le_scale: Float,
|
||||||
phase: HGPhaseFunction,
|
phase: HGPhaseFunction,
|
||||||
|
|
@ -642,7 +638,7 @@ impl RGBGridMedium {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bounds: &Bounds3f,
|
bounds: &Bounds3f,
|
||||||
render_from_medium: &Transform<Float>,
|
render_from_medium: &TransformGeneric<Float>,
|
||||||
g: Float,
|
g: Float,
|
||||||
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||||
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
pub mod aggregates;
|
pub mod aggregates;
|
||||||
pub mod bssrdf;
|
pub mod bssrdf;
|
||||||
pub mod bxdf;
|
pub mod bxdf;
|
||||||
pub mod cie;
|
|
||||||
pub mod film;
|
pub mod film;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
|
pub mod geometry;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
pub mod material;
|
pub mod material;
|
||||||
pub mod medium;
|
pub mod medium;
|
||||||
|
|
@ -11,4 +11,5 @@ pub mod options;
|
||||||
pub mod pbrt;
|
pub mod pbrt;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
|
pub mod scattering;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::geometry::Point2i;
|
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
|
||||||
|
use crate::Float;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
|
@ -19,11 +20,9 @@ pub struct BasicPBRTOptions {
|
||||||
pub force_diffuse: bool,
|
pub force_diffuse: bool,
|
||||||
pub use_gpu: bool,
|
pub use_gpu: bool,
|
||||||
pub wavefront: bool,
|
pub wavefront: bool,
|
||||||
|
pub interactive: bool,
|
||||||
|
pub fullscreen: bool,
|
||||||
pub rendering_space: RenderingCoordinateSystem,
|
pub rendering_space: RenderingCoordinateSystem,
|
||||||
pub debug_start: Option<(Point2i, i32)>,
|
|
||||||
pub write_partial_images: bool,
|
|
||||||
pub mse_reference_image: Option<String>,
|
|
||||||
pub mse_reference_output: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BasicPBRTOptions {
|
impl Default for BasicPBRTOptions {
|
||||||
|
|
@ -37,11 +36,9 @@ impl Default for BasicPBRTOptions {
|
||||||
force_diffuse: false,
|
force_diffuse: false,
|
||||||
use_gpu: false,
|
use_gpu: false,
|
||||||
wavefront: false,
|
wavefront: false,
|
||||||
|
interactive: false,
|
||||||
|
fullscreen: false,
|
||||||
rendering_space: RenderingCoordinateSystem::CameraWorld,
|
rendering_space: RenderingCoordinateSystem::CameraWorld,
|
||||||
debug_start: Some((Point2i::default(), 0)),
|
|
||||||
write_partial_images: false,
|
|
||||||
mse_reference_image: None,
|
|
||||||
mse_reference_output: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +49,20 @@ pub struct PBRTOptions {
|
||||||
|
|
||||||
pub n_threads: usize,
|
pub n_threads: usize,
|
||||||
pub log_level: String,
|
pub log_level: String,
|
||||||
|
pub write_partial_images: bool,
|
||||||
pub image_file: String,
|
pub image_file: String,
|
||||||
|
pub pixel_samples: Option<i32>,
|
||||||
|
pub gpu_device: Option<u32>,
|
||||||
|
pub mse_reference_image: Option<String>,
|
||||||
|
pub mse_reference_output: Option<String>,
|
||||||
|
pub debug_start: Option<(Point2i, i32)>,
|
||||||
|
pub quick_render: bool,
|
||||||
|
pub upgrade: bool,
|
||||||
|
pub display_server: String,
|
||||||
|
pub crop_window: Option<Bounds2f>,
|
||||||
|
pub pixel_bounds: Option<Bounds2i>,
|
||||||
|
pub pixel_material: Option<Point2i>,
|
||||||
|
pub displacement_edge_scale: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PBRTOptions {
|
impl Default for PBRTOptions {
|
||||||
|
|
@ -61,7 +71,20 @@ impl Default for PBRTOptions {
|
||||||
basic: BasicPBRTOptions::default(),
|
basic: BasicPBRTOptions::default(),
|
||||||
n_threads: 0,
|
n_threads: 0,
|
||||||
log_level: "info".to_string(),
|
log_level: "info".to_string(),
|
||||||
|
write_partial_images: false,
|
||||||
|
pixel_samples: None,
|
||||||
|
gpu_device: None,
|
||||||
|
quick_render: false,
|
||||||
|
upgrade: false,
|
||||||
image_file: "output.exr".to_string(),
|
image_file: "output.exr".to_string(),
|
||||||
|
mse_reference_image: None,
|
||||||
|
mse_reference_output: None,
|
||||||
|
debug_start: Some((Point2i::default(), 0)),
|
||||||
|
display_server: "".to_string(),
|
||||||
|
crop_window: None,
|
||||||
|
pixel_bounds: None,
|
||||||
|
pixel_material: None,
|
||||||
|
displacement_edge_scale: 1.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::geometry::{Lerp, Point2f, Vector2f, Vector3f};
|
use crate::core::geometry::Lerp;
|
||||||
use num_traits::{Num, PrimInt};
|
use num_traits::{Num, PrimInt};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::ops::{Add, Mul};
|
use std::ops::{Add, Mul};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||||
|
|
@ -101,64 +100,6 @@ pub const PI_OVER_2: Float = 1.570_796_326_794_896_619_23;
|
||||||
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
|
pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
|
||||||
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
|
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn lerp<T, F>(t: F, a: T, b: T) -> T
|
|
||||||
where
|
|
||||||
T: Lerp<F>,
|
|
||||||
F: Copy,
|
|
||||||
{
|
|
||||||
T::lerp(t, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
|
|
||||||
where
|
|
||||||
T: Num + Copy + PartialOrd,
|
|
||||||
{
|
|
||||||
if x < T::zero() || x > T::one() {
|
|
||||||
return T::zero();
|
|
||||||
}
|
|
||||||
(T::one() + T::one()) * lerp(x, a, b) / (a + b)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample_linear<T>(u: T, a: T, b: T) -> T
|
|
||||||
where
|
|
||||||
T: Num,
|
|
||||||
{
|
|
||||||
if u == T::zero() && a == T::zero() {
|
|
||||||
return T::zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
u * (a + b)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clamp_t<T>(val: T, low: T, high: T) -> T
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
let r: T;
|
|
||||||
if val < low {
|
|
||||||
r = low;
|
|
||||||
} else if val > high {
|
|
||||||
r = high;
|
|
||||||
} else {
|
|
||||||
r = val;
|
|
||||||
}
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
|
||||||
if coeffs.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = coeffs[0];
|
|
||||||
for &c in &coeffs[1..] {
|
|
||||||
result = t.mul_add(result, c);
|
|
||||||
}
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn find_interval<T, P>(sz: T, pred: P) -> T
|
pub fn find_interval<T, P>(sz: T, pred: P) -> T
|
||||||
where
|
where
|
||||||
|
|
@ -196,13 +137,6 @@ pub fn gamma(n: i32) -> Float {
|
||||||
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
|
n as Float * MACHINE_EPSILON / (1. - n as Float * MACHINE_EPSILON)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum RenderingCoordinateSystem {
|
|
||||||
Camera,
|
|
||||||
CameraWorld,
|
|
||||||
World,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the static counters. These are thread-safe.
|
// Define the static counters. These are thread-safe.
|
||||||
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
||||||
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
@ -240,30 +174,3 @@ macro_rules! check_rare {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InternCache<T> {
|
|
||||||
cache: Mutex<HashSet<Arc<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> InternCache<T>
|
|
||||||
where
|
|
||||||
T: Eq + Hash + Clone,
|
|
||||||
{
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
cache: Mutex::new(HashSet::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup(&self, value: T) -> Arc<T> {
|
|
||||||
let mut lock = self.cache.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(existing) = lock.get(&value) {
|
|
||||||
return existing.clone(); // Returns a cheap Arc copy
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_item = Arc::new(value);
|
|
||||||
lock.insert(new_item.clone());
|
|
||||||
new_item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::core::aggregates::LinearBVHNode;
|
use crate::core::aggregates::LinearBVHNode;
|
||||||
|
use crate::core::geometry::{Bounds3f, Ray};
|
||||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||||
use crate::core::material::Material;
|
use crate::core::material::Material;
|
||||||
use crate::core::medium::{Medium, MediumInterface};
|
use crate::core::medium::{Medium, MediumInterface};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
|
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
|
||||||
use crate::geometry::{Bounds3f, Ray};
|
|
||||||
use crate::lights::Light;
|
use crate::lights::Light;
|
||||||
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
||||||
use crate::utils::hash::hash_float;
|
use crate::utils::hash::hash_float;
|
||||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -89,7 +89,7 @@ pub struct SimplePrimitiv {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TransformedPrimitive {
|
pub struct TransformedPrimitive {
|
||||||
primitive: Arc<dyn PrimitiveTrait>,
|
primitive: Arc<dyn PrimitiveTrait>,
|
||||||
render_from_primitive: Transform<Float>,
|
render_from_primitive: TransformGeneric<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveTrait for TransformedPrimitive {
|
impl PrimitiveTrait for TransformedPrimitive {
|
||||||
|
|
@ -4,18 +4,19 @@ use enum_dispatch::enum_dispatch;
|
||||||
use rand::seq::index::sample;
|
use rand::seq::index::sample;
|
||||||
|
|
||||||
use crate::core::filter::FilterTrait;
|
use crate::core::filter::FilterTrait;
|
||||||
use crate::core::pbrt::{
|
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
||||||
Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t, find_interval, lerp,
|
use crate::core::options::{PBRTOptions, get_options};
|
||||||
};
|
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval};
|
||||||
use crate::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
use crate::utils::math::{
|
use crate::utils::math::{
|
||||||
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
||||||
PRIME_TABLE_SIZE, Scrambler, compute_radical_inverse_permutations, encode_morton_2,
|
PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2,
|
||||||
inverse_radical_inverse, log2_int, owen_scrambled_radical_inverse, permutation_element,
|
inverse_radical_inverse, lerp, log2_int, owen_scrambled_radical_inverse, permutation_element,
|
||||||
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
|
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
|
||||||
sobol_sample,
|
sobol_sample,
|
||||||
};
|
};
|
||||||
|
use crate::utils::parameters::ParameterDictionary;
|
||||||
use crate::utils::rng::Rng;
|
use crate::utils::rng::Rng;
|
||||||
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
||||||
use crate::utils::{hash::*, sobol};
|
use crate::utils::{hash::*, sobol};
|
||||||
|
|
@ -68,6 +69,21 @@ impl IndependentSampler {
|
||||||
rng: Rng::default(),
|
rng: Rng::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
_full_res: Point2i,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let nsamp = options
|
||||||
|
.quick_render
|
||||||
|
.then_some(1)
|
||||||
|
.or(options.pixel_samples)
|
||||||
|
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
Ok(Self::new(nsamp as usize, seed as u64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for IndependentSampler {
|
impl SamplerTrait for IndependentSampler {
|
||||||
|
|
@ -198,6 +214,37 @@ impl HaltonSampler {
|
||||||
|
|
||||||
(yp, xp - d * yp)
|
(yp, xp - d * yp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
full_res: Point2i,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let nsamp = options
|
||||||
|
.quick_render
|
||||||
|
.then_some(1)
|
||||||
|
.or(options.pixel_samples)
|
||||||
|
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
let s = match params
|
||||||
|
.get_one_string("randomization", "permutedigits")
|
||||||
|
.as_str()
|
||||||
|
{
|
||||||
|
"none" => RandomizeStrategy::None,
|
||||||
|
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||||
|
"fastowen" => RandomizeStrategy::FastOwen,
|
||||||
|
"owen" => RandomizeStrategy::Owen,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: Unknown randomization strategy for Halton",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HaltonSampler::new(nsamp as usize, full_res, s, seed as u64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for HaltonSampler {
|
impl SamplerTrait for HaltonSampler {
|
||||||
|
|
@ -293,6 +340,35 @@ impl StratifiedSampler {
|
||||||
dim: 0,
|
dim: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
_full_res: Point2i,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let jitter = params.get_one_bool("jitter", true);
|
||||||
|
let (x_samples, y_samples) = if options.quick_render {
|
||||||
|
(1, 1)
|
||||||
|
} else if let Some(n) = options.pixel_samples {
|
||||||
|
let div = (n as f64).sqrt() as i32;
|
||||||
|
let y = (1..=div).rev().find(|d| n % d == 0).unwrap();
|
||||||
|
|
||||||
|
(n / y, y)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
params.get_one_int("xsamples", 4),
|
||||||
|
params.get_one_int("ysamples", 4),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
Ok(Self::new(
|
||||||
|
x_samples as usize,
|
||||||
|
y_samples as usize,
|
||||||
|
Some(seed as u64),
|
||||||
|
jitter,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for StratifiedSampler {
|
impl SamplerTrait for StratifiedSampler {
|
||||||
|
|
@ -407,6 +483,34 @@ impl PaddedSobolSampler {
|
||||||
RandomizeStrategy::None => unreachable!(),
|
RandomizeStrategy::None => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
_full_res: Point2i,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let nsamp = options
|
||||||
|
.quick_render
|
||||||
|
.then_some(1)
|
||||||
|
.or(options.pixel_samples)
|
||||||
|
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||||
|
"none" => RandomizeStrategy::None,
|
||||||
|
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||||
|
"fastowen" => RandomizeStrategy::FastOwen,
|
||||||
|
"owen" => RandomizeStrategy::Owen,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: Unknown randomization strategy for ZSobol",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::new(nsamp as usize, s, Some(seed as u64)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for PaddedSobolSampler {
|
impl SamplerTrait for PaddedSobolSampler {
|
||||||
|
|
@ -510,6 +614,34 @@ impl SobolSampler {
|
||||||
RandomizeStrategy::None => unreachable!(),
|
RandomizeStrategy::None => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
full_res: Point2i,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let nsamp = options
|
||||||
|
.quick_render
|
||||||
|
.then_some(1)
|
||||||
|
.or(options.pixel_samples)
|
||||||
|
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||||
|
"none" => RandomizeStrategy::None,
|
||||||
|
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||||
|
"fastowen" => RandomizeStrategy::FastOwen,
|
||||||
|
"owen" => RandomizeStrategy::Owen,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: Unknown randomization strategy for ZSobol",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::new(nsamp as usize, full_res, s, Some(seed as u64)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for SobolSampler {
|
impl SamplerTrait for SobolSampler {
|
||||||
|
|
@ -550,12 +682,12 @@ impl SamplerTrait for SobolSampler {
|
||||||
sobol_sample(self.sobol_index, 0, NoRandomizer),
|
sobol_sample(self.sobol_index, 0, NoRandomizer),
|
||||||
sobol_sample(self.sobol_index, 1, NoRandomizer),
|
sobol_sample(self.sobol_index, 1, NoRandomizer),
|
||||||
);
|
);
|
||||||
u[0] = clamp_t(
|
u[0] = clamp(
|
||||||
u[0] * self.scale as Float - self.pixel[0] as Float,
|
u[0] * self.scale as Float - self.pixel[0] as Float,
|
||||||
0.,
|
0.,
|
||||||
ONE_MINUS_EPSILON,
|
ONE_MINUS_EPSILON,
|
||||||
) as Float;
|
) as Float;
|
||||||
u[1] = clamp_t(
|
u[1] = clamp(
|
||||||
u[1] * self.scale as Float - self.pixel[1] as Float,
|
u[1] * self.scale as Float - self.pixel[1] as Float,
|
||||||
1.,
|
1.,
|
||||||
ONE_MINUS_EPSILON,
|
ONE_MINUS_EPSILON,
|
||||||
|
|
@ -649,6 +781,39 @@ impl ZSobolSampler {
|
||||||
|
|
||||||
sample_index
|
sample_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
full_res: Point2i,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let options = get_options();
|
||||||
|
let nsamp = options
|
||||||
|
.quick_render
|
||||||
|
.then_some(1)
|
||||||
|
.or(options.pixel_samples)
|
||||||
|
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||||
|
let seed = params.get_one_int("seed", options.seed);
|
||||||
|
let s = match params.get_one_string("randomization", "fastowen").as_str() {
|
||||||
|
"none" => RandomizeStrategy::None,
|
||||||
|
"permutedigits" => RandomizeStrategy::PermuteDigits,
|
||||||
|
"fastowen" => RandomizeStrategy::FastOwen,
|
||||||
|
"owen" => RandomizeStrategy::Owen,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: Unknown randomization strategy for ZSobol",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ZSobolSampler::new(
|
||||||
|
nsamp as u32,
|
||||||
|
full_res,
|
||||||
|
s,
|
||||||
|
Some(seed as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SamplerTrait for ZSobolSampler {
|
impl SamplerTrait for ZSobolSampler {
|
||||||
|
|
@ -758,3 +923,40 @@ pub enum Sampler {
|
||||||
ZSobol(ZSobolSampler),
|
ZSobol(ZSobolSampler),
|
||||||
MLT(MLTSampler),
|
MLT(MLTSampler),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
pub fn create(
|
||||||
|
name: &str,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
full_res: Point2i,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"zsobol" => {
|
||||||
|
let sampler = ZSobolSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::ZSobol(sampler))
|
||||||
|
}
|
||||||
|
"paddedsobol" => {
|
||||||
|
let sampler = PaddedSobolSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::PaddedSobol(sampler))
|
||||||
|
}
|
||||||
|
"halton" => {
|
||||||
|
let sampler = HaltonSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::Halton(sampler))
|
||||||
|
}
|
||||||
|
"sobol" => {
|
||||||
|
let sampler = SobolSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::Sobol(sampler))
|
||||||
|
}
|
||||||
|
"Independent" => {
|
||||||
|
let sampler = IndependentSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::Independent(sampler))
|
||||||
|
}
|
||||||
|
"stratified" => {
|
||||||
|
let sampler = StratifiedSampler::create(params, full_res, loc)?;
|
||||||
|
Ok(Sampler::Stratified(sampler))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,9 @@ use crate::core::geometry::{
|
||||||
Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
|
Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
|
||||||
tan2_theta,
|
tan2_theta,
|
||||||
};
|
};
|
||||||
use crate::core::pbrt::{Float, PI, clamp_t};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
||||||
use crate::utils::math::safe_sqrt;
|
use crate::utils::math::{clamp, lerp, safe_sqrt, square};
|
||||||
use crate::utils::math::{lerp, square};
|
|
||||||
use crate::utils::sampling::sample_uniform_disk_polar;
|
use crate::utils::sampling::sample_uniform_disk_polar;
|
||||||
|
|
||||||
use num::complex::Complex;
|
use num::complex::Complex;
|
||||||
|
|
@ -91,10 +90,10 @@ impl TrowbridgeReitzDistribution {
|
||||||
|
|
||||||
pub fn regularize(&mut self) {
|
pub fn regularize(&mut self) {
|
||||||
if self.alpha_x < 0.3 {
|
if self.alpha_x < 0.3 {
|
||||||
self.alpha_x = clamp_t(2. * self.alpha_x, 0.1, 0.3);
|
self.alpha_x = clamp(2. * self.alpha_x, 0.1, 0.3);
|
||||||
}
|
}
|
||||||
if self.alpha_y < 0.3 {
|
if self.alpha_y < 0.3 {
|
||||||
self.alpha_y = clamp_t(2. * self.alpha_y, 0.1, 0.3);
|
self.alpha_y = clamp(2. * self.alpha_y, 0.1, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +129,7 @@ pub fn reflect(wo: Vector3f, n: Normal3f) -> Vector3f {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
|
pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
|
||||||
let mut cos_safe = clamp_t(cos_theta_i, -1., 1.);
|
let mut cos_safe = clamp(cos_theta_i, -1., 1.);
|
||||||
let mut eta_corr = eta;
|
let mut eta_corr = eta;
|
||||||
if cos_theta_i < 0. {
|
if cos_theta_i < 0. {
|
||||||
eta_corr = 1. / eta_corr;
|
eta_corr = 1. / eta_corr;
|
||||||
|
|
@ -149,7 +148,7 @@ pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float {
|
pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float {
|
||||||
let cos_corr = clamp_t(cos_theta_i, 0., 1.);
|
let cos_corr = clamp(cos_theta_i, 0., 1.);
|
||||||
let sin2_theta_i = 1. - square(cos_corr);
|
let sin2_theta_i = 1. - square(cos_corr);
|
||||||
let sin2_theta_t: Complex<Float> = sin2_theta_i / square(eta);
|
let sin2_theta_t: Complex<Float> = sin2_theta_i / square(eta);
|
||||||
let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt();
|
let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt();
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
|
||||||
|
};
|
||||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
|
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
|
||||||
use crate::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
|
||||||
use crate::geometry::{spherical_phi, spherical_theta};
|
|
||||||
use crate::image::WrapMode;
|
use crate::image::WrapMode;
|
||||||
|
use crate::spectra::color::ColorEncoding;
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, Spectrum,
|
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||||
|
SampledWavelengths, Spectrum, SpectrumProvider,
|
||||||
};
|
};
|
||||||
use crate::spectra::{SampledWavelengths, SpectrumTrait};
|
use crate::utils::Transform;
|
||||||
use crate::utils::color::{ColorEncoding, RGB};
|
use crate::utils::error::FileLoc;
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::square;
|
||||||
use crate::utils::mipmap::MIPMap;
|
use crate::utils::mipmap::MIPMap;
|
||||||
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMapSample};
|
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMapSample};
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::parameters::TextureParameterDictionary;
|
||||||
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
|
|
@ -42,6 +46,46 @@ pub enum TextureMapping2D {
|
||||||
Planar(PlanarMapping),
|
Planar(PlanarMapping),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextureMapping2D {
|
||||||
|
pub fn create(
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
render_from_texture: &Transform,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let mtype = params.get_one_string("mapping", "uv");
|
||||||
|
match mtype.as_str() {
|
||||||
|
"uv" => {
|
||||||
|
let su = params.get_one_float("uscale", 1.);
|
||||||
|
let sv = params.get_one_float("vscale", 1.);
|
||||||
|
let du = params.get_one_float("udelta", 0.);
|
||||||
|
let dv = params.get_one_float("vdelta", 0.);
|
||||||
|
let mapping = UVMapping::new(su, sv, du, dv);
|
||||||
|
TextureMapping2D::UV(mapping)
|
||||||
|
}
|
||||||
|
"spherical" => {
|
||||||
|
let mapping = SphericalMapping::new(&render_from_texture.inverse());
|
||||||
|
TextureMapping2D::Spherical(mapping)
|
||||||
|
}
|
||||||
|
"cylindrical" => {
|
||||||
|
let mapping = CylindricalMapping::new(&render_from_texture.inverse());
|
||||||
|
TextureMapping2D::Cylindrical(mapping)
|
||||||
|
}
|
||||||
|
"planar" => {
|
||||||
|
let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.));
|
||||||
|
let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.));
|
||||||
|
let ds = params.get_one_float("udelta", 0.);
|
||||||
|
let dt = params.get_one_float("vdelta", 0.);
|
||||||
|
let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt);
|
||||||
|
TextureMapping2D::Planar(mapping)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::error!("{}: 2D texture mapping unknown {}", loc, mtype);
|
||||||
|
TextureMapping2D::UV(UVMapping::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct UVMapping {
|
pub struct UVMapping {
|
||||||
su: Float,
|
su: Float,
|
||||||
|
|
@ -50,6 +94,12 @@ pub struct UVMapping {
|
||||||
dv: Float,
|
dv: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UVMapping {
|
||||||
|
pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self {
|
||||||
|
Self { su, sv, du, dv }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for UVMapping {
|
impl Default for UVMapping {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -80,11 +130,11 @@ impl TextureMapping2DTrait for UVMapping {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SphericalMapping {
|
pub struct SphericalMapping {
|
||||||
texture_from_render: Transform<Float>,
|
texture_from_render: TransformGeneric<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SphericalMapping {
|
impl SphericalMapping {
|
||||||
pub fn new(texture_from_render: &Transform<Float>) -> Self {
|
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_from_render: *texture_from_render,
|
texture_from_render: *texture_from_render,
|
||||||
}
|
}
|
||||||
|
|
@ -123,11 +173,11 @@ impl TextureMapping2DTrait for SphericalMapping {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CylindricalMapping {
|
pub struct CylindricalMapping {
|
||||||
texture_from_render: Transform<Float>,
|
texture_from_render: TransformGeneric<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CylindricalMapping {
|
impl CylindricalMapping {
|
||||||
pub fn new(texture_from_render: &Transform<Float>) -> Self {
|
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_from_render: *texture_from_render,
|
texture_from_render: *texture_from_render,
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +209,7 @@ impl TextureMapping2DTrait for CylindricalMapping {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlanarMapping {
|
pub struct PlanarMapping {
|
||||||
texture_from_render: Transform<Float>,
|
texture_from_render: TransformGeneric<Float>,
|
||||||
vs: Vector3f,
|
vs: Vector3f,
|
||||||
vt: Vector3f,
|
vt: Vector3f,
|
||||||
ds: Float,
|
ds: Float,
|
||||||
|
|
@ -168,7 +218,7 @@ pub struct PlanarMapping {
|
||||||
|
|
||||||
impl PlanarMapping {
|
impl PlanarMapping {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
texture_from_render: &Transform<Float>,
|
texture_from_render: &TransformGeneric<Float>,
|
||||||
vs: Vector3f,
|
vs: Vector3f,
|
||||||
vt: Vector3f,
|
vt: Vector3f,
|
||||||
ds: Float,
|
ds: Float,
|
||||||
|
|
@ -222,11 +272,11 @@ pub enum TextureMapping3D {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PointTransformMapping {
|
pub struct PointTransformMapping {
|
||||||
texture_from_render: Transform<Float>,
|
texture_from_render: TransformGeneric<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointTransformMapping {
|
impl PointTransformMapping {
|
||||||
pub fn new(texture_from_render: &Transform<Float>) -> Self {
|
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_from_render: *texture_from_render,
|
texture_from_render: *texture_from_render,
|
||||||
}
|
}
|
||||||
|
|
@ -332,6 +382,7 @@ pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug {
|
||||||
pub enum FloatTexture {
|
pub enum FloatTexture {
|
||||||
FloatConstant(FloatConstantTexture),
|
FloatConstant(FloatConstantTexture),
|
||||||
GPUFloatImage(GPUFloatImageTexture),
|
GPUFloatImage(GPUFloatImageTexture),
|
||||||
|
FloatImage(FloatImageTexture),
|
||||||
FloatMix(FloatMixTexture),
|
FloatMix(FloatMixTexture),
|
||||||
FloatDirectionMix(FloatDirectionMixTexture),
|
FloatDirectionMix(FloatDirectionMixTexture),
|
||||||
FloatScaled(FloatScaledTexture),
|
FloatScaled(FloatScaledTexture),
|
||||||
|
|
@ -345,6 +396,75 @@ pub enum FloatTexture {
|
||||||
Wrinkled(WrinkledTexture),
|
Wrinkled(WrinkledTexture),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FloatTexture {
|
||||||
|
pub fn create(
|
||||||
|
name: &str,
|
||||||
|
render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
gpu: bool,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"constant" => {
|
||||||
|
let tex = FloatConstantTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatConstant(tex))
|
||||||
|
}
|
||||||
|
"scale" => Ok(FloatScaledTexture::create(render_from_texture, params, loc)),
|
||||||
|
"mix" => {
|
||||||
|
let tex = FloatMixTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatMix(tex))
|
||||||
|
}
|
||||||
|
"directionmix" => {
|
||||||
|
let tex = FloatDirectionMixTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatDirectionMix(tex))
|
||||||
|
}
|
||||||
|
"bilerp" => {
|
||||||
|
let tex = FloatBilerpTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatBilerp(tex))
|
||||||
|
}
|
||||||
|
"imagemap" => {
|
||||||
|
if gpu {
|
||||||
|
let tex = GPUFloatImageTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::GPUFloatImage(tex))
|
||||||
|
} else {
|
||||||
|
let tex = FloatImageTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatImage(tex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"checkerboard" => {
|
||||||
|
let tex = FloatCheckerboardTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatCheckerboard(tex))
|
||||||
|
}
|
||||||
|
"dots" => {
|
||||||
|
let tex = FloatDotsTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatDots(tex))
|
||||||
|
}
|
||||||
|
"fbm" => {
|
||||||
|
let tex = FBmTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FBm(tex))
|
||||||
|
}
|
||||||
|
"wrinkled" => {
|
||||||
|
let tex = WrinkledTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::Wrinkled(tex))
|
||||||
|
}
|
||||||
|
"windy" => {
|
||||||
|
let tex = WindyTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::Windy(tex))
|
||||||
|
}
|
||||||
|
"ptex" => {
|
||||||
|
if gpu {
|
||||||
|
let tex = GPUFloatPtexTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::GPUFLoatPtex(tex))
|
||||||
|
} else {
|
||||||
|
let tex = FloatPtexTexture::create(render_from_texture, params, loc);
|
||||||
|
Ok(FloatTexture::FloatPtex(tex))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => Err(format!("Float texture type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatConstantTexture {
|
pub struct FloatConstantTexture {
|
||||||
value: Float,
|
value: Float,
|
||||||
|
|
@ -354,6 +474,14 @@ impl FloatConstantTexture {
|
||||||
pub fn new(value: Float) -> Self {
|
pub fn new(value: Float) -> Self {
|
||||||
Self { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
_render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(params.get_one_float("value", 1.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for FloatConstantTexture {
|
impl FloatTextureTrait for FloatConstantTexture {
|
||||||
|
|
@ -363,24 +491,72 @@ impl FloatTextureTrait for FloatConstantTexture {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GPUFloatImageTexture;
|
pub struct GPUFloatImageTexture {
|
||||||
|
mapping: TextureMapping2D,
|
||||||
|
filename: String,
|
||||||
|
scale: Float,
|
||||||
|
invert: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for GPUFloatImageTexture {}
|
impl FloatTextureTrait for GPUFloatImageTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatImageTexture {
|
||||||
|
base: ImageTextureBase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatImageTexture {
|
||||||
|
pub fn new(
|
||||||
|
mapping: TextureMapping2D,
|
||||||
|
filename: String,
|
||||||
|
filter_options: MIPMapFilterOptions,
|
||||||
|
wrap_mode: WrapMode,
|
||||||
|
scale: Float,
|
||||||
|
invert: bool,
|
||||||
|
encoding: ColorEncoding,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
base: ImageTextureBase::new(
|
||||||
|
mapping,
|
||||||
|
filename,
|
||||||
|
filter_options,
|
||||||
|
wrap_mode,
|
||||||
|
scale,
|
||||||
|
invert,
|
||||||
|
encoding,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatTextureTrait for FloatImageTexture {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatMixTexture {
|
pub struct FloatMixTexture {
|
||||||
tex1: Box<FloatTexture>,
|
tex1: Arc<FloatTexture>,
|
||||||
tex2: Box<FloatTexture>,
|
tex2: Arc<FloatTexture>,
|
||||||
amount: Box<FloatTexture>,
|
amount: Arc<FloatTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FloatMixTexture {
|
impl FloatMixTexture {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
tex1: Box<FloatTexture>,
|
tex1: Arc<FloatTexture>,
|
||||||
tex2: Box<FloatTexture>,
|
tex2: Arc<FloatTexture>,
|
||||||
amount: Box<FloatTexture>,
|
amount: Arc<FloatTexture>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { tex1, tex2, amount }
|
Self { tex1, tex2, amount }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
_render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let tex1 = params.get_float_texture("tex1", 0.);
|
||||||
|
let tex2 = params.get_float_texture("tex2", 1.);
|
||||||
|
let amount = params.get_float_texture("amount", 0.5);
|
||||||
|
Self::new(tex1, tex2, amount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for FloatMixTexture {
|
impl FloatTextureTrait for FloatMixTexture {
|
||||||
|
|
@ -399,13 +575,68 @@ impl FloatTextureTrait for FloatMixTexture {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatDirectionMixTexture;
|
pub struct FloatDirectionMixTexture {
|
||||||
|
tex1: Arc<FloatTexture>,
|
||||||
|
tex2: Arc<FloatTexture>,
|
||||||
|
dir: Vector3f,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatDirectionMixTexture {
|
||||||
|
pub fn new(tex1: Arc<FloatTexture>, tex2: Arc<FloatTexture>, dir: Vector3f) -> Self {
|
||||||
|
Self { tex1, tex2, dir }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.));
|
||||||
|
let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
|
||||||
|
let tex1 = params.get_float_texture("tex1", 0.);
|
||||||
|
let tex2 = params.get_float_texture("tex2", 1.);
|
||||||
|
Self::new(tex1, tex2, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for FloatDirectionMixTexture {}
|
impl FloatTextureTrait for FloatDirectionMixTexture {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatScaledTexture {
|
pub struct FloatScaledTexture {
|
||||||
tex: Box<FloatTexture>,
|
tex: Arc<FloatTexture>,
|
||||||
scale: Box<FloatTexture>,
|
scale: Arc<FloatTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatScaledTexture {
|
||||||
|
pub fn new(tex: Arc<FloatTexture>, scale: Arc<FloatTexture>) -> Self {
|
||||||
|
Self { tex, scale }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
_render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
_loc: &FileLoc,
|
||||||
|
) -> FloatTexture {
|
||||||
|
let mut tex = params.get_float_texture("tex", 1.);
|
||||||
|
let mut scale = params.get_float_texture("scale", 1.);
|
||||||
|
|
||||||
|
for _ in 0..2 {
|
||||||
|
if let FloatTexture::FloatConstant(c_tex) = &*scale {
|
||||||
|
let cs = c_tex.value;
|
||||||
|
if cs == 1.0 {
|
||||||
|
return (*tex).clone();
|
||||||
|
} else if let FloatTexture::FloatImage(img_tex) = &*tex {
|
||||||
|
let mut image_copy = img_tex.clone();
|
||||||
|
image_copy.base.multiply_scale(cs);
|
||||||
|
return FloatTexture::FloatImage(image_copy).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mem::swap(&mut tex, &mut scale);
|
||||||
|
}
|
||||||
|
std::mem::swap(&mut tex, &mut scale);
|
||||||
|
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for FloatScaledTexture {
|
impl FloatTextureTrait for FloatScaledTexture {
|
||||||
|
|
@ -419,7 +650,39 @@ impl FloatTextureTrait for FloatScaledTexture {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatBilerpTexture;
|
pub struct FloatBilerpTexture {
|
||||||
|
mapping: TextureMapping2D,
|
||||||
|
v00: Float,
|
||||||
|
v01: Float,
|
||||||
|
v10: Float,
|
||||||
|
v11: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatBilerpTexture {
|
||||||
|
pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self {
|
||||||
|
Self {
|
||||||
|
mapping,
|
||||||
|
v00,
|
||||||
|
v01,
|
||||||
|
v10,
|
||||||
|
v11,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
render_from_texture: &Transform,
|
||||||
|
params: &TextureParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let mapping = TextureMapping2D::create(params, render_from_texture, loc);
|
||||||
|
let v00 = params.get_one_float("v00", 0.);
|
||||||
|
let v01 = params.get_one_float("v01", 1.);
|
||||||
|
let v10 = params.get_one_float("v10", 0.);
|
||||||
|
let v11 = params.get_one_float("v11", 1.);
|
||||||
|
Self::new(mapping, v00, v01, v10, v11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FloatTextureTrait for FloatBilerpTexture {}
|
impl FloatTextureTrait for FloatBilerpTexture {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -496,6 +759,13 @@ impl SpectrumTextureTrait for RGBReflectanceConstantTexture {
|
||||||
pub struct SpectrumConstantTexture {
|
pub struct SpectrumConstantTexture {
|
||||||
value: Spectrum,
|
value: Spectrum,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpectrumConstantTexture {
|
||||||
|
pub fn new(value: Spectrum) -> Self {
|
||||||
|
Self { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SpectrumTextureTrait for SpectrumConstantTexture {
|
impl SpectrumTextureTrait for SpectrumConstantTexture {
|
||||||
fn evaluate(&self, _ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
fn evaluate(&self, _ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
self.value.sample(lambda)
|
self.value.sample(lambda)
|
||||||
|
|
@ -518,7 +788,7 @@ impl SpectrumTextureTrait for SpectrumCheckerboardTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum SpectrumType {
|
pub enum SpectrumType {
|
||||||
Illuminant,
|
Illuminant,
|
||||||
Albedo,
|
Albedo,
|
||||||
56
shared/src/data.rs
Normal file
56
shared/src/data.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::Float;
|
||||||
|
use bytemuck::cast_slice;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../../data/srgb_scale.dat");
|
||||||
|
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../../data/srgb_coeffs.dat");
|
||||||
|
|
||||||
|
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES));
|
||||||
|
|
||||||
|
pub static SRGB_COEFFS: Lazy<&[Float]> =
|
||||||
|
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES);
|
||||||
|
Box::leak(v.into_boxed_slice())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../../data/dcip3_scale.dat");
|
||||||
|
static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../../data/dcip3_coeffs.dat");
|
||||||
|
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES));
|
||||||
|
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
|
||||||
|
Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
let v: Vec<Float> = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES);
|
||||||
|
Box::leak(v.into_boxed_slice())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static ACES_SCALE_BYTES: &[u8] = include_bytes!("../../data/aces_scale.dat");
|
||||||
|
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../../data/aces_coeffs.dat");
|
||||||
|
|
||||||
|
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES));
|
||||||
|
|
||||||
|
pub static ACES_COEFFS: Lazy<&[Float]> =
|
||||||
|
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
let v: Vec<Float> = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES);
|
||||||
|
Box::leak(v.into_boxed_slice())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../../data/rec2020_scale.dat");
|
||||||
|
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../../data/rec2020_coeffs.dat");
|
||||||
|
|
||||||
|
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES));
|
||||||
|
pub static REC2020_COEFFS: Lazy<&[Float]> =
|
||||||
|
Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
let v: Vec<Float> = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES);
|
||||||
|
Box::leak(v.into_boxed_slice())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::core::geometry::{Bounds2i, Point2i};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::geometry::{Bounds2i, Point2i};
|
use crate::spectra::colorspace::RGBColorSpace;
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use crate::utils::math::SquareMatrix;
|
use crate::utils::math::SquareMatrix;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
pub mod io;
|
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
pub mod pixel;
|
pub mod pixel;
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
|
||||||
use crate::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
|
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::{lerp, square};
|
||||||
use core::hash;
|
use core::hash;
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use pixel::PixelStorage;
|
use pixel::PixelStorage;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// use rayon::prelude::*;
|
// use rayon::prelude::*;
|
||||||
|
use crate::core::geometry::{Bounds2i, Point2i};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::geometry::{Bounds2i, Point2i};
|
|
||||||
use crate::image::pixel::PixelStorage;
|
use crate::image::pixel::PixelStorage;
|
||||||
use crate::image::{Image, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
use crate::image::{Image, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
||||||
use crate::utils::math::windowed_sinc;
|
use crate::utils::math::windowed_sinc;
|
||||||
|
|
@ -242,7 +242,7 @@ fn copy_rect_in_kernel<T: PixelStorage>(
|
||||||
dst: &mut [T],
|
dst: &mut [T],
|
||||||
res: Point2i,
|
res: Point2i,
|
||||||
channels: usize,
|
channels: usize,
|
||||||
enc: crate::utils::color::ColorEncoding,
|
enc: crate::spectra::color::ColorEncoding,
|
||||||
extent: Bounds2i,
|
extent: Bounds2i,
|
||||||
buf: &[Float],
|
buf: &[Float],
|
||||||
) {
|
) {
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::color::{ColorEncoding, ColorEncodingTrait};
|
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait};
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|
||||||
/// Allows writing generic algorithms that work on any image format.
|
// Allows writing generic algorithms that work on any image format.
|
||||||
pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq {
|
pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq {
|
||||||
fn from_linear(val: Float, encoding: ColorEncoding) -> Self;
|
fn from_linear(val: Float, encoding: ColorEncoding) -> Self;
|
||||||
fn to_linear(self, encoding: ColorEncoding) -> Float;
|
fn to_linear(self, encoding: ColorEncoding) -> Float;
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::camera::{Camera, CameraTrait};
|
||||||
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
||||||
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
|
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
|
||||||
use crate::core::film::{FilmTrait, VisibleSurface};
|
use crate::core::film::{FilmTrait, VisibleSurface};
|
||||||
|
use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||||
};
|
};
|
||||||
|
|
@ -14,7 +15,6 @@ use crate::core::options::get_options;
|
||||||
use crate::core::pbrt::{Float, SHADOW_EPSILON};
|
use crate::core::pbrt::{Float, SHADOW_EPSILON};
|
||||||
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
||||||
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
||||||
use crate::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
|
|
||||||
use crate::lights::sampler::{LightSampler, UniformLightSampler};
|
use crate::lights::sampler::{LightSampler, UniformLightSampler};
|
||||||
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
|
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
|
||||||
use crate::shapes::ShapeIntersection;
|
use crate::shapes::ShapeIntersection;
|
||||||
|
|
@ -228,7 +228,7 @@ impl RayIntegratorTrait for SimplePathIntegrator {
|
||||||
mut ray: Ray,
|
mut ray: Ray,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
sampler: &mut Sampler,
|
sampler: &mut Sampler,
|
||||||
scratch: &Bump,
|
_scratch: &Bump,
|
||||||
_visible_surface: bool,
|
_visible_surface: bool,
|
||||||
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
||||||
let mut l = SampledSpectrum::new(0.0);
|
let mut l = SampledSpectrum::new(0.0);
|
||||||
|
|
@ -255,8 +255,7 @@ impl RayIntegratorTrait for SimplePathIntegrator {
|
||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
|
||||||
let Some(bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), scratch, sampler)
|
let Some(bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else {
|
||||||
else {
|
|
||||||
specular_bounce = false;
|
specular_bounce = false;
|
||||||
isect.skip_intersection(&mut ray, t_hit);
|
isect.skip_intersection(&mut ray, t_hit);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -367,7 +366,7 @@ impl RayIntegratorTrait for PathIntegrator {
|
||||||
mut ray: Ray,
|
mut ray: Ray,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
sampler: &mut Sampler,
|
sampler: &mut Sampler,
|
||||||
scratch: &Bump,
|
_scratch: &Bump,
|
||||||
visible_surface: bool,
|
visible_surface: bool,
|
||||||
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
||||||
let mut l = SampledSpectrum::new(0.0);
|
let mut l = SampledSpectrum::new(0.0);
|
||||||
|
|
@ -410,9 +409,7 @@ impl RayIntegratorTrait for PathIntegrator {
|
||||||
l += beta * wl * le;
|
l += beta * wl * le;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(mut bsdf) =
|
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else {
|
||||||
isect.get_bsdf(&ray, lambda, self.camera.as_ref(), scratch, sampler)
|
|
||||||
else {
|
|
||||||
specular_bounce = true;
|
specular_bounce = true;
|
||||||
isect.skip_intersection(&mut ray, t_hit);
|
isect.skip_intersection(&mut ray, t_hit);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -544,7 +541,7 @@ impl RayIntegratorTrait for SimpleVolPathIntegrator {
|
||||||
mut ray: Ray,
|
mut ray: Ray,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
sampler: &mut Sampler,
|
sampler: &mut Sampler,
|
||||||
scratch: &Bump,
|
_scratch: &Bump,
|
||||||
_visible_surface: bool,
|
_visible_surface: bool,
|
||||||
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
||||||
let mut l = SampledSpectrum::new(0.);
|
let mut l = SampledSpectrum::new(0.);
|
||||||
|
|
@ -632,10 +629,7 @@ impl RayIntegratorTrait for SimpleVolPathIntegrator {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle surface intersection along ray path
|
// Handle surface intersection along ray path
|
||||||
if let Some(bsdf) = &hit
|
if let Some(bsdf) = &hit.intr.get_bsdf(&ray, &lambda, &self.camera, sampler) {
|
||||||
.intr
|
|
||||||
.get_bsdf(&ray, &lambda, &self.camera, scratch, sampler)
|
|
||||||
{
|
|
||||||
let uc = sampler.get1d();
|
let uc = sampler.get1d();
|
||||||
let u = sampler.get2d();
|
let u = sampler.get2d();
|
||||||
if bsdf.sample_f(-ray.d, uc, u, FArgs::default()).is_some() {
|
if bsdf.sample_f(-ray.d, uc, u, FArgs::default()).is_some() {
|
||||||
|
|
@ -993,8 +987,7 @@ impl RayIntegratorTrait for VolPathIntegrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
let isect = &mut hit.intr;
|
let isect = &mut hit.intr;
|
||||||
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, scratch, sampler)
|
let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else {
|
||||||
else {
|
|
||||||
hit.intr.skip_intersection(&mut ray, hit.t_hit());
|
hit.intr.skip_intersection(&mut ray, hit.t_hit());
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -228,7 +228,7 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
|
||||||
lu = 0.5;
|
lu = 0.5;
|
||||||
}
|
}
|
||||||
let lambda = camera.get_film().sample_wavelengths(lu);
|
let lambda = camera.get_film().sample_wavelengths(lu);
|
||||||
let mut film = camera.get_film();
|
let film = camera.get_film();
|
||||||
let filter = film.get_filter();
|
let filter = film.get_filter();
|
||||||
let camera_sample = get_camera_sample(sampler, pixel, filter);
|
let camera_sample = get_camera_sample(sampler, pixel, filter);
|
||||||
if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) {
|
if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) {
|
||||||
15
shared/src/lib.rs
Normal file
15
shared/src/lib.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#![allow(unused_imports, dead_code)]
|
||||||
|
#![feature(float_erf)]
|
||||||
|
#![feature(f16)]
|
||||||
|
|
||||||
|
mod camera;
|
||||||
|
mod core;
|
||||||
|
mod data;
|
||||||
|
mod image;
|
||||||
|
mod integrators;
|
||||||
|
mod lights;
|
||||||
|
mod shapes;
|
||||||
|
mod spectra;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub use core::pbrt::*;
|
||||||
26
shared/src/lights/diffuse.rs
Normal file
26
shared/src/lights/diffuse.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::core::geometry::*;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::spectra::*;
|
||||||
|
use crate::shapes::GpuShapeHandle;
|
||||||
|
use crate::textures::GpuFloatTextureHandle;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct DiffuseAreaLight {
|
||||||
|
pub lemit_coeffs: [Float; 32],
|
||||||
|
pub scale: Float,
|
||||||
|
pub area: Float,
|
||||||
|
pub two_sided: bool,
|
||||||
|
pub image_id: i32,
|
||||||
|
pub shape_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LightTrait for DiffuseAreaLight {
|
||||||
|
fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
if !self.two_sided && n.dot(wo) <= 0.0 {
|
||||||
|
return SampledSpectrum::new(0.0);
|
||||||
|
}
|
||||||
|
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||||
|
spec.sample(lambda) * self.scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
core::geometry::Frame,
|
||||||
core::medium::Medium,
|
core::medium::Medium,
|
||||||
geometry::Frame,
|
spectra::{RGB, RGBColorSpace, RGBIlluminantSpectrum},
|
||||||
spectra::RGBIlluminantSpectrum,
|
|
||||||
utils::{
|
utils::{
|
||||||
color::RGB,
|
math::{clamp, equal_area_square_to_sphere},
|
||||||
colorspace::RGBColorSpace,
|
|
||||||
math::equal_area_square_to_sphere,
|
|
||||||
sampling::{
|
sampling::{
|
||||||
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
|
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
|
||||||
uniform_sphere_pdf,
|
uniform_sphere_pdf,
|
||||||
|
|
@ -15,7 +13,6 @@ use crate::{
|
||||||
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use crate::core::pbrt::clamp_t;
|
|
||||||
use crate::image::{PixelFormat, WrapMode};
|
use crate::image::{PixelFormat, WrapMode};
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -24,8 +21,8 @@ use super::{
|
||||||
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBase,
|
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBase,
|
||||||
LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface,
|
LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface,
|
||||||
Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths,
|
Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths,
|
||||||
SimpleInteraction, Spectrum, Transform, Vector3f, VectorLike, equal_area_sphere_to_square,
|
SimpleInteraction, Spectrum, TransformGeneric, Vector3f, VectorLike,
|
||||||
square,
|
equal_area_sphere_to_square, square,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -38,7 +35,7 @@ pub struct InfiniteUniformLight {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InfiniteUniformLight {
|
impl InfiniteUniformLight {
|
||||||
pub fn new(render_from_light: Transform<Float>, le: Spectrum, scale: Float) -> Self {
|
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::Infinite,
|
LightType::Infinite,
|
||||||
&render_from_light,
|
&render_from_light,
|
||||||
|
|
@ -137,7 +134,7 @@ pub struct InfiniteImageLight {
|
||||||
|
|
||||||
impl InfiniteImageLight {
|
impl InfiniteImageLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: Transform<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
image: Image,
|
image: Image,
|
||||||
image_color_space: Arc<RGBColorSpace>,
|
image_color_space: Arc<RGBColorSpace>,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -333,7 +330,7 @@ impl InfinitePortalLight {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: Transform<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
equal_area_image: &Image,
|
equal_area_image: &Image,
|
||||||
image_color_space: Arc<RGBColorSpace>,
|
image_color_space: Arc<RGBColorSpace>,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -485,8 +482,8 @@ impl InfinitePortalLight {
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
Point2f::new(
|
Point2f::new(
|
||||||
clamp_t((alpha + PI / 2.0) / PI, 0.0, 1.0),
|
clamp((alpha + PI / 2.0) / PI, 0.0, 1.0),
|
||||||
clamp_t((beta + PI / 2.0) / PI, 0.0, 1.0),
|
clamp((beta + PI / 2.0) / PI, 0.0, 1.0),
|
||||||
),
|
),
|
||||||
duv_dw,
|
duv_dw,
|
||||||
))
|
))
|
||||||
|
|
@ -2,25 +2,24 @@ pub mod diffuse;
|
||||||
pub mod infinite;
|
pub mod infinite;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
|
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||||
|
Vector3f, VectorLike, cos_theta,
|
||||||
|
};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||||
};
|
};
|
||||||
use crate::core::medium::MediumInterface;
|
use crate::core::medium::MediumInterface;
|
||||||
use crate::core::pbrt::{Float, InternCache, PI};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
|
||||||
Vector3f, VectorLike, cos_theta,
|
|
||||||
};
|
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, SampledSpectrum,
|
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
|
||||||
SampledWavelengths, Spectrum, SpectrumTrait,
|
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
|
||||||
};
|
};
|
||||||
use crate::utils::color::RGB;
|
use crate::utils::containers::InternCache;
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
@ -29,6 +28,8 @@ use std::sync::{Arc, OnceLock};
|
||||||
use diffuse::DiffuseAreaLight;
|
use diffuse::DiffuseAreaLight;
|
||||||
use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||||
|
|
||||||
|
pub use sampler::{LightSampler, LightSamplerTrait};
|
||||||
|
|
||||||
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
|
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
|
||||||
|
|
||||||
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||||
|
|
@ -64,7 +65,7 @@ pub struct LightLeSample {
|
||||||
pdf_dir: Float,
|
pdf_dir: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Copy, Default, Clone)]
|
||||||
pub struct LightSampleContext {
|
pub struct LightSampleContext {
|
||||||
pub pi: Point3fi,
|
pub pi: Point3fi,
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
|
|
@ -147,14 +148,14 @@ impl LightLiSample {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LightBase {
|
pub struct LightBase {
|
||||||
pub light_type: LightType,
|
pub light_type: LightType,
|
||||||
pub render_from_light: Transform<Float>,
|
pub render_from_light: TransformGeneric<Float>,
|
||||||
pub medium_interface: MediumInterface,
|
pub medium_interface: MediumInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightBase {
|
impl LightBase {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
light_type: LightType,
|
light_type: LightType,
|
||||||
render_from_light: &Transform<Float>,
|
render_from_light: &TransformGeneric<Float>,
|
||||||
medium_interface: &MediumInterface,
|
medium_interface: &MediumInterface,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -185,7 +186,7 @@ impl LightBase {
|
||||||
|
|
||||||
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
||||||
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
||||||
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s, LAMBDA_MIN, LAMBDA_MAX);
|
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
|
||||||
cache.lookup(dense_spectrum)
|
cache.lookup(dense_spectrum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +355,7 @@ pub struct DistantLight {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DistantLight {
|
impl DistantLight {
|
||||||
pub fn new(render_from_light: &Transform<Float>, lemit: Spectrum, scale: Float) -> Self {
|
pub fn new(render_from_light: &TransformGeneric<Float>, lemit: Spectrum, scale: Float) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
LightType::DeltaDirection,
|
LightType::DeltaDirection,
|
||||||
render_from_light,
|
render_from_light,
|
||||||
|
|
@ -450,7 +451,7 @@ pub struct GoniometricLight {
|
||||||
|
|
||||||
impl GoniometricLight {
|
impl GoniometricLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: &Transform<Float>,
|
render_from_light: &TransformGeneric<Float>,
|
||||||
medium_interface: &MediumInterface,
|
medium_interface: &MediumInterface,
|
||||||
iemit: Spectrum,
|
iemit: Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -545,7 +546,7 @@ pub struct PointLight {
|
||||||
|
|
||||||
impl PointLight {
|
impl PointLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: Transform<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
i: &Spectrum,
|
i: &Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -643,15 +644,15 @@ pub struct ProjectionLight {
|
||||||
scale: Float,
|
scale: Float,
|
||||||
screen_bounds: Bounds2f,
|
screen_bounds: Bounds2f,
|
||||||
hither: Float,
|
hither: Float,
|
||||||
screen_from_light: Transform<Float>,
|
screen_from_light: TransformGeneric<Float>,
|
||||||
light_from_screen: Transform<Float>,
|
light_from_screen: TransformGeneric<Float>,
|
||||||
a: Float,
|
a: Float,
|
||||||
distrib: PiecewiseConstant2D,
|
distrib: PiecewiseConstant2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectionLight {
|
impl ProjectionLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: Transform<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
image: Image,
|
image: Image,
|
||||||
image_color_space: Arc<RGBColorSpace>,
|
image_color_space: Arc<RGBColorSpace>,
|
||||||
|
|
@ -674,7 +675,7 @@ impl ProjectionLight {
|
||||||
};
|
};
|
||||||
|
|
||||||
let hither = 1e-3;
|
let hither = 1e-3;
|
||||||
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
|
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
|
||||||
let light_from_screen = screen_from_light.inverse();
|
let light_from_screen = screen_from_light.inverse();
|
||||||
let opposite = (radians(fov) / 2.).tan();
|
let opposite = (radians(fov) / 2.).tan();
|
||||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||||
|
|
@ -803,7 +804,7 @@ pub struct SpotLight {
|
||||||
|
|
||||||
impl SpotLight {
|
impl SpotLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: &Transform<Float>,
|
render_from_light: &TransformGeneric<Float>,
|
||||||
medium_interface: &MediumInterface,
|
medium_interface: &MediumInterface,
|
||||||
iemit: Spectrum,
|
iemit: Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
@ -2,13 +2,13 @@ use super::{
|
||||||
Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f,
|
Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f,
|
||||||
SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square,
|
SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square,
|
||||||
};
|
};
|
||||||
use crate::geometry::primitives::OctahedralVector;
|
use crate::core::geometry::primitives::OctahedralVector;
|
||||||
use crate::geometry::{DirectionCone, Normal};
|
use crate::core::geometry::{DirectionCone, Normal};
|
||||||
use crate::utils::math::sample_discrete;
|
use crate::utils::math::{clamp, lerp, sample_discrete};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::pbrt::{ONE_MINUS_EPSILON, clamp_t, lerp};
|
use crate::core::pbrt::ONE_MINUS_EPSILON;
|
||||||
use crate::utils::sampling::AliasTable;
|
use crate::utils::sampling::AliasTable;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ impl CompactLightBounds {
|
||||||
if min == max {
|
if min == max {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
65535.0 * clamp_t((c - min) / (max - min), 0.0, 1.0)
|
65535.0 * clamp((c - min) / (max - min), 0.0, 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
use super::{
|
use super::{
|
||||||
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f,
|
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f,
|
||||||
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
ShapeTrait, SurfaceInteraction, Vector3f,
|
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
||||||
};
|
};
|
||||||
|
use crate::core::geometry::{Tuple, VectorLike, spherical_quad_area};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::pbrt::{Float, clamp_t, gamma, lerp};
|
use crate::core::pbrt::{Float, gamma};
|
||||||
use crate::geometry::{Tuple, VectorLike, spherical_quad_area};
|
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
|
||||||
use crate::utils::math::{SquareMatrix, difference_of_products, quadratic};
|
|
||||||
use crate::utils::mesh::BilinearPatchMesh;
|
use crate::utils::mesh::BilinearPatchMesh;
|
||||||
use crate::utils::sampling::{
|
use crate::utils::sampling::{
|
||||||
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
|
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
|
||||||
};
|
};
|
||||||
use crate::utils::transform::Transform;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
|
@ -228,7 +227,7 @@ impl BilinearPatchShape {
|
||||||
time: Float,
|
time: Float,
|
||||||
wo: Vector3f,
|
wo: Vector3f,
|
||||||
) -> SurfaceInteraction {
|
) -> SurfaceInteraction {
|
||||||
// Base geometry and derivatives
|
// Base geom and derivatives
|
||||||
let p = lerp(
|
let p = lerp(
|
||||||
uv[0],
|
uv[0],
|
||||||
lerp(uv[1], data.p00, data.p01),
|
lerp(uv[1], data.p00, data.p01),
|
||||||
|
|
@ -379,7 +378,7 @@ impl BilinearPatchShape {
|
||||||
}
|
}
|
||||||
let r = Transform::rotate_from_to(isect.n().normalize().into(), ns.into());
|
let r = Transform::rotate_from_to(isect.n().normalize().into(), ns.into());
|
||||||
|
|
||||||
isect.set_shading_geometry(
|
isect.set_shading_geom(
|
||||||
ns,
|
ns,
|
||||||
r.apply_to_vector(isect.dpdu),
|
r.apply_to_vector(isect.dpdu),
|
||||||
r.apply_to_vector(isect.dpdv),
|
r.apply_to_vector(isect.dpdv),
|
||||||
|
|
@ -505,10 +504,12 @@ impl BilinearPatchShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeTrait for BilinearPatchShape {
|
impl ShapeTrait for BilinearPatchShape {
|
||||||
|
#[inline]
|
||||||
fn area(&self) -> Float {
|
fn area(&self) -> Float {
|
||||||
self.area
|
self.area
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn normal_bounds(&self) -> DirectionCone {
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
if data.p00 == data.p10
|
if data.p00 == data.p10
|
||||||
|
|
@ -553,14 +554,16 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
.min(n_avg.dot(n10))
|
.min(n_avg.dot(n10))
|
||||||
.min(n_avg.dot(n01))
|
.min(n_avg.dot(n01))
|
||||||
.min(n_avg.dot(n11));
|
.min(n_avg.dot(n11));
|
||||||
DirectionCone::new(n_avg.into(), clamp_t(cos_theta, -1., 1.))
|
DirectionCone::new(n_avg.into(), clamp(cos_theta, -1., 1.))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn bounds(&self) -> Bounds3f {
|
fn bounds(&self) -> Bounds3f {
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11))
|
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
let t_max_val = t_max?;
|
let t_max_val = t_max?;
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
|
|
@ -576,6 +579,7 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
let t_max_val = t_max.unwrap_or(Float::INFINITY);
|
let t_max_val = t_max.unwrap_or(Float::INFINITY);
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
|
|
@ -583,6 +587,7 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
// Sample bilinear patch parametric coordinate (u, v)
|
// Sample bilinear patch parametric coordinate (u, v)
|
||||||
|
|
@ -615,6 +620,7 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
let data = self.get_data();
|
let data = self.get_data();
|
||||||
let v00 = (data.p00 - ctx.p()).normalize();
|
let v00 = (data.p00 - ctx.p()).normalize();
|
||||||
|
|
@ -632,6 +638,7 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn pdf(&self, intr: &Interaction) -> Float {
|
fn pdf(&self, intr: &Interaction) -> Float {
|
||||||
let Interaction::Surface(si) = intr else {
|
let Interaction::Surface(si) = intr else {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|
@ -663,6 +670,7 @@ impl ShapeTrait for BilinearPatchShape {
|
||||||
if cross == 0. { 0. } else { param_pdf / cross }
|
if cross == 0. { 0. } else { param_pdf / cross }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
let ray = ctx.spawn_ray(wi);
|
let ray = ctx.spawn_ray(wi);
|
||||||
let Some(isect) = self.intersect(&ray, None) else {
|
let Some(isect) = self.intersect(&ray, None) else {
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::pbrt::{clamp_t, lerp};
|
use crate::utils::math::{clamp, lerp, square};
|
||||||
use crate::utils::math::square;
|
|
||||||
use crate::utils::splines::{
|
use crate::utils::splines::{
|
||||||
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
|
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
|
||||||
};
|
};
|
||||||
|
|
@ -15,7 +14,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
struct IntersectionContext {
|
struct IntersectionContext {
|
||||||
ray: Ray,
|
ray: Ray,
|
||||||
object_from_ray: Arc<Transform<Float>>,
|
object_from_ray: Arc<Transform>,
|
||||||
common: CurveCommon,
|
common: CurveCommon,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +40,7 @@ impl CurveShape {
|
||||||
(dx, _) = ray.d.coordinate_system();
|
(dx, _) = ray.d.coordinate_system();
|
||||||
}
|
}
|
||||||
|
|
||||||
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx.into()).expect("Inversion error");
|
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
|
||||||
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
||||||
|
|
||||||
// Test ray against bound of projected control points
|
// Test ray against bound of projected control points
|
||||||
|
|
@ -71,7 +70,7 @@ impl CurveShape {
|
||||||
let max_depth = if l0 > 0. {
|
let max_depth = if l0 > 0. {
|
||||||
let eps = self.common.width[0].max(self.common.width[1]) * 0.05;
|
let eps = self.common.width[0].max(self.common.width[1]) * 0.05;
|
||||||
let r0: i32 = (1.41421356237 * 6. * l0 / (8. * eps)).log2() as i32 / 2;
|
let r0: i32 = (1.41421356237 * 6. * l0 / (8. * eps)).log2() as i32 / 2;
|
||||||
clamp_t(r0, 0, 10)
|
clamp(r0, 0, 10)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
@ -152,7 +151,7 @@ impl CurveShape {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let w = Vector2f::new(cp[0].x(), cp[0].y()).dot(-segment_dir) / denom;
|
let w = Vector2f::new(cp[0].x(), cp[0].y()).dot(-segment_dir) / denom;
|
||||||
let u = clamp_t(lerp(w, u0, u1), u0, u1);
|
let u = clamp(lerp(w, u0, u1), u0, u1);
|
||||||
let ray_length = context.ray.d.norm();
|
let ray_length = context.ray.d.norm();
|
||||||
|
|
||||||
let mut hit_width = lerp(u, self.common.width[0], self.common.width[1]);
|
let mut hit_width = lerp(u, self.common.width[0], self.common.width[1]);
|
||||||
|
|
@ -169,7 +168,7 @@ impl CurveShape {
|
||||||
hit_width = hit_width * n_hit.dot(context.ray.d.into()).abs() / ray_length;
|
hit_width = hit_width * n_hit.dot(context.ray.d.into()).abs() / ray_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (pc, dpcdw) = evaluate_cubic_bezier(cp, clamp_t(w, 0., 1.));
|
let (pc, dpcdw) = evaluate_cubic_bezier(cp, clamp(w, 0., 1.));
|
||||||
let ray_length = context.ray.d.norm();
|
let ray_length = context.ray.d.norm();
|
||||||
|
|
||||||
if !self.valid_hit(pc, hit_width, t_max, ray_length) {
|
if !self.valid_hit(pc, hit_width, t_max, ray_length) {
|
||||||
|
|
@ -3,18 +3,18 @@ use super::{
|
||||||
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||||
};
|
};
|
||||||
|
use crate::core::geometry::{Sqrt, Tuple, VectorLike};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::pbrt::{gamma, lerp};
|
use crate::core::pbrt::gamma;
|
||||||
use crate::geometry::{Sqrt, Tuple, VectorLike};
|
|
||||||
use crate::utils::interval::Interval;
|
use crate::utils::interval::Interval;
|
||||||
use crate::utils::math::{difference_of_products, square};
|
use crate::utils::math::{difference_of_products, lerp, square};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl CylinderShape {
|
impl CylinderShape {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
z_min: Float,
|
z_min: Float,
|
||||||
|
|
@ -3,8 +3,8 @@ use super::{
|
||||||
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
||||||
};
|
};
|
||||||
|
use crate::core::geometry::VectorLike;
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::geometry::VectorLike;
|
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::square;
|
||||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -15,8 +15,8 @@ impl DiskShape {
|
||||||
inner_radius: Float,
|
inner_radius: Float,
|
||||||
height: Float,
|
height: Float,
|
||||||
phi_max: Float,
|
phi_max: Float,
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -5,16 +5,16 @@ pub mod disk;
|
||||||
pub mod sphere;
|
pub mod sphere;
|
||||||
pub mod triangle;
|
pub mod triangle;
|
||||||
|
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||||
|
Vector3fi, VectorLike,
|
||||||
|
};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||||
};
|
};
|
||||||
use crate::core::material::Material;
|
use crate::core::material::Material;
|
||||||
use crate::core::medium::{Medium, MediumInterface};
|
use crate::core::medium::{Medium, MediumInterface};
|
||||||
use crate::core::pbrt::{Float, PI};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::geometry::{
|
|
||||||
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
|
||||||
Vector3fi, VectorLike,
|
|
||||||
};
|
|
||||||
use crate::lights::Light;
|
use crate::lights::Light;
|
||||||
use crate::utils::math::{next_float_down, next_float_up};
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::Transform;
|
||||||
|
|
@ -29,8 +29,8 @@ pub struct SphereShape {
|
||||||
theta_z_min: Float,
|
theta_z_min: Float,
|
||||||
theta_z_max: Float,
|
theta_z_max: Float,
|
||||||
phi_max: Float,
|
phi_max: Float,
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
transform_swap_handedness: bool,
|
transform_swap_handedness: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -55,8 +55,8 @@ pub struct CylinderShape {
|
||||||
z_min: Float,
|
z_min: Float,
|
||||||
z_max: Float,
|
z_max: Float,
|
||||||
phi_max: Float,
|
phi_max: Float,
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
transform_swap_handedness: bool,
|
transform_swap_handedness: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -67,8 +67,8 @@ pub struct DiskShape {
|
||||||
inner_radius: Float,
|
inner_radius: Float,
|
||||||
height: Float,
|
height: Float,
|
||||||
phi_max: Float,
|
phi_max: Float,
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
transform_swap_handedness: bool,
|
transform_swap_handedness: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +102,8 @@ pub struct CurveCommon {
|
||||||
n: [Normal3f; 2],
|
n: [Normal3f; 2],
|
||||||
normal_angle: Float,
|
normal_angle: Float,
|
||||||
inv_sin_normal_angle: Float,
|
inv_sin_normal_angle: Float,
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
transform_swap_handedness: bool,
|
transform_swap_handedness: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -116,8 +116,8 @@ impl CurveCommon {
|
||||||
w1: Float,
|
w1: Float,
|
||||||
curve_type: CurveType,
|
curve_type: CurveType,
|
||||||
norm: &[Vector3f],
|
norm: &[Vector3f],
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let transform_swap_handedness = render_from_object.swaps_handedness();
|
let transform_swap_handedness = render_from_object.swaps_handedness();
|
||||||
|
|
@ -3,11 +3,11 @@ use super::{
|
||||||
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
||||||
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||||
};
|
};
|
||||||
|
use crate::core::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::pbrt::{clamp_t, gamma};
|
use crate::core::pbrt::gamma;
|
||||||
use crate::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
|
|
||||||
use crate::utils::interval::Interval;
|
use crate::utils::interval::Interval;
|
||||||
use crate::utils::math::{difference_of_products, radians, safe_acos, safe_sqrt, square};
|
use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
|
||||||
use crate::utils::sampling::sample_uniform_sphere;
|
use crate::utils::sampling::sample_uniform_sphere;
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
@ -15,23 +15,23 @@ use std::sync::Arc;
|
||||||
|
|
||||||
impl SphereShape {
|
impl SphereShape {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_object: Arc<Transform<Float>>,
|
render_from_object: Arc<Transform>,
|
||||||
object_from_render: Arc<Transform<Float>>,
|
object_from_render: Arc<Transform>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
z_min: Float,
|
z_min: Float,
|
||||||
z_max: Float,
|
z_max: Float,
|
||||||
phi_max: Float,
|
phi_max: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let theta_z_min = clamp_t(z_min.min(z_max) / radius, -1., 1.).acos();
|
let theta_z_min = clamp(z_min.min(z_max) / radius, -1., 1.).acos();
|
||||||
let theta_z_max = clamp_t(z_max.min(z_max) / radius, -1., 1.).acos();
|
let theta_z_max = clamp(z_max.min(z_max) / radius, -1., 1.).acos();
|
||||||
let phi_max = radians(clamp_t(phi_max, 0., 360.0));
|
let phi_max = radians(clamp(phi_max, 0., 360.0));
|
||||||
Self {
|
Self {
|
||||||
render_from_object: render_from_object.clone(),
|
render_from_object: render_from_object.clone(),
|
||||||
object_from_render: object_from_render.clone(),
|
object_from_render: object_from_render.clone(),
|
||||||
radius,
|
radius,
|
||||||
z_min: clamp_t(z_min.min(z_max), -radius, radius),
|
z_min: clamp(z_min.min(z_max), -radius, radius),
|
||||||
z_max: clamp_t(z_min.max(z_max), -radius, radius),
|
z_max: clamp(z_min.max(z_max), -radius, radius),
|
||||||
theta_z_max,
|
theta_z_max,
|
||||||
theta_z_min,
|
theta_z_min,
|
||||||
phi_max,
|
phi_max,
|
||||||
|
|
@ -3,9 +3,9 @@ use super::{
|
||||||
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
|
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
|
||||||
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
|
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
|
||||||
};
|
};
|
||||||
|
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::pbrt::gamma;
|
use crate::core::pbrt::gamma;
|
||||||
use crate::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
|
|
||||||
use crate::utils::math::{difference_of_products, square};
|
use crate::utils::math::{difference_of_products, square};
|
||||||
use crate::utils::mesh::TriangleMesh;
|
use crate::utils::mesh::TriangleMesh;
|
||||||
use crate::utils::sampling::{
|
use crate::utils::sampling::{
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::Float;
|
||||||
|
|
||||||
pub const CIE_SAMPLES: usize = 471;
|
pub const CIE_SAMPLES: usize = 471;
|
||||||
pub const CIE_X: [Float; CIE_SAMPLES] = [
|
pub const CIE_X: [Float; CIE_SAMPLES] = [
|
||||||
|
|
@ -1484,7 +1484,7 @@ pub const CIE_ILLUM_A: [Float; 214] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// CIE Illuminant D S basis functions
|
// CIE Illuminant D S basis functions
|
||||||
const N_CIES: usize = 107;
|
pub const N_CIES: usize = 107;
|
||||||
|
|
||||||
pub const CIE_S_LAMBDA: [Float; N_CIES] = [
|
pub const CIE_S_LAMBDA: [Float; N_CIES] = [
|
||||||
300.000000, 305.000000, 310.000000, 315.000000, 320.000000, 325.000000, 330.000000, 335.000000,
|
300.000000, 305.000000, 310.000000, 315.000000, 320.000000, 325.000000, 330.000000, 335.000000,
|
||||||
|
|
@ -1687,6 +1687,30 @@ pub const CIE_ILLUM_F2: [Float; 162] = [
|
||||||
780.000000, 0.210000,
|
780.000000, 0.210000,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const CIE_ILLUM_F3: [Float; 162] = [
|
||||||
|
380.000000, 0.820000, 385.000000, 1.020000, 390.000000, 1.260000, 395.000000, 1.440000,
|
||||||
|
400.000000, 2.570000, 405.000000, 14.360000, 410.000000, 2.700000, 415.000000, 2.450000,
|
||||||
|
420.000000, 2.730000, 425.000000, 3.000000, 430.000000, 3.280000, 435.000000, 31.850000,
|
||||||
|
440.000000, 9.470000, 445.000000, 4.020000, 450.000000, 4.250000, 455.000000, 4.440000,
|
||||||
|
460.000000, 4.590000, 465.000000, 4.720000, 470.000000, 4.800000, 475.000000, 4.860000,
|
||||||
|
480.000000, 4.870000, 485.000000, 4.850000, 490.000000, 4.880000, 495.000000, 4.770000,
|
||||||
|
500.000000, 4.670000, 505.000000, 4.620000, 510.000000, 4.620000, 515.000000, 4.730000,
|
||||||
|
520.000000, 4.990000, 525.000000, 5.480000, 530.000000, 6.250000, 535.000000, 7.340000,
|
||||||
|
540.000000, 8.780000, 545.000000, 23.820000, 550.000000, 16.139999, 555.000000, 14.590000,
|
||||||
|
560.000000, 16.629999, 565.000000, 18.490000, 570.000000, 19.950001, 575.000000, 23.110001,
|
||||||
|
580.000000, 24.690001, 585.000000, 21.410000, 590.000000, 20.850000, 595.000000, 19.930000,
|
||||||
|
600.000000, 18.670000, 605.000000, 17.219999, 610.000000, 15.650000, 615.000000, 14.040000,
|
||||||
|
620.000000, 12.450000, 625.000000, 10.950000, 630.000000, 9.510000, 635.000000, 8.270000,
|
||||||
|
640.000000, 7.110000, 645.000000, 6.090000, 650.000000, 5.220000, 655.000000, 4.450000,
|
||||||
|
660.000000, 3.800000, 665.000000, 3.230000, 670.000000, 2.750000, 675.000000, 2.330000,
|
||||||
|
680.000000, 1.990000, 685.000000, 1.700000, 690.000000, 1.550000, 695.000000, 1.270000,
|
||||||
|
700.000000, 1.090000, 705.000000, 0.960000, 710.000000, 0.830000, 715.000000, 0.710000,
|
||||||
|
720.000000, 0.620000, 725.000000, 0.540000, 730.000000, 0.490000, 735.000000, 0.460000,
|
||||||
|
740.000000, 0.430000, 745.000000, 0.390000, 750.000000, 0.390000, 755.000000, 0.350000,
|
||||||
|
760.000000, 0.380000, 765.000000, 0.390000, 770.000000, 0.330000, 775.000000, 0.280000,
|
||||||
|
780.000000, 0.210000,
|
||||||
|
];
|
||||||
|
|
||||||
pub const CIE_ILLUM_F4: [Float; 162] = [
|
pub const CIE_ILLUM_F4: [Float; 162] = [
|
||||||
380.000000, 0.570000, 385.000000, 0.700000, 390.000000, 0.870000, 395.000000, 0.980000,
|
380.000000, 0.570000, 385.000000, 0.700000, 390.000000, 0.870000, 395.000000, 0.980000,
|
||||||
400.000000, 2.010000, 405.000000, 13.750000, 410.000000, 1.950000, 415.000000, 1.590000,
|
400.000000, 2.010000, 405.000000, 13.750000, 410.000000, 1.950000, 415.000000, 1.590000,
|
||||||
|
|
@ -1920,7 +1944,7 @@ pub const AG_ETA: [Float; 112] = [
|
||||||
799.897949, 0.142563, 826.561157, 0.145000, 855.063293, 0.151938, 885.601257, 0.163000,
|
799.897949, 0.142563, 826.561157, 0.145000, 855.063293, 0.151938, 885.601257, 0.163000,
|
||||||
];
|
];
|
||||||
|
|
||||||
const AG_K: [Float; 112] = [
|
pub const AG_K: [Float; 112] = [
|
||||||
298.757050, 1.080000, 302.400421, 0.882000, 306.133759, 0.761063, 309.960449, 0.647000,
|
298.757050, 1.080000, 302.400421, 0.882000, 306.133759, 0.761063, 309.960449, 0.647000,
|
||||||
313.884003, 0.550875, 317.908142, 0.504000, 322.036835, 0.554375, 326.274139, 0.663000,
|
313.884003, 0.550875, 317.908142, 0.504000, 322.036835, 0.554375, 326.274139, 0.663000,
|
||||||
330.624481, 0.818563, 335.092377, 0.986000, 339.682678, 1.120687, 344.400482, 1.240000,
|
330.624481, 0.818563, 335.092377, 0.986000, 339.682678, 1.120687, 344.400482, 1.240000,
|
||||||
|
|
@ -2116,7 +2140,7 @@ pub const TIO2_K: [Float; 68] = [
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Data for various types of glass (refractive index eta vs. wavelength in nm).
|
// Data for various types of glass (refractive index eta vs. wavelength in nm).
|
||||||
pub const GLASSBK7_ETA: [f32; 58] = [
|
pub const GLASS_BK7_ETA: [f32; 58] = [
|
||||||
300.0,
|
300.0,
|
||||||
1.5527702635739,
|
1.5527702635739,
|
||||||
322.0,
|
322.0,
|
||||||
|
|
@ -6184,3 +6208,199 @@ pub const SONY_ILCE_9_B: [f32; 70] = [
|
||||||
720.0,
|
720.0,
|
||||||
0.0005996684000000017,
|
0.0005996684000000017,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static N_SWATCHES_REFLECTANCES: usize = 24;
|
||||||
|
pub static SWATCHES_RAW: &[&[Float]] = &[
|
||||||
|
&[
|
||||||
|
380.0, 0.055, 390.0, 0.058, 400.0, 0.061, 410.0, 0.062, 420.0, 0.062, 430.0, 0.062, 440.0,
|
||||||
|
0.062, 450.0, 0.062, 460.0, 0.062, 470.0, 0.062, 480.0, 0.062, 490.0, 0.063, 500.0, 0.065,
|
||||||
|
510.0, 0.070, 520.0, 0.076, 530.0, 0.079, 540.0, 0.081, 550.0, 0.084, 560.0, 0.091, 570.0,
|
||||||
|
0.103, 580.0, 0.119, 590.0, 0.134, 600.0, 0.143, 610.0, 0.147, 620.0, 0.151, 630.0, 0.158,
|
||||||
|
640.0, 0.168, 650.0, 0.179, 660.0, 0.188, 670.0, 0.190, 680.0, 0.186, 690.0, 0.181, 700.0,
|
||||||
|
0.182, 710.0, 0.187, 720.0, 0.196, 730.0, 0.209,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.117, 390.0, 0.143, 400.0, 0.175, 410.0, 0.191, 420.0, 0.196, 430.0, 0.199, 440.0,
|
||||||
|
0.204, 450.0, 0.213, 460.0, 0.228, 470.0, 0.251, 480.0, 0.280, 490.0, 0.309, 500.0, 0.329,
|
||||||
|
510.0, 0.333, 520.0, 0.315, 530.0, 0.286, 540.0, 0.273, 550.0, 0.276, 560.0, 0.277, 570.0,
|
||||||
|
0.289, 580.0, 0.339, 590.0, 0.420, 600.0, 0.488, 610.0, 0.525, 620.0, 0.546, 630.0, 0.562,
|
||||||
|
640.0, 0.578, 650.0, 0.595, 660.0, 0.612, 670.0, 0.625, 680.0, 0.638, 690.0, 0.656, 700.0,
|
||||||
|
0.678, 710.0, 0.700, 720.0, 0.717, 730.0, 0.734,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.130, 390.0, 0.177, 400.0, 0.251, 410.0, 0.306, 420.0, 0.324, 430.0, 0.330, 440.0,
|
||||||
|
0.333, 450.0, 0.331, 460.0, 0.323, 470.0, 0.311, 480.0, 0.298, 490.0, 0.285, 500.0, 0.269,
|
||||||
|
510.0, 0.250, 520.0, 0.231, 530.0, 0.214, 540.0, 0.199, 550.0, 0.185, 560.0, 0.169, 570.0,
|
||||||
|
0.157, 580.0, 0.149, 590.0, 0.145, 600.0, 0.142, 610.0, 0.141, 620.0, 0.141, 630.0, 0.141,
|
||||||
|
640.0, 0.143, 650.0, 0.147, 660.0, 0.152, 670.0, 0.154, 680.0, 0.150, 690.0, 0.144, 700.0,
|
||||||
|
0.136, 710.0, 0.132, 720.0, 0.135, 730.0, 0.147,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.051, 390.0, 0.054, 400.0, 0.056, 410.0, 0.057, 420.0, 0.058, 430.0, 0.059, 440.0,
|
||||||
|
0.060, 450.0, 0.061, 460.0, 0.062, 470.0, 0.063, 480.0, 0.065, 490.0, 0.067, 500.0, 0.075,
|
||||||
|
510.0, 0.101, 520.0, 0.145, 530.0, 0.178, 540.0, 0.184, 550.0, 0.170, 560.0, 0.149, 570.0,
|
||||||
|
0.133, 580.0, 0.122, 590.0, 0.115, 600.0, 0.109, 610.0, 0.105, 620.0, 0.104, 630.0, 0.106,
|
||||||
|
640.0, 0.109, 650.0, 0.112, 660.0, 0.114, 670.0, 0.114, 680.0, 0.112, 690.0, 0.112, 700.0,
|
||||||
|
0.115, 710.0, 0.120, 720.0, 0.125, 730.0, 0.130,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.144, 390.0, 0.198, 400.0, 0.294, 410.0, 0.375, 420.0, 0.408, 430.0, 0.421, 440.0,
|
||||||
|
0.426, 450.0, 0.426, 460.0, 0.419, 470.0, 0.403, 480.0, 0.379, 490.0, 0.346, 500.0, 0.311,
|
||||||
|
510.0, 0.281, 520.0, 0.254, 530.0, 0.229, 540.0, 0.214, 550.0, 0.208, 560.0, 0.202, 570.0,
|
||||||
|
0.194, 580.0, 0.193, 590.0, 0.200, 600.0, 0.214, 610.0, 0.230, 620.0, 0.241, 630.0, 0.254,
|
||||||
|
640.0, 0.279, 650.0, 0.313, 660.0, 0.348, 670.0, 0.366, 680.0, 0.366, 690.0, 0.359, 700.0,
|
||||||
|
0.358, 710.0, 0.365, 720.0, 0.377, 730.0, 0.398,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.136, 390.0, 0.179, 400.0, 0.247, 410.0, 0.297, 420.0, 0.320, 430.0, 0.337, 440.0,
|
||||||
|
0.355, 450.0, 0.381, 460.0, 0.419, 470.0, 0.466, 480.0, 0.510, 490.0, 0.546, 500.0, 0.567,
|
||||||
|
510.0, 0.574, 520.0, 0.569, 530.0, 0.551, 540.0, 0.524, 550.0, 0.488, 560.0, 0.445, 570.0,
|
||||||
|
0.400, 580.0, 0.350, 590.0, 0.299, 600.0, 0.252, 610.0, 0.221, 620.0, 0.204, 630.0, 0.196,
|
||||||
|
640.0, 0.191, 650.0, 0.188, 660.0, 0.191, 670.0, 0.199, 680.0, 0.212, 690.0, 0.223, 700.0,
|
||||||
|
0.232, 710.0, 0.233, 720.0, 0.229, 730.0, 0.229,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.054, 390.0, 0.054, 400.0, 0.053, 410.0, 0.054, 420.0, 0.054, 430.0, 0.055, 440.0,
|
||||||
|
0.055, 450.0, 0.055, 460.0, 0.056, 470.0, 0.057, 480.0, 0.058, 490.0, 0.061, 500.0, 0.068,
|
||||||
|
510.0, 0.089, 520.0, 0.125, 530.0, 0.154, 540.0, 0.174, 550.0, 0.199, 560.0, 0.248, 570.0,
|
||||||
|
0.335, 580.0, 0.444, 590.0, 0.538, 600.0, 0.587, 610.0, 0.595, 620.0, 0.591, 630.0, 0.587,
|
||||||
|
640.0, 0.584, 650.0, 0.584, 660.0, 0.590, 670.0, 0.603, 680.0, 0.620, 690.0, 0.639, 700.0,
|
||||||
|
0.655, 710.0, 0.663, 720.0, 0.663, 730.0, 0.667,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.122, 390.0, 0.164, 400.0, 0.229, 410.0, 0.286, 420.0, 0.327, 430.0, 0.361, 440.0,
|
||||||
|
0.388, 450.0, 0.400, 460.0, 0.392, 470.0, 0.362, 480.0, 0.316, 490.0, 0.260, 500.0, 0.209,
|
||||||
|
510.0, 0.168, 520.0, 0.138, 530.0, 0.117, 540.0, 0.104, 550.0, 0.096, 560.0, 0.090, 570.0,
|
||||||
|
0.086, 580.0, 0.084, 590.0, 0.084, 600.0, 0.084, 610.0, 0.084, 620.0, 0.084, 630.0, 0.085,
|
||||||
|
640.0, 0.090, 650.0, 0.098, 660.0, 0.109, 670.0, 0.123, 680.0, 0.143, 690.0, 0.169, 700.0,
|
||||||
|
0.205, 710.0, 0.244, 720.0, 0.287, 730.0, 0.332,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.096, 390.0, 0.115, 400.0, 0.131, 410.0, 0.135, 420.0, 0.133, 430.0, 0.132, 440.0,
|
||||||
|
0.130, 450.0, 0.128, 460.0, 0.125, 470.0, 0.120, 480.0, 0.115, 490.0, 0.110, 500.0, 0.105,
|
||||||
|
510.0, 0.100, 520.0, 0.095, 530.0, 0.093, 540.0, 0.092, 550.0, 0.093, 560.0, 0.096, 570.0,
|
||||||
|
0.108, 580.0, 0.156, 590.0, 0.265, 600.0, 0.399, 610.0, 0.500, 620.0, 0.556, 630.0, 0.579,
|
||||||
|
640.0, 0.588, 650.0, 0.591, 660.0, 0.593, 670.0, 0.594, 680.0, 0.598, 690.0, 0.602, 700.0,
|
||||||
|
0.607, 710.0, 0.609, 720.0, 0.609, 730.0, 0.610,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.092, 390.0, 0.116, 400.0, 0.146, 410.0, 0.169, 420.0, 0.178, 430.0, 0.173, 440.0,
|
||||||
|
0.158, 450.0, 0.139, 460.0, 0.119, 470.0, 0.101, 480.0, 0.087, 490.0, 0.075, 500.0, 0.066,
|
||||||
|
510.0, 0.060, 520.0, 0.056, 530.0, 0.053, 540.0, 0.051, 550.0, 0.051, 560.0, 0.052, 570.0,
|
||||||
|
0.052, 580.0, 0.051, 590.0, 0.052, 600.0, 0.058, 610.0, 0.073, 620.0, 0.096, 630.0, 0.119,
|
||||||
|
640.0, 0.141, 650.0, 0.166, 660.0, 0.194, 670.0, 0.227, 680.0, 0.265, 690.0, 0.309, 700.0,
|
||||||
|
0.355, 710.0, 0.396, 720.0, 0.436, 730.0, 0.478,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.061, 390.0, 0.061, 400.0, 0.062, 410.0, 0.063, 420.0, 0.064, 430.0, 0.066, 440.0,
|
||||||
|
0.069, 450.0, 0.075, 460.0, 0.085, 470.0, 0.105, 480.0, 0.139, 490.0, 0.192, 500.0, 0.271,
|
||||||
|
510.0, 0.376, 520.0, 0.476, 530.0, 0.531, 540.0, 0.549, 550.0, 0.546, 560.0, 0.528, 570.0,
|
||||||
|
0.504, 580.0, 0.471, 590.0, 0.428, 600.0, 0.381, 610.0, 0.347, 620.0, 0.327, 630.0, 0.318,
|
||||||
|
640.0, 0.312, 650.0, 0.310, 660.0, 0.314, 670.0, 0.327, 680.0, 0.345, 690.0, 0.363, 700.0,
|
||||||
|
0.376, 710.0, 0.381, 720.0, 0.378, 730.0, 0.379,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.063, 390.0, 0.063, 400.0, 0.063, 410.0, 0.064, 420.0, 0.064, 430.0, 0.064, 440.0,
|
||||||
|
0.065, 450.0, 0.066, 460.0, 0.067, 470.0, 0.068, 480.0, 0.071, 490.0, 0.076, 500.0, 0.087,
|
||||||
|
510.0, 0.125, 520.0, 0.206, 530.0, 0.305, 540.0, 0.383, 550.0, 0.431, 560.0, 0.469, 570.0,
|
||||||
|
0.518, 580.0, 0.568, 590.0, 0.607, 600.0, 0.628, 610.0, 0.637, 620.0, 0.640, 630.0, 0.642,
|
||||||
|
640.0, 0.645, 650.0, 0.648, 660.0, 0.651, 670.0, 0.653, 680.0, 0.657, 690.0, 0.664, 700.0,
|
||||||
|
0.673, 710.0, 0.680, 720.0, 0.684, 730.0, 0.688,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.066, 390.0, 0.079, 400.0, 0.102, 410.0, 0.146, 420.0, 0.200, 430.0, 0.244, 440.0,
|
||||||
|
0.282, 450.0, 0.309, 460.0, 0.308, 470.0, 0.278, 480.0, 0.231, 490.0, 0.178, 500.0, 0.130,
|
||||||
|
510.0, 0.094, 520.0, 0.070, 530.0, 0.054, 540.0, 0.046, 550.0, 0.042, 560.0, 0.039, 570.0,
|
||||||
|
0.038, 580.0, 0.038, 590.0, 0.038, 600.0, 0.038, 610.0, 0.039, 620.0, 0.039, 630.0, 0.040,
|
||||||
|
640.0, 0.041, 650.0, 0.042, 660.0, 0.044, 670.0, 0.045, 680.0, 0.046, 690.0, 0.046, 700.0,
|
||||||
|
0.048, 710.0, 0.052, 720.0, 0.057, 730.0, 0.065,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.052, 390.0, 0.053, 400.0, 0.054, 410.0, 0.055, 420.0, 0.057, 430.0, 0.059, 440.0,
|
||||||
|
0.061, 450.0, 0.066, 460.0, 0.075, 470.0, 0.093, 480.0, 0.125, 490.0, 0.178, 500.0, 0.246,
|
||||||
|
510.0, 0.307, 520.0, 0.337, 530.0, 0.334, 540.0, 0.317, 550.0, 0.293, 560.0, 0.262, 570.0,
|
||||||
|
0.230, 580.0, 0.198, 590.0, 0.165, 600.0, 0.135, 610.0, 0.115, 620.0, 0.104, 630.0, 0.098,
|
||||||
|
640.0, 0.094, 650.0, 0.092, 660.0, 0.093, 670.0, 0.097, 680.0, 0.102, 690.0, 0.108, 700.0,
|
||||||
|
0.113, 710.0, 0.115, 720.0, 0.114, 730.0, 0.114,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.050, 390.0, 0.049, 400.0, 0.048, 410.0, 0.047, 420.0, 0.047, 430.0, 0.047, 440.0,
|
||||||
|
0.047, 450.0, 0.047, 460.0, 0.046, 470.0, 0.045, 480.0, 0.044, 490.0, 0.044, 500.0, 0.045,
|
||||||
|
510.0, 0.046, 520.0, 0.047, 530.0, 0.048, 540.0, 0.049, 550.0, 0.050, 560.0, 0.054, 570.0,
|
||||||
|
0.060, 580.0, 0.072, 590.0, 0.104, 600.0, 0.178, 610.0, 0.312, 620.0, 0.467, 630.0, 0.581,
|
||||||
|
640.0, 0.644, 650.0, 0.675, 660.0, 0.690, 670.0, 0.698, 680.0, 0.706, 690.0, 0.715, 700.0,
|
||||||
|
0.724, 710.0, 0.730, 720.0, 0.734, 730.0, 0.738,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.058, 390.0, 0.054, 400.0, 0.052, 410.0, 0.052, 420.0, 0.053, 430.0, 0.054, 440.0,
|
||||||
|
0.056, 450.0, 0.059, 460.0, 0.067, 470.0, 0.081, 480.0, 0.107, 490.0, 0.152, 500.0, 0.225,
|
||||||
|
510.0, 0.336, 520.0, 0.462, 530.0, 0.559, 540.0, 0.616, 550.0, 0.650, 560.0, 0.672, 570.0,
|
||||||
|
0.694, 580.0, 0.710, 590.0, 0.723, 600.0, 0.731, 610.0, 0.739, 620.0, 0.746, 630.0, 0.752,
|
||||||
|
640.0, 0.758, 650.0, 0.764, 660.0, 0.769, 670.0, 0.771, 680.0, 0.776, 690.0, 0.782, 700.0,
|
||||||
|
0.790, 710.0, 0.796, 720.0, 0.799, 730.0, 0.804,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.145, 390.0, 0.195, 400.0, 0.283, 410.0, 0.346, 420.0, 0.362, 430.0, 0.354, 440.0,
|
||||||
|
0.334, 450.0, 0.306, 460.0, 0.276, 470.0, 0.248, 480.0, 0.218, 490.0, 0.190, 500.0, 0.168,
|
||||||
|
510.0, 0.149, 520.0, 0.127, 530.0, 0.107, 540.0, 0.100, 550.0, 0.102, 560.0, 0.104, 570.0,
|
||||||
|
0.109, 580.0, 0.137, 590.0, 0.200, 600.0, 0.290, 610.0, 0.400, 620.0, 0.516, 630.0, 0.615,
|
||||||
|
640.0, 0.687, 650.0, 0.732, 660.0, 0.760, 670.0, 0.774, 680.0, 0.783, 690.0, 0.793, 700.0,
|
||||||
|
0.803, 710.0, 0.812, 720.0, 0.817, 730.0, 0.825,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.108, 390.0, 0.141, 400.0, 0.192, 410.0, 0.236, 420.0, 0.261, 430.0, 0.286, 440.0,
|
||||||
|
0.317, 450.0, 0.353, 460.0, 0.390, 470.0, 0.426, 480.0, 0.446, 490.0, 0.444, 500.0, 0.423,
|
||||||
|
510.0, 0.385, 520.0, 0.337, 530.0, 0.283, 540.0, 0.231, 550.0, 0.185, 560.0, 0.146, 570.0,
|
||||||
|
0.118, 580.0, 0.101, 590.0, 0.090, 600.0, 0.082, 610.0, 0.076, 620.0, 0.074, 630.0, 0.073,
|
||||||
|
640.0, 0.073, 650.0, 0.074, 660.0, 0.076, 670.0, 0.077, 680.0, 0.076, 690.0, 0.075, 700.0,
|
||||||
|
0.073, 710.0, 0.072, 720.0, 0.074, 730.0, 0.079,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.189, 390.0, 0.255, 400.0, 0.423, 410.0, 0.660, 420.0, 0.811, 430.0, 0.862, 440.0,
|
||||||
|
0.877, 450.0, 0.884, 460.0, 0.891, 470.0, 0.896, 480.0, 0.899, 490.0, 0.904, 500.0, 0.907,
|
||||||
|
510.0, 0.909, 520.0, 0.911, 530.0, 0.910, 540.0, 0.911, 550.0, 0.914, 560.0, 0.913, 570.0,
|
||||||
|
0.916, 580.0, 0.915, 590.0, 0.916, 600.0, 0.914, 610.0, 0.915, 620.0, 0.918, 630.0, 0.919,
|
||||||
|
640.0, 0.921, 650.0, 0.923, 660.0, 0.924, 670.0, 0.922, 680.0, 0.922, 690.0, 0.925, 700.0,
|
||||||
|
0.927, 710.0, 0.930, 720.0, 0.930, 730.0, 0.933,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.171, 390.0, 0.232, 400.0, 0.365, 410.0, 0.507, 420.0, 0.567, 430.0, 0.583, 440.0,
|
||||||
|
0.588, 450.0, 0.590, 460.0, 0.591, 470.0, 0.590, 480.0, 0.588, 490.0, 0.588, 500.0, 0.589,
|
||||||
|
510.0, 0.589, 520.0, 0.591, 530.0, 0.590, 540.0, 0.590, 550.0, 0.590, 560.0, 0.589, 570.0,
|
||||||
|
0.591, 580.0, 0.590, 590.0, 0.590, 600.0, 0.587, 610.0, 0.585, 620.0, 0.583, 630.0, 0.580,
|
||||||
|
640.0, 0.578, 650.0, 0.576, 660.0, 0.574, 670.0, 0.572, 680.0, 0.571, 690.0, 0.569, 700.0,
|
||||||
|
0.568, 710.0, 0.568, 720.0, 0.566, 730.0, 0.566,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.144, 390.0, 0.192, 400.0, 0.272, 410.0, 0.331, 420.0, 0.350, 430.0, 0.357, 440.0,
|
||||||
|
0.361, 450.0, 0.363, 460.0, 0.363, 470.0, 0.361, 480.0, 0.359, 490.0, 0.358, 500.0, 0.358,
|
||||||
|
510.0, 0.359, 520.0, 0.360, 530.0, 0.360, 540.0, 0.361, 550.0, 0.361, 560.0, 0.360, 570.0,
|
||||||
|
0.362, 580.0, 0.362, 590.0, 0.361, 600.0, 0.359, 610.0, 0.358, 620.0, 0.355, 630.0, 0.352,
|
||||||
|
640.0, 0.350, 650.0, 0.348, 660.0, 0.345, 670.0, 0.343, 680.0, 0.340, 690.0, 0.338, 700.0,
|
||||||
|
0.335, 710.0, 0.334, 720.0, 0.332, 730.0, 0.331,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.105, 390.0, 0.131, 400.0, 0.163, 410.0, 0.180, 420.0, 0.186, 430.0, 0.190, 440.0,
|
||||||
|
0.193, 450.0, 0.194, 460.0, 0.194, 470.0, 0.192, 480.0, 0.191, 490.0, 0.191, 500.0, 0.191,
|
||||||
|
510.0, 0.192, 520.0, 0.192, 530.0, 0.192, 540.0, 0.192, 550.0, 0.192, 560.0, 0.192, 570.0,
|
||||||
|
0.193, 580.0, 0.192, 590.0, 0.192, 600.0, 0.191, 610.0, 0.189, 620.0, 0.188, 630.0, 0.186,
|
||||||
|
640.0, 0.184, 650.0, 0.182, 660.0, 0.181, 670.0, 0.179, 680.0, 0.178, 690.0, 0.176, 700.0,
|
||||||
|
0.174, 710.0, 0.173, 720.0, 0.172, 730.0, 0.171,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.068, 390.0, 0.077, 400.0, 0.084, 410.0, 0.087, 420.0, 0.089, 430.0, 0.090, 440.0,
|
||||||
|
0.092, 450.0, 0.092, 460.0, 0.091, 470.0, 0.090, 480.0, 0.090, 490.0, 0.090, 500.0, 0.090,
|
||||||
|
510.0, 0.090, 520.0, 0.090, 530.0, 0.090, 540.0, 0.090, 550.0, 0.090, 560.0, 0.090, 570.0,
|
||||||
|
0.090, 580.0, 0.090, 590.0, 0.089, 600.0, 0.089, 610.0, 0.088, 620.0, 0.087, 630.0, 0.086,
|
||||||
|
640.0, 0.086, 650.0, 0.085, 660.0, 0.084, 670.0, 0.084, 680.0, 0.083, 690.0, 0.083, 700.0,
|
||||||
|
0.082, 710.0, 0.081, 720.0, 0.081, 730.0, 0.081,
|
||||||
|
],
|
||||||
|
&[
|
||||||
|
380.0, 0.031, 390.0, 0.032, 400.0, 0.032, 410.0, 0.033, 420.0, 0.033, 430.0, 0.033, 440.0,
|
||||||
|
0.033, 450.0, 0.033, 460.0, 0.032, 470.0, 0.032, 480.0, 0.032, 490.0, 0.032, 500.0, 0.032,
|
||||||
|
510.0, 0.032, 520.0, 0.032, 530.0, 0.032, 540.0, 0.032, 550.0, 0.032, 560.0, 0.032, 570.0,
|
||||||
|
0.032, 580.0, 0.032, 590.0, 0.032, 600.0, 0.032, 610.0, 0.032, 620.0, 0.032, 630.0, 0.032,
|
||||||
|
640.0, 0.032, 650.0, 0.032, 660.0, 0.032, 670.0, 0.032, 680.0, 0.032, 690.0, 0.032, 700.0,
|
||||||
|
0.032, 710.0, 0.032, 720.0, 0.032, 730.0, 0.033,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -283,12 +283,20 @@ impl RGB {
|
||||||
(self.r + self.g + self.b) / 3.0
|
(self.r + self.g + self.b) / 3.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max(&self) -> Float {
|
pub fn max_component_value(&self) -> Float {
|
||||||
self.r.max(self.g).max(self.b)
|
self.r.max(self.g).max(self.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn max_component_index(&self) -> usize {
|
||||||
|
if self.r > self.g {
|
||||||
|
if self.r > self.b { 0 } else { 2 }
|
||||||
|
} else {
|
||||||
|
if self.g > self.b { 1 } else { 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clamp_zero(rgb: Self) -> Self {
|
pub fn clamp_zero(rgb: Self) -> Self {
|
||||||
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.b.max(0.))
|
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.g.max(0.))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,15 +518,37 @@ pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RGBToSpectrumTable {
|
pub struct RGBToSpectrumTable {
|
||||||
z_nodes: &'static [f32],
|
coeffs: &'static [Float],
|
||||||
coeffs: &'static CoefficientArray,
|
scale: &'static [Float],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBToSpectrumTable {
|
impl RGBToSpectrumTable {
|
||||||
pub fn srgb() -> Self {
|
pub fn srgb() -> Self {
|
||||||
// use crate::core::constants::{RGB_TO_SPECTRUM_Z_NODES, RGB_TO_SPECTRUM_COEFFS};
|
Self {
|
||||||
// Self::new(&RGB_TO_SPECTRUM_Z_NODES, &RGB_TO_SPECTRUM_COEFFS)
|
coeffs: *crate::data::SRGB_COEFFS,
|
||||||
todo!("Link the static constant arrays for sRGB coefficients here")
|
scale: *crate::data::SRGB_SCALE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dci_p3() -> Self {
|
||||||
|
Self {
|
||||||
|
coeffs: *crate::data::DCI_P3_COEFFS,
|
||||||
|
scale: *crate::data::DCI_P3_SCALE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rec2020() -> Self {
|
||||||
|
Self {
|
||||||
|
coeffs: *crate::data::REC2020_COEFFS,
|
||||||
|
scale: *crate::data::REC2020_SCALE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aces2065_1() -> Self {
|
||||||
|
Self {
|
||||||
|
coeffs: *crate::data::ACES_COEFFS,
|
||||||
|
scale: *crate::data::ACES_SCALE,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,11 +593,12 @@ impl RGBSigmoidPolynomial {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBToSpectrumTable {
|
impl RGBToSpectrumTable {
|
||||||
pub fn new(z_nodes: &'static [f32], coeffs: &'static CoefficientArray) -> Self {
|
pub fn new(scale: &'static [Float], coeffs: &'static [Float]) -> Self {
|
||||||
Self { z_nodes, coeffs }
|
Self { scale, coeffs }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||||
|
//TODO: Check all of this logic, this is important
|
||||||
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
|
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
|
||||||
return RGBSigmoidPolynomial::new(
|
return RGBSigmoidPolynomial::new(
|
||||||
0.0,
|
0.0,
|
||||||
|
|
@ -575,53 +606,68 @@ impl RGBToSpectrumTable {
|
||||||
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
|
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let maxc;
|
|
||||||
if rgb[0] > rgb[1] {
|
|
||||||
if rgb[0] > rgb[2] {
|
|
||||||
maxc = 0;
|
|
||||||
} else {
|
|
||||||
maxc = 2;
|
|
||||||
}
|
|
||||||
} else if rgb[1] > rgb[2] {
|
|
||||||
maxc = 1;
|
|
||||||
} else {
|
|
||||||
maxc = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let z = rgb[maxc];
|
let max_c = rgb.max_component_value();
|
||||||
let x = rgb[(maxc + 1) % 3] * (RES - 1) as Float / z;
|
let c = [rgb.r, rgb.g, rgb.b];
|
||||||
let y = rgb[(maxc + 2) % 3] * (RES - 1) as Float / z;
|
let (min_c, mid_c) = if c[0] < c[1] {
|
||||||
|
if c[1] < c[2] {
|
||||||
|
(c[0], c[1])
|
||||||
|
}
|
||||||
|
// 0 < 1 < 2
|
||||||
|
else if c[0] < c[2] {
|
||||||
|
(c[0], c[2])
|
||||||
|
}
|
||||||
|
// 0 < 2 < 1
|
||||||
|
else {
|
||||||
|
(c[2], c[0])
|
||||||
|
} // 2 < 0 < 1
|
||||||
|
} else {
|
||||||
|
if c[1] > c[2] {
|
||||||
|
(c[2], c[1])
|
||||||
|
}
|
||||||
|
// 2 < 1 < 0
|
||||||
|
else if c[0] > c[2] {
|
||||||
|
(c[1], c[2])
|
||||||
|
}
|
||||||
|
// 1 < 2 < 0
|
||||||
|
else {
|
||||||
|
(c[1], c[0])
|
||||||
|
} // 1 < 0 < 2
|
||||||
|
};
|
||||||
|
|
||||||
let xi = x.min(RES as Float - 2.0);
|
let z = min_c / max_c;
|
||||||
let yi = y.min(RES as Float - 2.0);
|
let x = mid_c / max_c;
|
||||||
let zi = crate::core::pbrt::find_interval(RES, |i: usize| self.z_nodes[i] < z);
|
|
||||||
let dx = (x - xi) as usize;
|
let z_float = z * (RES - 1) as Float;
|
||||||
let dy = (y - yi) as usize;
|
let zi = (z_float as usize).min(RES - 2);
|
||||||
let dz = (z - self.z_nodes[zi]) / (self.z_nodes[zi + 1] - self.z_nodes[zi]);
|
let z_t = z_float - zi as Float;
|
||||||
let mut c = [0.0; 3];
|
let x_float = x * (RES - 1) as Float;
|
||||||
|
let xi = (x_float as usize).min(RES - 2);
|
||||||
|
let x_t = x_float - xi as Float;
|
||||||
|
|
||||||
|
let mut coeffs = [0.0; 3];
|
||||||
#[allow(clippy::needless_range_loop)]
|
#[allow(clippy::needless_range_loop)]
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let co = |dx: usize, dy: usize, dz: usize| {
|
let offset = |dz, dx| (((zi + dz) * RES + (xi + dx)) * RES + (RES - 1)) * 3 + i;
|
||||||
self.coeffs[maxc][zi as usize + dz][yi as usize + dy][xi as usize + dx][i]
|
let c00 = self.coeffs[offset(0, 0)];
|
||||||
};
|
let c01 = self.coeffs[offset(0, 1)];
|
||||||
c[i] = lerp(
|
let c10 = self.coeffs[offset(1, 0)];
|
||||||
dz,
|
let c11 = self.coeffs[offset(1, 1)];
|
||||||
lerp(
|
let v0 = lerp(x_t, c00, c01);
|
||||||
dy as Float,
|
let v1 = lerp(x_t, c10, c11);
|
||||||
lerp(dx as Float, co(0, 0, 0) as Float, co(1, 0, 0)) as Float,
|
coeffs[i] = lerp(z_t, v0, v1);
|
||||||
lerp(dx as Float, co(0, 1, 0) as Float, co(1, 1, 0) as Float),
|
|
||||||
),
|
|
||||||
lerp(
|
|
||||||
dy as Float,
|
|
||||||
lerp(dx as Float, co(0, 0, 1) as Float, co(1, 0, 1)) as Float,
|
|
||||||
lerp(dx as Float, co(0, 1, 1) as Float, co(1, 1, 1) as Float),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scale_float = max_c * (RES - 1) as Float;
|
||||||
|
let si = (scale_float as usize).min(RES - 2);
|
||||||
|
let s_t = scale_float - si as Float;
|
||||||
|
|
||||||
|
let scale = lerp(s_t, self.scale[si], self.scale[si + 1]);
|
||||||
|
|
||||||
RGBSigmoidPolynomial {
|
RGBSigmoidPolynomial {
|
||||||
c0: c[0],
|
c0: coeffs[0],
|
||||||
c1: c[1],
|
c1: coeffs[1],
|
||||||
c2: c[2],
|
c2: coeffs[2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ impl RGBColorSpace {
|
||||||
let rgb_values = [
|
let rgb_values = [
|
||||||
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
||||||
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
||||||
[r_xyz.z(), g_xyz.z(), g_xyz.z()],
|
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
|
||||||
];
|
];
|
||||||
let rgb = SquareMatrix::new(rgb_values);
|
let rgb = SquareMatrix::new(rgb_values);
|
||||||
let c: RGB = rgb.inverse()? * w_xyz;
|
let c: RGB = rgb.inverse()? * w_xyz;
|
||||||
|
|
@ -59,6 +59,16 @@ impl RGBColorSpace {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_named(name: &str) -> Result<Arc<RGBColorSpace>, String> {
|
||||||
|
match name.to_lowercase().as_str() {
|
||||||
|
"aces2065-1" => Ok(Self::aces2065_1().clone()),
|
||||||
|
"rec2020" => Ok(Self::rec2020().clone()),
|
||||||
|
"dci-p3" => Ok(Self::dci_p3().clone()),
|
||||||
|
"srgb" => Ok(Self::srgb().clone()),
|
||||||
|
_ => Err(format!("Color space '{}' not found", name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
||||||
self.xyz_from_rgb * rgb
|
self.xyz_from_rgb * rgb
|
||||||
}
|
}
|
||||||
|
|
@ -79,8 +89,8 @@ impl RGBColorSpace {
|
||||||
self.rgb_from_xyz * other.xyz_from_rgb
|
self.rgb_from_xyz * other.xyz_from_rgb
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn srgb() -> &'static Self {
|
pub fn srgb() -> &'static Arc<Self> {
|
||||||
static SRGB_SPACE: Lazy<RGBColorSpace> = Lazy::new(|| {
|
static SRGB_SPACE: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
let r = Point2f::new(0.64, 0.33);
|
let r = Point2f::new(0.64, 0.33);
|
||||||
let g = Point2f::new(0.30, 0.60);
|
let g = Point2f::new(0.30, 0.60);
|
||||||
let b = Point2f::new(0.15, 0.06);
|
let b = Point2f::new(0.15, 0.06);
|
||||||
|
|
@ -88,12 +98,54 @@ impl RGBColorSpace {
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
let illuminant = Spectrum::std_illuminant_d65();
|
||||||
let table = RGBToSpectrumTable::srgb();
|
let table = RGBToSpectrumTable::srgb();
|
||||||
|
|
||||||
RGBColorSpace::new(r, g, b, illuminant, table)
|
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
||||||
.expect("Failed to initialize standard sRGB color space")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
&SRGB_SPACE
|
&SRGB_SPACE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dci_p3() -> &'static Arc<Self> {
|
||||||
|
static DCI_P3: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
|
let r = Point2f::new(0.680, 0.320);
|
||||||
|
let g = Point2f::new(0.265, 0.690);
|
||||||
|
let b = Point2f::new(0.150, 0.060);
|
||||||
|
let illuminant = Spectrum::std_illuminant_d65();
|
||||||
|
|
||||||
|
let table = RGBToSpectrumTable::dci_p3();
|
||||||
|
|
||||||
|
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
||||||
|
});
|
||||||
|
&DCI_P3
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rec2020() -> &'static Arc<Self> {
|
||||||
|
static REC2020: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
|
let r = Point2f::new(0.708, 0.292);
|
||||||
|
let g = Point2f::new(0.170, 0.797);
|
||||||
|
let b = Point2f::new(0.131, 0.046);
|
||||||
|
let illuminant = Spectrum::std_illuminant_d65();
|
||||||
|
|
||||||
|
let table = RGBToSpectrumTable::rec2020();
|
||||||
|
|
||||||
|
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
||||||
|
});
|
||||||
|
&REC2020
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aces2065_1() -> &'static Arc<Self> {
|
||||||
|
static ACES: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
|
let r = Point2f::new(0.7347, 0.2653);
|
||||||
|
let g = Point2f::new(0.0000, 1.0000);
|
||||||
|
let b = Point2f::new(0.0001, -0.0770);
|
||||||
|
// ACES uses D60
|
||||||
|
let illuminant = Spectrum::std_illuminant_d65();
|
||||||
|
|
||||||
|
let table = RGBToSpectrumTable::aces2065_1();
|
||||||
|
|
||||||
|
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
||||||
|
});
|
||||||
|
&ACES
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for RGBColorSpace {
|
impl PartialEq for RGBColorSpace {
|
||||||
65
shared/src/spectra/data.rs
Normal file
65
shared/src/spectra/data.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use super::Spectrum;
|
||||||
|
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
||||||
|
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
|
||||||
|
use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES};
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::spectra::{BlackbodySpectrum, SpectrumProvider};
|
||||||
|
use crate::utils::math::square;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
fn create_cie_spectrum(data: &[Float]) -> Spectrum {
|
||||||
|
let pls = PiecewiseLinearSpectrum::from_interleaved(data, false);
|
||||||
|
|
||||||
|
let dss = DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(pls));
|
||||||
|
|
||||||
|
Spectrum::DenselySampled(dss)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_cie_d(temperature: Float) -> DenselySampledSpectrum {
|
||||||
|
let cct = temperature * 1.4388 / 1.4380;
|
||||||
|
if cct < 4000.0 {
|
||||||
|
let bb = BlackbodySpectrum::new(cct);
|
||||||
|
return DenselySampledSpectrum::from_function(
|
||||||
|
|lambda| bb.evaluate(lambda),
|
||||||
|
LAMBDA_MIN,
|
||||||
|
LAMBDA_MAX,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = if cct < 7000. {
|
||||||
|
-4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
|
||||||
|
} else {
|
||||||
|
-2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
|
||||||
|
};
|
||||||
|
let y = -3. * x + 2.87 * x - 0.275;
|
||||||
|
|
||||||
|
let m = 0.0241 + 0.2562 * x - 0.7341 * y;
|
||||||
|
let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
|
||||||
|
let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;
|
||||||
|
|
||||||
|
let values: Vec<Float> = (0..N_CIES)
|
||||||
|
.map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let dpls = PiecewiseLinearSpectrum {
|
||||||
|
lambdas: CIE_S_LAMBDA.to_vec(),
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
|
||||||
|
DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(dpls))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cie_x() -> &'static Spectrum {
|
||||||
|
static X: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_X));
|
||||||
|
&X
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cie_y() -> &'static Spectrum {
|
||||||
|
static Y: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Y));
|
||||||
|
&Y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cie_z() -> &'static Spectrum {
|
||||||
|
static Z: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Z));
|
||||||
|
&Z
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,51 @@
|
||||||
|
pub mod cie;
|
||||||
|
pub mod color;
|
||||||
|
pub mod colorspace;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod rgb;
|
pub mod rgb;
|
||||||
pub mod sampled;
|
pub mod sampled;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
|
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::color::{RGB, XYZ};
|
use crate::utils::file::read_float_file;
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
|
pub use color::{RGB, XYZ};
|
||||||
|
pub use colorspace::RGBColorSpace;
|
||||||
pub use data::*;
|
pub use data::*;
|
||||||
pub use rgb::*;
|
pub use rgb::*;
|
||||||
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
||||||
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||||
pub use simple::*; // CIE_X, etc
|
pub use simple::*;
|
||||||
//
|
//
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait SpectrumTrait {
|
pub trait SpectrumProvider: Copy {
|
||||||
fn evaluate(&self, lambda: Float) -> Float;
|
fn evaluate(&self, lambda: Float) -> Float;
|
||||||
fn max_value(&self) -> Float;
|
fn max_value(&self) -> Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "spirv"))]
|
||||||
|
impl SpectrumProvider for std::sync::Arc<DenselySampledSpectrum> {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
(**self).evaluate(lambda)
|
||||||
|
}
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
(**self).max_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "spirv")] // or target_os = "cuda"
|
||||||
|
impl SpectrumProvider for u32 {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
// Here you would call a global function that accesses
|
||||||
|
// a static buffer of spectra data
|
||||||
|
crate::gpu::lookup_global_spectrum(*self, lambda)
|
||||||
|
}
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
crate::gpu::lookup_global_spectrum_max(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[enum_dispatch(SpectrumTrait)]
|
#[enum_dispatch(SpectrumTrait)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Spectrum {
|
pub enum Spectrum {
|
||||||
|
|
@ -28,6 +54,8 @@ pub enum Spectrum {
|
||||||
PiecewiseLinear(PiecewiseLinearSpectrum),
|
PiecewiseLinear(PiecewiseLinearSpectrum),
|
||||||
Blackbody(BlackbodySpectrum),
|
Blackbody(BlackbodySpectrum),
|
||||||
RGBAlbedo(RGBAlbedoSpectrum),
|
RGBAlbedo(RGBAlbedoSpectrum),
|
||||||
|
RGBIlluminant(RGBIlluminantSpectrum),
|
||||||
|
RGBUnbounded(RGBUnboundedSpectrum),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spectrum {
|
impl Spectrum {
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use super::sampled::{
|
use super::{
|
||||||
LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths,
|
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
|
||||||
|
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumProvider, XYZ,
|
||||||
};
|
};
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::spectra::{DenselySampledSpectrum, SpectrumTrait};
|
use crate::Float;
|
||||||
use crate::utils::color::{RGB, RGBSigmoidPolynomial, XYZ};
|
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBAlbedoSpectrum {
|
pub struct RGBAlbedoSpectrum {
|
||||||
|
|
@ -28,7 +26,7 @@ impl RGBAlbedoSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTrait for RGBAlbedoSpectrum {
|
impl SpectrumProvider for RGBAlbedoSpectrum {
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
self.rsp.evaluate(lambda)
|
self.rsp.evaluate(lambda)
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +58,7 @@ impl UnboundedRGBSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTrait for UnboundedRGBSpectrum {
|
impl SpectrumProvider for UnboundedRGBSpectrum {
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
self.scale * self.rsp.evaluate(lambda)
|
self.scale * self.rsp.evaluate(lambda)
|
||||||
}
|
}
|
||||||
|
|
@ -71,40 +69,39 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct RGBIlluminantSpectrum {
|
pub struct RGBIlluminantSpectrum<P: SpectrumProvider> {
|
||||||
scale: Float,
|
scale: Float,
|
||||||
rsp: RGBSigmoidPolynomial,
|
rsp: RGBSigmoidPolynomial,
|
||||||
illuminant: Option<Arc<DenselySampledSpectrum>>,
|
illuminant: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBIlluminantSpectrum {
|
// impl RGBIlluminantSpectrum {
|
||||||
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
||||||
let illuminant = &cs.illuminant;
|
// let illuminant = &cs.illuminant;
|
||||||
let densely_sampled =
|
// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
|
||||||
DenselySampledSpectrum::from_spectrum(illuminant, LAMBDA_MIN, LAMBDA_MAX);
|
// let m = rgb.max_component_value();
|
||||||
let m = rgb.max();
|
// let scale = 2. * m;
|
||||||
let scale = 2. * m;
|
// let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
||||||
let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
// rgb / scale
|
||||||
rgb / scale
|
// } else {
|
||||||
} else {
|
// RGB::new(0., 0., 0.)
|
||||||
RGB::new(0., 0., 0.)
|
// });
|
||||||
});
|
// Self {
|
||||||
Self {
|
// scale,
|
||||||
scale,
|
// rsp,
|
||||||
rsp,
|
// illuminant: Some(Arc::new(densely_sampled)),
|
||||||
illuminant: Some(Arc::new(densely_sampled)),
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
// if self.illuminant.is_none() {
|
||||||
|
// return SampledSpectrum::new(0.);
|
||||||
|
// }
|
||||||
|
// SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
impl SpectrumProvider for RGBIlluminantSpectrum {
|
||||||
if self.illuminant.is_none() {
|
|
||||||
return SampledSpectrum::new(0.);
|
|
||||||
}
|
|
||||||
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for RGBIlluminantSpectrum {
|
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
match &self.illuminant {
|
match &self.illuminant {
|
||||||
Some(illuminant) => {
|
Some(illuminant) => {
|
||||||
|
|
@ -144,7 +141,7 @@ impl Default for RGBUnboundedSpectrum {
|
||||||
|
|
||||||
impl RGBUnboundedSpectrum {
|
impl RGBUnboundedSpectrum {
|
||||||
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
||||||
let m = rgb.max();
|
let m = rgb.max_component_value();
|
||||||
|
|
||||||
let scale = 2.0 * m;
|
let scale = 2.0 * m;
|
||||||
|
|
||||||
|
|
@ -164,7 +161,7 @@ impl RGBUnboundedSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTrait for RGBUnboundedSpectrum {
|
impl SpectrumProvider for RGBUnboundedSpectrum {
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
self.scale * self.rsp.evaluate(lambda)
|
self.scale * self.rsp.evaluate(lambda)
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::utils::math::{clamp, lerp};
|
||||||
use std::ops::{
|
use std::ops::{
|
||||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||||
};
|
};
|
||||||
|
|
@ -12,6 +13,7 @@ pub const LAMBDA_MIN: i32 = 360;
|
||||||
pub const LAMBDA_MAX: i32 = 830;
|
pub const LAMBDA_MAX: i32 = 830;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct SampledSpectrum {
|
pub struct SampledSpectrum {
|
||||||
pub values: [Float; N_SPECTRUM_SAMPLES],
|
pub values: [Float; N_SPECTRUM_SAMPLES],
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +94,20 @@ impl SampledSpectrum {
|
||||||
|
|
||||||
pub fn clamp_zero(s: &SampledSpectrum) -> Self {
|
pub fn clamp_zero(s: &SampledSpectrum) -> Self {
|
||||||
let ret = SampledSpectrum::from_fn(|i| s[i].max(0.));
|
let ret = SampledSpectrum::from_fn(|i| s[i].max(0.));
|
||||||
assert!(!ret.has_nans());
|
debug_assert!(!ret.has_nans());
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sqrt(&self) -> Self {
|
||||||
|
let values = self.values.map(|v| v.sqrt());
|
||||||
|
let ret = SampledSpectrum { values };
|
||||||
|
debug_assert!(!ret.has_nans());
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp(s: &SampledSpectrum, low: Float, high: Float) -> Self {
|
||||||
|
let ret = SampledSpectrum::from_fn(|i| clamp(s[i], low, high));
|
||||||
|
debug_assert!(!ret.has_nans());
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,6 +295,7 @@ impl Neg for SampledSpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct SampledWavelengths {
|
pub struct SampledWavelengths {
|
||||||
pub lambda: [Float; N_SPECTRUM_SAMPLES],
|
pub lambda: [Float; N_SPECTRUM_SAMPLES],
|
||||||
pub pdf: [Float; N_SPECTRUM_SAMPLES],
|
pub pdf: [Float; N_SPECTRUM_SAMPLES],
|
||||||
|
|
@ -318,7 +334,7 @@ impl SampledWavelengths {
|
||||||
|
|
||||||
pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self {
|
pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self {
|
||||||
let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
|
let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
|
||||||
lambda[0] = crate::core::pbrt::lerp(u, lambda_min, lambda_min);
|
lambda[0] = lerp(u, lambda_min, lambda_min);
|
||||||
let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float;
|
let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float;
|
||||||
for i in 1..N_SPECTRUM_SAMPLES {
|
for i in 1..N_SPECTRUM_SAMPLES {
|
||||||
lambda[i] = lambda[i - 1] + delta;
|
lambda[i] = lambda[i - 1] + delta;
|
||||||
480
shared/src/spectra/simple.rs
Normal file
480
shared/src/spectra/simple.rs
Normal file
|
|
@ -0,0 +1,480 @@
|
||||||
|
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
||||||
|
use crate::core::cie::*;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::spectra::{
|
||||||
|
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
|
||||||
|
};
|
||||||
|
use crate::utils::file::read_float_file;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ConstantSpectrum {
|
||||||
|
c: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstantSpectrum {
|
||||||
|
pub fn new(c: Float) -> Self {
|
||||||
|
Self { c }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpectrumProvider for ConstantSpectrum {
|
||||||
|
fn evaluate(&self, _lambda: Float) -> Float {
|
||||||
|
self.c
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
self.c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DenselySampledSpectrum {
|
||||||
|
lambda_min: i32,
|
||||||
|
lambda_max: i32,
|
||||||
|
values: Vec<Float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DenselySampledSpectrum {
|
||||||
|
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
||||||
|
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
||||||
|
Self {
|
||||||
|
lambda_min,
|
||||||
|
lambda_max,
|
||||||
|
values: vec![0.0; n_values],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_spectrum(spec: &Spectrum) -> Self {
|
||||||
|
let lambda_min = LAMBDA_MIN;
|
||||||
|
let lambda_max = LAMBDA_MAX;
|
||||||
|
let mut s = Self::new(lambda_min, lambda_max);
|
||||||
|
if s.values.is_empty() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
for lambda in lambda_min..=lambda_max {
|
||||||
|
let index = (lambda - lambda_min) as usize;
|
||||||
|
s.values[index] = spec.evaluate(lambda as Float);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_spectrum_with_range(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
|
||||||
|
let mut s = Self::new(lambda_min, lambda_max);
|
||||||
|
if s.values.is_empty() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
for lambda in lambda_min..=lambda_max {
|
||||||
|
let index = (lambda - lambda_min) as usize;
|
||||||
|
s.values[index] = spec.evaluate(lambda as Float);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Float) -> Float,
|
||||||
|
{
|
||||||
|
let mut s = Self::new(lambda_min, lambda_max);
|
||||||
|
if s.values.is_empty() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
for lambda in lambda_min..=lambda_max {
|
||||||
|
let index = (lambda - lambda_min) as usize;
|
||||||
|
s.values[index] = f(lambda as Float);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
let mut s = SampledSpectrum::default();
|
||||||
|
|
||||||
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
|
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
|
||||||
|
if offset >= self.values.len() {
|
||||||
|
s[i] = 0.;
|
||||||
|
} else {
|
||||||
|
s[i] = self.values[offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_component_value(&self) -> Float {
|
||||||
|
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_component_value(&self) -> Float {
|
||||||
|
self.values
|
||||||
|
.iter()
|
||||||
|
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average(&self) -> Float {
|
||||||
|
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
||||||
|
let mut r = Self::new(1, 1);
|
||||||
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
|
r.values[i] = if rhs[i] != 0.0 {
|
||||||
|
self.values[i] / rhs.values[i]
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(&mut self, factor: Float) {
|
||||||
|
for v in &mut self.values {
|
||||||
|
*v *= factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for DenselySampledSpectrum {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.lambda_min != other.lambda_min
|
||||||
|
|| self.lambda_max != other.lambda_max
|
||||||
|
|| self.values.len() != other.values.len()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values
|
||||||
|
.iter()
|
||||||
|
.zip(&other.values)
|
||||||
|
.all(|(a, b)| a.to_bits() == b.to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for DenselySampledSpectrum {}
|
||||||
|
|
||||||
|
impl Hash for DenselySampledSpectrum {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.lambda_min.hash(state);
|
||||||
|
self.lambda_max.hash(state);
|
||||||
|
|
||||||
|
for v in &self.values {
|
||||||
|
v.to_bits().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpectrumProvider for DenselySampledSpectrum {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
let offset = (lambda.round() as i32) - self.lambda_min;
|
||||||
|
if offset < 0 || offset as usize >= self.values.len() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
self.values[offset as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PiecewiseLinearSpectrum {
|
||||||
|
pub lambdas: Vec<Float>,
|
||||||
|
pub values: Vec<Float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiecewiseLinearSpectrum {
|
||||||
|
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self {
|
||||||
|
if data.len() % 2 != 0 {
|
||||||
|
panic!("Interleaved data must have an even number of elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut temp: Vec<(Float, Float)> =
|
||||||
|
data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect();
|
||||||
|
|
||||||
|
temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
|
let (lambdas, values): (Vec<Float>, Vec<Float>) = temp.into_iter().unzip();
|
||||||
|
|
||||||
|
// Normalization left to the reader
|
||||||
|
// This usually involves integrating the curve and scaling 'values'
|
||||||
|
|
||||||
|
Self { lambdas, values }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(filepath: &str) -> Option<Self> {
|
||||||
|
let vals = match read_float_file(filepath) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if vals.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if vals.len() % 2 == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = vals.len() / 2;
|
||||||
|
let mut lambdas = Vec::with_capacity(count);
|
||||||
|
let mut values = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
for (_, pair) in vals.chunks(2).enumerate() {
|
||||||
|
let curr_lambda = pair[0];
|
||||||
|
let curr_val = pair[1];
|
||||||
|
|
||||||
|
if let Some(&prev_lambda) = lambdas.last() {
|
||||||
|
if curr_lambda <= prev_lambda {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lambdas.push(curr_lambda);
|
||||||
|
values.push(curr_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(PiecewiseLinearSpectrum { lambdas, values })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpectrumProvider for PiecewiseLinearSpectrum {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
if self.lambdas.is_empty() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if lambda <= self.lambdas[0] {
|
||||||
|
return self.values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if lambda >= *self.lambdas.last().unwrap() {
|
||||||
|
return *self.values.last().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = self.lambdas.partition_point(|&l| l < lambda);
|
||||||
|
let l0 = self.lambdas[i - 1];
|
||||||
|
let l1 = self.lambdas[i];
|
||||||
|
let v0 = self.values[i - 1];
|
||||||
|
let v1 = self.values[i];
|
||||||
|
|
||||||
|
let t = (lambda - l0) / (l1 - l0);
|
||||||
|
|
||||||
|
v0 + t * (v1 - v0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
if self.values.is_empty() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
self.values.iter().fold(0.0, |acc, &v| acc.max(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BlackbodySpectrum {
|
||||||
|
temperature: Float,
|
||||||
|
normalization_factor: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Planck's Law
|
||||||
|
impl BlackbodySpectrum {
|
||||||
|
const C: Float = 299792458.0;
|
||||||
|
const H: Float = 6.62606957e-34;
|
||||||
|
const KB: Float = 1.3806488e-23;
|
||||||
|
|
||||||
|
pub fn new(temperature: Float) -> Self {
|
||||||
|
// Physical constants
|
||||||
|
let lambda_max = 2.8977721e-3 / temperature * 1e9;
|
||||||
|
let max_val = Self::planck_law(lambda_max, temperature);
|
||||||
|
Self {
|
||||||
|
temperature,
|
||||||
|
normalization_factor: if max_val > 0.0 { 1.0 / max_val } else { 0.0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
|
||||||
|
if temp <= 0.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
let lambda_m = lambda_nm * 1e-9;
|
||||||
|
let c1 = 2.0 * Self::H * Self::C * Self::C;
|
||||||
|
let c2 = (Self::H * Self::C) / Self::KB;
|
||||||
|
|
||||||
|
let numerator = c1 / lambda_m.powi(5);
|
||||||
|
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
|
||||||
|
|
||||||
|
if denominator.is_infinite() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
numerator / denominator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
SampledSpectrum::from_fn(|i| {
|
||||||
|
Self::planck_law(lambda[i], self.temperature) * self.normalization_factor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpectrumProvider for BlackbodySpectrum {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
Self::planck_law(lambda, self.temperature) * self.normalization_factor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_value(&self) -> Float {
|
||||||
|
let lambda_max = 2.8977721e-3 / self.temperature * 1e9;
|
||||||
|
Self::planck_law(lambda_max, self.temperature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
|
||||||
|
// A macro to reduce boilerplate and make the list readable
|
||||||
|
macro_rules! add {
|
||||||
|
($name:expr, $data:expr, $norm:expr) => {
|
||||||
|
m.insert(
|
||||||
|
$name.to_string(),
|
||||||
|
Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum::from_interleaved($data, $norm)),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
add!("stdillum-A", &CIE_ILLUM_A, true);
|
||||||
|
add!("stdillum-D50", &CIE_ILLUM_D5000, true);
|
||||||
|
add!("stdillum-D65", &CIE_ILLUM_D6500, true);
|
||||||
|
add!("stdillum-F1", &CIE_ILLUM_F1, true);
|
||||||
|
add!("stdillum-F2", &CIE_ILLUM_F2, true);
|
||||||
|
add!("stdillum-F3", &CIE_ILLUM_F3, true);
|
||||||
|
add!("stdillum-F4", &CIE_ILLUM_F4, true);
|
||||||
|
add!("stdillum-F5", &CIE_ILLUM_F5, true);
|
||||||
|
add!("stdillum-F6", &CIE_ILLUM_F6, true);
|
||||||
|
add!("stdillum-F7", &CIE_ILLUM_F7, true);
|
||||||
|
add!("stdillum-F8", &CIE_ILLUM_F8, true);
|
||||||
|
add!("stdillum-F9", &CIE_ILLUM_F9, true);
|
||||||
|
add!("stdillum-F10", &CIE_ILLUM_F10, true);
|
||||||
|
add!("stdillum-F11", &CIE_ILLUM_F11, true);
|
||||||
|
add!("stdillum-F12", &CIE_ILLUM_F12, true);
|
||||||
|
add!("illum-acesD60", &ACES_ILLUM_D60, true);
|
||||||
|
|
||||||
|
// --- Glasses ---
|
||||||
|
add!("glass-BK7", &GLASS_BK7_ETA, false);
|
||||||
|
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
|
||||||
|
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
|
||||||
|
add!("glass-LASF9", &GLASS_LASF9_ETA, false);
|
||||||
|
add!("glass-F5", &GLASS_SF5_ETA, false);
|
||||||
|
add!("glass-F10", &GLASS_SF10_ETA, false);
|
||||||
|
add!("glass-F11", &GLASS_SF11_ETA, false);
|
||||||
|
|
||||||
|
// --- Metals ---
|
||||||
|
add!("metal-Ag-eta", &AG_ETA, false);
|
||||||
|
add!("metal-Ag-k", &AG_K, false);
|
||||||
|
add!("metal-Al-eta", &AL_ETA, false);
|
||||||
|
add!("metal-Al-k", &AL_K, false);
|
||||||
|
add!("metal-Au-eta", &AU_ETA, false);
|
||||||
|
add!("metal-Au-k", &AU_K, false);
|
||||||
|
add!("metal-Cu-eta", &CU_ETA, false);
|
||||||
|
add!("metal-Cu-k", &CU_K, false);
|
||||||
|
add!("metal-CuZn-eta", &CUZN_ETA, false);
|
||||||
|
add!("metal-CuZn-k", &CUZN_K, false);
|
||||||
|
add!("metal-MgO-eta", &MGO_ETA, false);
|
||||||
|
add!("metal-MgO-k", &MGO_K, false);
|
||||||
|
add!("metal-TiO2-eta", &TIO2_ETA, false);
|
||||||
|
add!("metal-TiO2-k", &TIO2_K, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 100D ---
|
||||||
|
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
|
||||||
|
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
|
||||||
|
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 1DX MkII ---
|
||||||
|
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
|
||||||
|
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
|
||||||
|
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 200D ---
|
||||||
|
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
|
||||||
|
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
|
||||||
|
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 200D MkII ---
|
||||||
|
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
|
||||||
|
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
|
||||||
|
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D ---
|
||||||
|
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
|
||||||
|
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
|
||||||
|
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkII ---
|
||||||
|
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
|
||||||
|
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
|
||||||
|
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkIII ---
|
||||||
|
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
|
||||||
|
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
|
||||||
|
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkIV ---
|
||||||
|
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
|
||||||
|
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
|
||||||
|
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5DS ---
|
||||||
|
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
|
||||||
|
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
|
||||||
|
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS M ---
|
||||||
|
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
|
||||||
|
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
|
||||||
|
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
|
||||||
|
|
||||||
|
// --- Hasselblad L1D 20C ---
|
||||||
|
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
|
||||||
|
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
|
||||||
|
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
|
||||||
|
|
||||||
|
// --- Nikon D810 ---
|
||||||
|
add!("nikon_d810_r", &NIKON_D810_R, false);
|
||||||
|
add!("nikon_d810_g", &NIKON_D810_G, false);
|
||||||
|
add!("nikon_d810_b", &NIKON_D810_B, false);
|
||||||
|
|
||||||
|
// --- Nikon D850 ---
|
||||||
|
add!("nikon_d850_r", &NIKON_D850_R, false);
|
||||||
|
add!("nikon_d850_g", &NIKON_D850_G, false);
|
||||||
|
add!("nikon_d850_b", &NIKON_D850_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 6400 ---
|
||||||
|
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
|
||||||
|
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
|
||||||
|
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 7M3 ---
|
||||||
|
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
|
||||||
|
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
|
||||||
|
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 7RM3 ---
|
||||||
|
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
|
||||||
|
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
|
||||||
|
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 9 ---
|
||||||
|
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
|
||||||
|
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
|
||||||
|
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);
|
||||||
|
|
||||||
|
m
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
|
||||||
|
NAMED_SPECTRA.get(name).cloned()
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::utils::math::lerp;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::collections::hash_map::RandomState;
|
use std::collections::hash_map::RandomState;
|
||||||
use std::hash::{BuildHasher, Hash, Hasher};
|
use std::hash::{BuildHasher, Hash, Hasher};
|
||||||
use std::ops::{Add, Index, IndexMut, Mul, Sub};
|
use std::ops::{Add, Index, IndexMut, Mul, Sub};
|
||||||
use std::sync::RwLock;
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
use crate::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
|
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -92,6 +94,14 @@ impl<T> Array2D<T> {
|
||||||
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
|
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
|
||||||
(pp.y() * width as i32 + pp.x()) as usize
|
(pp.y() * width as i32 + pp.x()) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_linear(&self, index: usize) -> &T {
|
||||||
|
&self.values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
|
||||||
|
&mut self.values[index]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Index<Point2i> for Array2D<T> {
|
impl<T> Index<Point2i> for Array2D<T> {
|
||||||
|
|
@ -305,3 +315,30 @@ impl<T> SampledGrid<T> {
|
||||||
self.max_value_convert(bounds, |v| v.clone())
|
self.max_value_convert(bounds, |v| v.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct InternCache<T> {
|
||||||
|
cache: Mutex<HashSet<Arc<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InternCache<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
cache: Mutex::new(HashSet::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, value: T) -> Arc<T> {
|
||||||
|
let mut lock = self.cache.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(existing) = lock.get(&value) {
|
||||||
|
return existing.clone(); // Returns a cheap Arc copy
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_item = Arc::new(value);
|
||||||
|
lock.insert(new_item.clone());
|
||||||
|
new_item
|
||||||
|
}
|
||||||
|
}
|
||||||
36
shared/src/utils/error.rs
Normal file
36
shared/src/utils/error.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use image_rs::{ImageError as IError, error};
|
||||||
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::image::PixelFormat;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum LlsError {
|
||||||
|
SingularMatrix,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum InversionError {
|
||||||
|
SingularMatrix,
|
||||||
|
EmptyMatrix,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LlsError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
LlsError::SingularMatrix => write!(f, "Matrix is singular and cannot be inverted."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InversionError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
InversionError::SingularMatrix => {
|
||||||
|
write!(f, "Matrix is singular and cannot be inverted.")
|
||||||
|
}
|
||||||
|
InversionError::EmptyMatrix => write!(f, "Matrix is empty and cannot be inverted."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::utils::math::{next_float_down, next_float_up};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct Interval {
|
pub struct Interval {
|
||||||
pub low: Float,
|
pub low: Float,
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use super::color::{RGB, XYZ};
|
|
||||||
use super::error::{InversionError, LlsError};
|
use super::error::{InversionError, LlsError};
|
||||||
use crate::core::pbrt::{
|
use crate::core::geometry::{Lerp, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
|
||||||
Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4, clamp_t, evaluate_polynomial,
|
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
|
||||||
lerp,
|
use crate::spectra::color::{RGB, XYZ};
|
||||||
};
|
|
||||||
use crate::geometry::{Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
|
|
||||||
use crate::utils::hash::{hash_buffer, mix_bits};
|
use crate::utils::hash::{hash_buffer, mix_bits};
|
||||||
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
||||||
|
|
||||||
|
|
@ -42,6 +39,43 @@ where
|
||||||
a * b + c
|
a * b + c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clamp<T>(val: T, low: T, high: T) -> T
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
let r: T;
|
||||||
|
if val < low {
|
||||||
|
r = low;
|
||||||
|
} else if val > high {
|
||||||
|
r = high;
|
||||||
|
} else {
|
||||||
|
r = val;
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn lerp<T, F>(t: F, a: T, b: T) -> T
|
||||||
|
where
|
||||||
|
T: Lerp<F>,
|
||||||
|
F: Copy,
|
||||||
|
{
|
||||||
|
T::lerp(t, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
||||||
|
if coeffs.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = coeffs[0];
|
||||||
|
for &c in &coeffs[1..] {
|
||||||
|
result = t.mul_add(result, c);
|
||||||
|
}
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
|
pub fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
|
||||||
where
|
where
|
||||||
|
|
@ -75,7 +109,7 @@ pub fn safe_asin<T: NumFloat>(x: T) -> T {
|
||||||
let epsilon = T::from(0.0001).unwrap();
|
let epsilon = T::from(0.0001).unwrap();
|
||||||
let one = T::one();
|
let one = T::one();
|
||||||
if x >= -(one + epsilon) && x <= one + epsilon {
|
if x >= -(one + epsilon) && x <= one + epsilon {
|
||||||
clamp_t(x, -one, one).asin()
|
clamp(x, -one, one).asin()
|
||||||
} else {
|
} else {
|
||||||
panic!("Not valid value for asin")
|
panic!("Not valid value for asin")
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +118,7 @@ pub fn safe_asin<T: NumFloat>(x: T) -> T {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn safe_acos(x: Float) -> Float {
|
pub fn safe_acos(x: Float) -> Float {
|
||||||
if (-1.001..1.001).contains(&x) {
|
if (-1.001..1.001).contains(&x) {
|
||||||
clamp_t(x, -1., 1.).asin()
|
clamp(x, -1., 1.).asin()
|
||||||
} else {
|
} else {
|
||||||
panic!("Not valid value for acos")
|
panic!("Not valid value for acos")
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +270,7 @@ pub fn smooth_step(x: Float, a: Float, b: Float) -> Float {
|
||||||
if x < a { return 0. } else { return 1. }
|
if x < a { return 0. } else { return 1. }
|
||||||
}
|
}
|
||||||
|
|
||||||
let t = clamp_t((x - a) / (b - a), 0., 1.);
|
let t = clamp((x - a) / (b - a), 0., 1.);
|
||||||
t * t * (3. - 2. * t)
|
t * t * (3. - 2. * t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1281,6 +1315,19 @@ impl<T, const N: usize> SquareMatrix<T, N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> From<&[T; 16]> for SquareMatrix<T, 4> {
|
||||||
|
fn from(flat: &[T; 16]) -> Self {
|
||||||
|
Self {
|
||||||
|
m: [
|
||||||
|
[flat[0], flat[1], flat[2], flat[3]],
|
||||||
|
[flat[4], flat[5], flat[6], flat[7]],
|
||||||
|
[flat[8], flat[9], flat[10], flat[11]],
|
||||||
|
[flat[12], flat[13], flat[14], flat[15]],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, const N: usize> SquareMatrix<T, N>
|
impl<T, const N: usize> SquareMatrix<T, N>
|
||||||
where
|
where
|
||||||
T: NumFloat + Sum + Product + Copy,
|
T: NumFloat + Sum + Product + Copy,
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -21,7 +21,7 @@ pub struct TriangleMesh {
|
||||||
impl TriangleMesh {
|
impl TriangleMesh {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_object: &Transform<Float>,
|
render_from_object: &TransformGeneric<Float>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
indices: Vec<usize>,
|
indices: Vec<usize>,
|
||||||
mut p: Vec<Point3f>,
|
mut p: Vec<Point3f>,
|
||||||
|
|
@ -108,7 +108,7 @@ pub struct BilinearPatchMesh {
|
||||||
|
|
||||||
impl BilinearPatchMesh {
|
impl BilinearPatchMesh {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_object: &Transform<Float>,
|
render_from_object: &TransformGeneric<Float>,
|
||||||
reverse_orientation: bool,
|
reverse_orientation: bool,
|
||||||
indices: Vec<usize>,
|
indices: Vec<usize>,
|
||||||
mut p: Vec<Point3f>,
|
mut p: Vec<Point3f>,
|
||||||
84
shared/src/utils/mod.rs
Normal file
84
shared/src/utils/mod.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
pub mod containers;
|
||||||
|
pub mod error;
|
||||||
|
pub mod file;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod interval;
|
||||||
|
pub mod math;
|
||||||
|
pub mod mesh;
|
||||||
|
pub mod mipmap;
|
||||||
|
pub mod parameters;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod quaternion;
|
||||||
|
pub mod rng;
|
||||||
|
pub mod sampling;
|
||||||
|
pub mod sobol;
|
||||||
|
pub mod splines;
|
||||||
|
pub mod strings;
|
||||||
|
pub mod transform;
|
||||||
|
|
||||||
|
pub use file::{read_float_file, resolve_filename};
|
||||||
|
pub use strings::*;
|
||||||
|
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn partition_slice<T, F>(data: &mut [T], predicate: F) -> usize
|
||||||
|
where
|
||||||
|
F: Fn(&T) -> bool,
|
||||||
|
{
|
||||||
|
let mut i = 0;
|
||||||
|
for j in 0..data.len() {
|
||||||
|
if predicate(&data[j]) {
|
||||||
|
data.swap(i, j);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AtomicFloat {
|
||||||
|
bits: AtomicU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomicFloat {
|
||||||
|
pub fn new(value: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
bits: AtomicU64::new(value.to_bits()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&self) -> f64 {
|
||||||
|
f64::from_bits(self.bits.load(Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&self, value: f64) {
|
||||||
|
self.bits.store(value.to_bits(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&self, value: f64) {
|
||||||
|
let mut current_bits = self.bits.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let current_val = f64::from_bits(current_bits);
|
||||||
|
let new_val = current_val + value;
|
||||||
|
let new_bits = new_val.to_bits();
|
||||||
|
|
||||||
|
match self.bits.compare_exchange_weak(
|
||||||
|
current_bits,
|
||||||
|
new_bits,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(x) => current_bits = x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AtomicFloat {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ use std::f32::consts::PI;
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use crate::core::geometry::{Vector3f, VectorLike};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::geometry::{Vector3f, VectorLike};
|
|
||||||
use crate::utils::math::{safe_asin, sinx_over_x};
|
use crate::utils::math::{safe_asin, sinx_over_x};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
|
@ -1,21 +1,41 @@
|
||||||
use super::math::safe_sqrt;
|
|
||||||
use crate::check_rare;
|
use crate::check_rare;
|
||||||
use crate::core::pbrt::{
|
use crate::core::geometry::{
|
||||||
Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t,
|
|
||||||
evaluate_polynomial, find_interval, lerp,
|
|
||||||
};
|
|
||||||
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
|
|
||||||
use crate::geometry::{
|
|
||||||
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
|
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::pbrt::{
|
||||||
|
Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
use crate::utils::math::{
|
use crate::utils::math::{
|
||||||
catmull_rom_weights, difference_of_products, logistic, newton_bisection, square,
|
catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic,
|
||||||
sum_of_products,
|
newton_bisection, safe_sqrt, square, sum_of_products,
|
||||||
};
|
};
|
||||||
use crate::utils::rng::Rng;
|
use crate::utils::rng::Rng;
|
||||||
|
use num_traits::Num;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||||
|
|
||||||
|
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
|
||||||
|
where
|
||||||
|
T: Num + Copy + PartialOrd,
|
||||||
|
{
|
||||||
|
if x < T::zero() || x > T::one() {
|
||||||
|
return T::zero();
|
||||||
|
}
|
||||||
|
(T::one() + T::one()) * lerp(x, a, b) / (a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn sample_linear<T>(u: T, a: T, b: T) -> T
|
||||||
|
// where
|
||||||
|
// T: Num,
|
||||||
|
// {
|
||||||
|
// if u == T::zero() && a == T::zero() {
|
||||||
|
// return T::zero();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// u * (a + b)
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
|
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
|
||||||
if u == 0. && a == 0. {
|
if u == 0. && a == 0. {
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|
@ -181,10 +201,10 @@ pub fn sample_spherical_rectangle(
|
||||||
let au = u[0] * (g0 + g1 - 2. * PI) + (u[0] - 1.) * (g2 + g3);
|
let au = u[0] * (g0 + g1 - 2. * PI) + (u[0] - 1.) * (g2 + g3);
|
||||||
let fu = (au.cos() * b0 - b1) / au.sin();
|
let fu = (au.cos() * b0 - b1) / au.sin();
|
||||||
let mut cu = (1. / (square(fu) + square(b0)).sqrt()).copysign(fu);
|
let mut cu = (1. / (square(fu) + square(b0)).sqrt()).copysign(fu);
|
||||||
cu = clamp_t(cu, -ONE_MINUS_EPSILON, ONE_MINUS_EPSILON);
|
cu = clamp(cu, -ONE_MINUS_EPSILON, ONE_MINUS_EPSILON);
|
||||||
|
|
||||||
let mut xu = -(cu * z0) / safe_sqrt(1. - square(cu));
|
let mut xu = -(cu * z0) / safe_sqrt(1. - square(cu));
|
||||||
xu = clamp_t(xu, x0, x1);
|
xu = clamp(xu, x0, x1);
|
||||||
|
|
||||||
// Find _xv_ along $y$ edge for spherical rectangle sample
|
// Find _xv_ along $y$ edge for spherical rectangle sample
|
||||||
let dd = (square(xu) + square(z0)).sqrt();
|
let dd = (square(xu) + square(z0)).sqrt();
|
||||||
|
|
@ -262,7 +282,7 @@ pub fn invert_spherical_rectangle_sample(
|
||||||
let v = r.to_local(p_rect - p_ref);
|
let v = r.to_local(p_rect - p_ref);
|
||||||
let mut xu = v.x();
|
let mut xu = v.x();
|
||||||
let yv = v.y();
|
let yv = v.y();
|
||||||
xu = clamp_t(xu, x0, x1);
|
xu = clamp(xu, x0, x1);
|
||||||
if xu == 0. {
|
if xu == 0. {
|
||||||
xu = 1e-10;
|
xu = 1e-10;
|
||||||
}
|
}
|
||||||
|
|
@ -304,9 +324,9 @@ pub fn invert_spherical_rectangle_sample(
|
||||||
let hvsq = [square(hv[0]), square(hv[1])];
|
let hvsq = [square(hv[0]), square(hv[1])];
|
||||||
let yz = [(hv[0] * dd) / (1. - hvsq[0]), (hv[1] * dd) / (1. - hvsq[1])];
|
let yz = [(hv[0] * dd) / (1. - hvsq[0]), (hv[1] * dd) / (1. - hvsq[1])];
|
||||||
if (yz[0] - yv).abs() < (yz[1] - yv).abs() {
|
if (yz[0] - yv).abs() < (yz[1] - yv).abs() {
|
||||||
Point2f::new(clamp_t(u0, 0., 1.), u1[0])
|
Point2f::new(clamp(u0, 0., 1.), u1[0])
|
||||||
} else {
|
} else {
|
||||||
Point2f::new(clamp_t(u0, 0., 1.), u1[1])
|
Point2f::new(clamp(u0, 0., 1.), u1[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,7 +378,7 @@ pub fn sample_spherical_triangle(
|
||||||
let k2 = sin_phi - sin_alpha * a.dot(b);
|
let k2 = sin_phi - sin_alpha * a.dot(b);
|
||||||
let mut cos_bp = (k2 + difference_of_products(k2, cos_phi, k1, sin_phi) * cos_alpha)
|
let mut cos_bp = (k2 + difference_of_products(k2, cos_phi, k1, sin_phi) * cos_alpha)
|
||||||
/ ((sum_of_products(k2, sin_phi, k1, cos_phi)) * sin_alpha);
|
/ ((sum_of_products(k2, sin_phi, k1, cos_phi)) * sin_alpha);
|
||||||
cos_bp = clamp_t(cos_bp, -1., 1.);
|
cos_bp = clamp(cos_bp, -1., 1.);
|
||||||
// Sample c' along the arc between a and c
|
// Sample c' along the arc between a and c
|
||||||
let sin_bp = safe_sqrt(1. - square(cos_bp));
|
let sin_bp = safe_sqrt(1. - square(cos_bp));
|
||||||
let cp = cos_bp * a + sin_bp * c.gram_schmidt(a).normalize();
|
let cp = cos_bp * a + sin_bp * c.gram_schmidt(a).normalize();
|
||||||
|
|
@ -376,8 +396,8 @@ pub fn sample_spherical_triangle(
|
||||||
|
|
||||||
let mut b1 = s.dot(s1) * inv_divisor;
|
let mut b1 = s.dot(s1) * inv_divisor;
|
||||||
let mut b2 = w.dot(s.cross(e1)) * inv_divisor;
|
let mut b2 = w.dot(s.cross(e1)) * inv_divisor;
|
||||||
b1 = clamp_t(b1, 0., 1.);
|
b1 = clamp(b1, 0., 1.);
|
||||||
b2 = clamp_t(b2, 0., 1.);
|
b2 = clamp(b2, 0., 1.);
|
||||||
if b1 + b2 > 1. {
|
if b1 + b2 > 1. {
|
||||||
b1 /= b1 + b2;
|
b1 /= b1 + b2;
|
||||||
b2 /= b1 + b2;
|
b2 /= b1 + b2;
|
||||||
|
|
@ -443,7 +463,7 @@ pub fn invert_spherical_triangle_sample(
|
||||||
|
|
||||||
let u1 = (1.0 - w.dot(b)) / (1.0 - cp.dot(b));
|
let u1 = (1.0 - w.dot(b)) / (1.0 - cp.dot(b));
|
||||||
|
|
||||||
Some(Point2f::new(clamp_t(u0, 0.0, 1.0), clamp_t(u1, 0.0, 1.0)))
|
Some(Point2f::new(clamp(u0, 0.0, 1.0), clamp(u1, 0.0, 1.0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_catmull_rom(
|
pub fn sample_catmull_rom(
|
||||||
|
|
@ -611,7 +631,7 @@ pub fn sample_trimmed_logistic(u: Float, s: Float, a: Float, b: Float) -> Float
|
||||||
let p = |val: Float| invert_logistic_sample(val, s);
|
let p = |val: Float| invert_logistic_sample(val, s);
|
||||||
let u = lerp(u, p(a), p(b));
|
let u = lerp(u, p(a), p(b));
|
||||||
let x = sample_logistic(u, s);
|
let x = sample_logistic(u, s);
|
||||||
clamp_t(x, a, b)
|
clamp(x, a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uniform_hemisphere_pdf() -> Float {
|
pub fn uniform_hemisphere_pdf() -> Float {
|
||||||
|
|
@ -1027,7 +1047,7 @@ impl WindowedPiecewiseConstant2D {
|
||||||
numerator / denominator
|
numerator / denominator
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = crate::core::pbrt::lerp(t, min, max);
|
let res = lerp(t, min, max);
|
||||||
res.clamp(min, max)
|
res.clamp(min, max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
|
||||||
use crate::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::utils::math::lerp;
|
||||||
use num_traits::Num;
|
use num_traits::Num;
|
||||||
|
|
||||||
fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {
|
fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {
|
||||||
5
shared/src/utils/strings.rs
Normal file
5
shared/src/utils/strings.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
use unicode_normalization::UnicodeNormalization;
|
||||||
|
|
||||||
|
pub fn normalize_utf8(input: &str) -> String {
|
||||||
|
input.nfc().collect::<String>()
|
||||||
|
}
|
||||||
|
|
@ -5,26 +5,26 @@ use std::iter::{Product, Sum};
|
||||||
use std::ops::{Add, Div, Index, IndexMut, Mul};
|
use std::ops::{Add, Div, Index, IndexMut, Mul};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::color::{RGB, XYZ};
|
use super::math::{radians, safe_acos, SquareMatrix};
|
||||||
use super::math::{SquareMatrix, radians, safe_acos};
|
|
||||||
use super::quaternion::Quaternion;
|
use super::quaternion::Quaternion;
|
||||||
use crate::core::interaction::{
|
use crate::core::geometry::{
|
||||||
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
|
||||||
};
|
|
||||||
use crate::core::pbrt::{Float, gamma};
|
|
||||||
use crate::geometry::{
|
|
||||||
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
|
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
|
||||||
VectorLike,
|
VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::interaction::{
|
||||||
|
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{gamma, Float};
|
||||||
|
use crate::spectra::{RGB, XYZ};
|
||||||
use crate::utils::error::InversionError;
|
use crate::utils::error::InversionError;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Transform<T: NumFloat> {
|
pub struct TransformGeneric<T: NumFloat> {
|
||||||
m: SquareMatrix<T, 4>,
|
m: SquareMatrix<T, 4>,
|
||||||
m_inv: SquareMatrix<T, 4>,
|
m_inv: SquareMatrix<T, 4>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: NumFloat + Sum + Product> Transform<T> {
|
impl<T: NumFloat + Sum + Product> TransformGeneric<T> {
|
||||||
pub fn new(m: SquareMatrix<T, 4>, m_inv: SquareMatrix<T, 4>) -> Self {
|
pub fn new(m: SquareMatrix<T, 4>, m_inv: SquareMatrix<T, 4>) -> Self {
|
||||||
Self { m, m_inv }
|
Self { m, m_inv }
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +34,12 @@ impl<T: NumFloat + Sum + Product> Transform<T> {
|
||||||
Ok(Self { m, m_inv: inv })
|
Ok(Self { m, m_inv: inv })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_flat(flat: &[T; 16]) -> Result<Self, InversionError> {
|
||||||
|
let m: SquareMatrix<T, 4> = SquareMatrix::from(flat);
|
||||||
|
let inv = m.inverse()?;
|
||||||
|
Ok(Self { m, m_inv: inv })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn identity() -> Self {
|
pub fn identity() -> Self {
|
||||||
let m: SquareMatrix<T, 4> = SquareMatrix::identity();
|
let m: SquareMatrix<T, 4> = SquareMatrix::identity();
|
||||||
Self { m, m_inv: m }
|
Self { m, m_inv: m }
|
||||||
|
|
@ -83,13 +89,15 @@ impl<T: NumFloat + Sum + Product> Transform<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: NumFloat + Sum + Product> Default for Transform<T> {
|
impl<T: NumFloat + Sum + Product> Default for TransformGeneric<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::identity()
|
Self::identity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transform<Float> {
|
pub type Transform = TransformGeneric<Float>;
|
||||||
|
|
||||||
|
impl TransformGeneric<Float> {
|
||||||
pub fn apply_to_point(&self, p: Point3f) -> Point3f {
|
pub fn apply_to_point(&self, p: Point3f) -> Point3f {
|
||||||
let x = p.x();
|
let x = p.x();
|
||||||
let y = p.y();
|
let y = p.y();
|
||||||
|
|
@ -407,7 +415,7 @@ impl Transform<Float> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate(delta: Vector3f) -> Self {
|
pub fn translate(delta: Vector3f) -> Self {
|
||||||
Transform {
|
TransformGeneric {
|
||||||
m: SquareMatrix::new([
|
m: SquareMatrix::new([
|
||||||
[1.0, 0.0, 0.0, delta.x()],
|
[1.0, 0.0, 0.0, delta.x()],
|
||||||
[0.0, 1.0, 0.0, delta.y()],
|
[0.0, 1.0, 0.0, delta.y()],
|
||||||
|
|
@ -440,7 +448,11 @@ impl Transform<Float> {
|
||||||
Self { m, m_inv }
|
Self { m, m_inv }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perspective(fov: Float, n: Float, f: Float) -> Result<Transform<Float>, InversionError> {
|
pub fn perspective(
|
||||||
|
fov: Float,
|
||||||
|
n: Float,
|
||||||
|
f: Float,
|
||||||
|
) -> Result<TransformGeneric<Float>, InversionError> {
|
||||||
let persp: SquareMatrix<Float, 4> = SquareMatrix::new([
|
let persp: SquareMatrix<Float, 4> = SquareMatrix::new([
|
||||||
[1., 0., 0., 0.],
|
[1., 0., 0., 0.],
|
||||||
[0., 1., 0., 0.],
|
[0., 1., 0., 0.],
|
||||||
|
|
@ -448,22 +460,23 @@ impl Transform<Float> {
|
||||||
[0., 0., 1., 0.],
|
[0., 0., 1., 0.],
|
||||||
]);
|
]);
|
||||||
let inv_tan_ang = 1. / (radians(fov) / 2.).tan();
|
let inv_tan_ang = 1. / (radians(fov) / 2.).tan();
|
||||||
let persp_transform = Transform::from_matrix(persp)?;
|
let persp_transform = TransformGeneric::from_matrix(persp)?;
|
||||||
Ok(Transform::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform)
|
Ok(TransformGeneric::scale(inv_tan_ang, inv_tan_ang, 1.) * persp_transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn orthographic(z_near: Float, z_far: Float) -> Self {
|
pub fn orthographic(z_near: Float, z_far: Float) -> Self {
|
||||||
Self::scale(1., 1., 1. / (z_far - z_near)) * Self::translate(Vector3f::new(0., 0., -z_near))
|
Self::scale(1., 1., 1. / (z_far - z_near)) * Self::translate(Vector3f::new(0., 0., -z_near))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate_around_axis(theta: Float, axis: Vector3f) -> Self {
|
pub fn rotate_around_axis(theta: Float, axis: impl Into<Vector3f>) -> Self {
|
||||||
let sin_theta = theta.to_radians().sin();
|
let sin_theta = theta.to_radians().sin();
|
||||||
let cos_theta = theta.to_radians().cos();
|
let cos_theta = theta.to_radians().cos();
|
||||||
Transform::rotate(sin_theta, cos_theta, axis)
|
TransformGeneric::rotate(sin_theta, cos_theta, axis)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate(sin_theta: Float, cos_theta: Float, axis: Vector3f) -> Self {
|
pub fn rotate(sin_theta: Float, cos_theta: Float, axis: impl Into<Vector3f>) -> Self {
|
||||||
let a = axis.normalize();
|
let vec_axis: Vector3f = axis.into();
|
||||||
|
let a = vec_axis.normalize();
|
||||||
let mut m: SquareMatrix<Float, 4> = SquareMatrix::default();
|
let mut m: SquareMatrix<Float, 4> = SquareMatrix::default();
|
||||||
m[0][0] = a.x() * a.x() + (1. - a.x() * a.x()) * cos_theta;
|
m[0][0] = a.x() * a.x() + (1. - a.x() * a.x()) * cos_theta;
|
||||||
m[0][1] = a.x() * a.y() * (1. - cos_theta) - a.z() * sin_theta;
|
m[0][1] = a.x() * a.y() * (1. - cos_theta) - a.z() * sin_theta;
|
||||||
|
|
@ -479,7 +492,7 @@ impl Transform<Float> {
|
||||||
m[2][1] = a.y() * a.z() * (1. - cos_theta) + a.x() * sin_theta;
|
m[2][1] = a.y() * a.z() * (1. - cos_theta) + a.x() * sin_theta;
|
||||||
m[2][2] = a.z() * a.z() + (1. - a.z() * a.z()) * cos_theta;
|
m[2][2] = a.z() * a.z() + (1. - a.z() * a.z()) * cos_theta;
|
||||||
m[2][3] = 0.;
|
m[2][3] = 0.;
|
||||||
Transform::new(m, m.transpose())
|
TransformGeneric::new(m, m.transpose())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate_from_to(from: Vector3f, to: Vector3f) -> Self {
|
pub fn rotate_from_to(from: Vector3f, to: Vector3f) -> Self {
|
||||||
|
|
@ -506,7 +519,7 @@ impl Transform<Float> {
|
||||||
+ 4. * uv / (uu * vv) * v[i] * u[j];
|
+ 4. * uv / (uu * vv) * v[i] * u[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Transform::new(r, r.transpose())
|
TransformGeneric::new(r, r.transpose())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate_x(theta: Float) -> Self {
|
pub fn rotate_x(theta: Float) -> Self {
|
||||||
|
|
@ -609,47 +622,47 @@ impl Transform<Float> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: num_traits::Float> PartialEq for Transform<T> {
|
impl<T: num_traits::Float> PartialEq for TransformGeneric<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.m == other.m && self.m_inv == other.m_inv
|
self.m == other.m && self.m_inv == other.m_inv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: NumFloat> Mul for Transform<T> {
|
impl<T: NumFloat> Mul for TransformGeneric<T> {
|
||||||
type Output = Transform<T>;
|
type Output = TransformGeneric<T>;
|
||||||
|
|
||||||
fn mul(self, rhs: Transform<T>) -> Self::Output {
|
fn mul(self, rhs: TransformGeneric<T>) -> Self::Output {
|
||||||
Transform {
|
TransformGeneric {
|
||||||
m: self.m * rhs.m,
|
m: self.m * rhs.m,
|
||||||
m_inv: rhs.m_inv * self.m_inv,
|
m_inv: rhs.m_inv * self.m_inv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b, T> Mul<&'b Transform<T>> for &Transform<T>
|
impl<'b, T> Mul<&'b TransformGeneric<T>> for &TransformGeneric<T>
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: NumFloat,
|
||||||
{
|
{
|
||||||
type Output = Transform<T>;
|
type Output = TransformGeneric<T>;
|
||||||
fn mul(self, rhs: &'b Transform<T>) -> Self::Output {
|
fn mul(self, rhs: &'b TransformGeneric<T>) -> Self::Output {
|
||||||
Transform {
|
TransformGeneric {
|
||||||
m: self.m * rhs.m,
|
m: self.m * rhs.m,
|
||||||
m_inv: rhs.m_inv * self.m_inv,
|
m_inv: rhs.m_inv * self.m_inv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mul<Transform<Float>> for Float {
|
impl Mul<TransformGeneric<Float>> for Float {
|
||||||
type Output = Transform<Float>;
|
type Output = TransformGeneric<Float>;
|
||||||
fn mul(self, rhs: Transform<Float>) -> Self::Output {
|
fn mul(self, rhs: TransformGeneric<Float>) -> Self::Output {
|
||||||
Transform {
|
TransformGeneric {
|
||||||
m: rhs.m * self,
|
m: rhs.m * self,
|
||||||
m_inv: rhs.m_inv * self,
|
m_inv: rhs.m_inv * self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Mul<Point<T, 3>> for Transform<T>
|
impl<T> Mul<Point<T, 3>> for TransformGeneric<T>
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: NumFloat,
|
||||||
{
|
{
|
||||||
|
|
@ -668,7 +681,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Mul<Vector<T, 3>> for Transform<T>
|
impl<T> Mul<Vector<T, 3>> for TransformGeneric<T>
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: NumFloat,
|
||||||
{
|
{
|
||||||
|
|
@ -687,7 +700,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Mul<Normal<T, 3>> for Transform<T>
|
impl<T> Mul<Normal<T, 3>> for TransformGeneric<T>
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: NumFloat,
|
||||||
{
|
{
|
||||||
|
|
@ -705,7 +718,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Quaternion> for Transform<Float> {
|
impl From<Quaternion> for TransformGeneric<Float> {
|
||||||
fn from(q: Quaternion) -> Self {
|
fn from(q: Quaternion) -> Self {
|
||||||
let xx = q.v.x() * q.v.x();
|
let xx = q.v.x() * q.v.x();
|
||||||
let yy = q.v.y() * q.v.y();
|
let yy = q.v.y() * q.v.y();
|
||||||
|
|
@ -737,7 +750,7 @@ impl From<Quaternion> for Transform<Float> {
|
||||||
// For a pure rotation, the inverse is the transpose.
|
// For a pure rotation, the inverse is the transpose.
|
||||||
let m_inv_sq = m_sq.transpose();
|
let m_inv_sq = m_sq.transpose();
|
||||||
|
|
||||||
Transform::new(m_sq, m_inv_sq)
|
TransformGeneric::new(m_sq, m_inv_sq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,10 +777,10 @@ impl DerivativeTerm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct AnimatedTransform {
|
pub struct AnimatedTransform {
|
||||||
pub start_transform: Transform<Float>,
|
pub start_transform: Transform,
|
||||||
pub end_transform: Transform<Float>,
|
pub end_transform: Transform,
|
||||||
pub start_time: Float,
|
pub start_time: Float,
|
||||||
pub end_time: Float,
|
pub end_time: Float,
|
||||||
actually_animated: bool,
|
actually_animated: bool,
|
||||||
|
|
@ -783,10 +796,14 @@ pub struct AnimatedTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnimatedTransform {
|
impl AnimatedTransform {
|
||||||
|
pub fn from_transform(t: &Transform) -> Self {
|
||||||
|
Self::new(t, 0., t, 1.)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
start_transform: &Transform<Float>,
|
start_transform: &Transform,
|
||||||
start_time: Float,
|
start_time: Float,
|
||||||
end_transform: &Transform<Float>,
|
end_transform: &Transform,
|
||||||
end_time: Float,
|
end_time: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let actually_animated = start_transform != end_transform;
|
let actually_animated = start_transform != end_transform;
|
||||||
|
|
@ -812,12 +829,14 @@ impl AnimatedTransform {
|
||||||
|
|
||||||
let (t0, r_temp, s0) = start_transform.decompose();
|
let (t0, r_temp, s0) = start_transform.decompose();
|
||||||
let mut rm = r_temp;
|
let mut rm = r_temp;
|
||||||
let scale_transform = Transform::from_matrix(rm).expect("Scaling matrix should invertible");
|
let scale_transform =
|
||||||
|
TransformGeneric::from_matrix(rm).expect("Scaling matrix should invertible");
|
||||||
let r0 = scale_transform.to_quaternion();
|
let r0 = scale_transform.to_quaternion();
|
||||||
let (t1, r_temp, s1) = end_transform.decompose();
|
let (t1, r_temp, s1) = end_transform.decompose();
|
||||||
rm = r_temp;
|
rm = r_temp;
|
||||||
|
|
||||||
let scale_transform = Transform::from_matrix(rm).expect("Scaling matrix should invertible");
|
let scale_transform =
|
||||||
|
TransformGeneric::from_matrix(rm).expect("Scaling matrix should invertible");
|
||||||
let mut r1 = scale_transform.to_quaternion();
|
let mut r1 = scale_transform.to_quaternion();
|
||||||
|
|
||||||
if r0.dot(r1) < 0. {
|
if r0.dot(r1) < 0. {
|
||||||
|
|
@ -1993,7 +2012,7 @@ impl AnimatedTransform {
|
||||||
t.apply_to_interaction(si)
|
t.apply_to_interaction(si)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpolate(&self, time: Float) -> Transform<Float> {
|
pub fn interpolate(&self, time: Float) -> TransformGeneric<Float> {
|
||||||
if !self.actually_animated || time <= self.start_time {
|
if !self.actually_animated || time <= self.start_time {
|
||||||
return self.start_transform;
|
return self.start_transform;
|
||||||
}
|
}
|
||||||
|
|
@ -2008,9 +2027,9 @@ impl AnimatedTransform {
|
||||||
|
|
||||||
let scale = (1.0 - dt) * self.s[0] + dt * self.s[1];
|
let scale = (1.0 - dt) * self.s[0] + dt * self.s[1];
|
||||||
let scale_transform =
|
let scale_transform =
|
||||||
Transform::from_matrix(scale).expect("Scale matrix is not inversible");
|
TransformGeneric::from_matrix(scale).expect("Scale matrix is not inversible");
|
||||||
|
|
||||||
Transform::translate(trans) * Transform::from(rotate) * scale_transform
|
TransformGeneric::translate(trans) * TransformGeneric::from(rotate) * scale_transform
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_inverse_point(&self, p: Point3f, time: Float) -> Point3f {
|
pub fn apply_inverse_point(&self, p: Point3f, time: Float) -> Point3f {
|
||||||
|
|
@ -2042,15 +2061,22 @@ impl AnimatedTransform {
|
||||||
.apply_to_bounds(*b)
|
.apply_to_bounds(*b)
|
||||||
.union(self.end_transform.apply_to_bounds(*b))
|
.union(self.end_transform.apply_to_bounds(*b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_animated(&self) -> bool {
|
||||||
|
self.actually_animated
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn look_at(
|
pub fn look_at(
|
||||||
pos: Point3f,
|
pos: impl Into<Point3f>,
|
||||||
look: Point3f,
|
look: impl Into<Point3f>,
|
||||||
up: Point3f,
|
up: impl Into<Point3f>,
|
||||||
) -> Result<Transform<Float>, InversionError> {
|
) -> Result<TransformGeneric<Float>, InversionError> {
|
||||||
let mut world_from_camera: SquareMatrix<Float, 4> = SquareMatrix::default();
|
let mut world_from_camera: SquareMatrix<Float, 4> = SquareMatrix::default();
|
||||||
// Initialize fourth column of viewing matrix
|
// Initialize fourth column of viewing matrix
|
||||||
|
let pos: Point3f = pos.into();
|
||||||
|
let look: Point3f = look.into();
|
||||||
|
let up: Point3f = up.into();
|
||||||
world_from_camera[0][3] = pos.x();
|
world_from_camera[0][3] = pos.x();
|
||||||
world_from_camera[1][3] = pos.y();
|
world_from_camera[1][3] = pos.y();
|
||||||
world_from_camera[2][3] = pos.z();
|
world_from_camera[2][3] = pos.z();
|
||||||
|
|
@ -2085,5 +2111,5 @@ pub fn look_at(
|
||||||
world_from_camera[3][2] = 0.;
|
world_from_camera[3][2] = 0.;
|
||||||
|
|
||||||
let camera_from_world = world_from_camera.inverse()?;
|
let camera_from_world = world_from_camera.inverse()?;
|
||||||
Ok(Transform::new(camera_from_world, world_from_camera))
|
Ok(TransformGeneric::new(camera_from_world, world_from_camera))
|
||||||
}
|
}
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait};
|
|
||||||
use crate::core::film::FilmTrait;
|
|
||||||
use crate::core::pbrt::{Float, PI};
|
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
|
||||||
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct EquiRectangularMapping;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Mapping {
|
|
||||||
EquiRectangular(EquiRectangularMapping),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SphericalCamera {
|
|
||||||
pub base: CameraBase,
|
|
||||||
pub screen: Bounds2f,
|
|
||||||
pub lens_radius: Float,
|
|
||||||
pub focal_distance: Float,
|
|
||||||
pub mapping: Mapping,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CameraTrait for SphericalCamera {
|
|
||||||
fn base(&self) -> &CameraBase {
|
|
||||||
&self.base
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
|
||||||
self.base.init_metadata(metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_ray(
|
|
||||||
&self,
|
|
||||||
sample: CameraSample,
|
|
||||||
_lamdba: &SampledWavelengths,
|
|
||||||
) -> Option<CameraRay> {
|
|
||||||
// Compute spherical camera ray direction
|
|
||||||
let mut uv = Point2f::new(
|
|
||||||
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
|
||||||
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
|
|
||||||
);
|
|
||||||
let dir: Vector3f;
|
|
||||||
if self.mapping == Mapping::EquiRectangular(EquiRectangularMapping) {
|
|
||||||
// Compute ray direction using equirectangular mapping
|
|
||||||
let theta = PI * uv[1];
|
|
||||||
let phi = 2. * PI * uv[0];
|
|
||||||
dir = spherical_direction(theta.sin(), theta.cos(), phi);
|
|
||||||
} else {
|
|
||||||
// Compute ray direction using equal area mapping
|
|
||||||
uv = wrap_equal_area_square(&mut uv);
|
|
||||||
dir = equal_area_square_to_sphere(uv);
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut dir.y(), &mut dir.z());
|
|
||||||
|
|
||||||
let ray = Ray::new(
|
|
||||||
Point3f::new(0., 0., 0.),
|
|
||||||
dir,
|
|
||||||
Some(self.sample_time(sample.time)),
|
|
||||||
self.base().medium.clone(),
|
|
||||||
);
|
|
||||||
Some(CameraRay {
|
|
||||||
ray: self.render_from_camera(&ray, &mut None),
|
|
||||||
weight: SampledSpectrum::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
|
|
||||||
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
|
|
||||||
use crate::utils::transform::Transform;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DirectionCone {
|
|
||||||
pub w: Vector3f,
|
|
||||||
pub cos_theta: Float,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DirectionCone {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
w: Vector3f::zero(),
|
|
||||||
cos_theta: Float::INFINITY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectionCone {
|
|
||||||
pub fn new(w: Vector3f, cos_theta: Float) -> Self {
|
|
||||||
Self {
|
|
||||||
w: w.normalize(),
|
|
||||||
cos_theta,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_from_vector(w: Vector3f) -> Self {
|
|
||||||
Self::new(w, 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.cos_theta == Float::INFINITY
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn entire_sphere() -> Self {
|
|
||||||
Self::new(Vector3f::new(0., 0., 1.), -1.)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn closest_vector_income(&self, wt: Vector3f) -> Vector3f {
|
|
||||||
let wp = wt.normalize();
|
|
||||||
let w = self.w;
|
|
||||||
if wp.dot(w) > self.cos_theta {
|
|
||||||
return wp;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sin_theta = -safe_sqrt(1. - self.cos_theta * self.cos_theta);
|
|
||||||
let a = wp.cross(w);
|
|
||||||
self.cos_theta * w
|
|
||||||
+ sin_theta / a.norm()
|
|
||||||
* Vector3f::new(
|
|
||||||
w.x()
|
|
||||||
* (wp.y() * w.y() + wp.z() * w.z()
|
|
||||||
- wp.x() * (square(w.y() + square(w.z())))),
|
|
||||||
w.y()
|
|
||||||
* (wp.x() * w.x() + wp.z() * w.z()
|
|
||||||
- wp.y() * (square(w.x() + square(w.z())))),
|
|
||||||
w.z()
|
|
||||||
* (wp.x() * w.x() + wp.y() * w.y()
|
|
||||||
- wp.z() * (square(w.x() + square(w.y())))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inside(d: &DirectionCone, w: Vector3f) -> bool {
|
|
||||||
!d.is_empty() && d.w.dot(w.normalize()) > d.cos_theta
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bound_subtended_directions(b: &Bounds3f, p: Point3f) -> DirectionCone {
|
|
||||||
let (p_center, radius) = b.bounding_sphere();
|
|
||||||
if p.distance_squared(p_center) < square(radius) {
|
|
||||||
return DirectionCone::entire_sphere();
|
|
||||||
}
|
|
||||||
|
|
||||||
let w = (p_center - p).normalize();
|
|
||||||
let sin2_theta_max = square(radius) / p_center.distance_squared(p);
|
|
||||||
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
|
|
||||||
DirectionCone::new(w, cos_theta_max)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union(a: &DirectionCone, b: &DirectionCone) -> DirectionCone {
|
|
||||||
if a.is_empty() {
|
|
||||||
return b.clone();
|
|
||||||
}
|
|
||||||
if b.is_empty() {
|
|
||||||
return a.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the cases where one cone is inside the other
|
|
||||||
let theta_a = safe_acos(a.cos_theta);
|
|
||||||
let theta_b = safe_acos(b.cos_theta);
|
|
||||||
let theta_d = a.w.angle_between(b.w);
|
|
||||||
|
|
||||||
if (theta_d + theta_b).min(PI) <= theta_b {
|
|
||||||
return a.clone();
|
|
||||||
}
|
|
||||||
if (theta_d + theta_a).min(PI) <= theta_a {
|
|
||||||
return b.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the spread angle of the merged cone, $\theta_o$
|
|
||||||
let theta_o = (theta_a + theta_d + theta_b) / 2.;
|
|
||||||
if theta_o >= PI {
|
|
||||||
return DirectionCone::entire_sphere();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the merged cone's axis and return cone union
|
|
||||||
let theta_r = theta_o - theta_a;
|
|
||||||
let wr = a.w.cross(b.w);
|
|
||||||
if wr.norm_squared() >= 0. {
|
|
||||||
return DirectionCone::entire_sphere();
|
|
||||||
}
|
|
||||||
|
|
||||||
let w = Transform::rotate_around_axis(degrees(theta_r), wr).apply_to_vector(a.w);
|
|
||||||
DirectionCone::new(w, theta_o.cos())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
pub mod bounds;
|
|
||||||
pub mod cone;
|
|
||||||
pub mod primitives;
|
|
||||||
pub mod ray;
|
|
||||||
pub mod traits;
|
|
||||||
|
|
||||||
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
|
|
||||||
pub use self::cone::DirectionCone;
|
|
||||||
pub use self::primitives::{
|
|
||||||
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i,
|
|
||||||
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i,
|
|
||||||
};
|
|
||||||
pub use self::ray::{Ray, RayDifferential};
|
|
||||||
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, PI, clamp_t};
|
|
||||||
use crate::utils::math::square;
|
|
||||||
use num_traits::Float as NumFloat;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn min<T: PartialOrd>(a: T, b: T) -> T {
|
|
||||||
if a < b { a } else { b }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn max<T: PartialOrd>(a: T, b: T) -> T {
|
|
||||||
if a > b { a } else { b }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cos_theta(w: Vector3f) -> Float {
|
|
||||||
w.z()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn abs_cos_theta(w: Vector3f) -> Float {
|
|
||||||
w.z().abs()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn cos2_theta(w: Vector3f) -> Float {
|
|
||||||
square(w.z())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn sin2_theta(w: Vector3f) -> Float {
|
|
||||||
0_f32.max(1. - cos2_theta(w))
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn sin_theta(w: Vector3f) -> Float {
|
|
||||||
sin2_theta(w).sqrt()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn tan_theta(w: Vector3f) -> Float {
|
|
||||||
sin_theta(w) / cos_theta(w)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn tan2_theta(w: Vector3f) -> Float {
|
|
||||||
sin2_theta(w) / cos2_theta(w)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn cos_phi(w: Vector3f) -> Float {
|
|
||||||
let sin_theta = sin_theta(w);
|
|
||||||
if sin_theta == 0. {
|
|
||||||
1.
|
|
||||||
} else {
|
|
||||||
clamp_t(w.x() / sin_theta, -1., 1.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn sin_phi(w: Vector3f) -> Float {
|
|
||||||
let sin_theta = sin_theta(w);
|
|
||||||
if sin_theta == 0. {
|
|
||||||
0.
|
|
||||||
} else {
|
|
||||||
clamp_t(w.y() / sin_theta, -1., 1.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn same_hemisphere(w: Vector3f, wp: Vector3f) -> bool {
|
|
||||||
w.z() * wp.z() > 0.
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_direction(sin_theta: Float, cos_theta: Float, phi: Float) -> Vector3f {
|
|
||||||
Vector3f::new(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_triangle_area(a: Vector3f, b: Vector3f, c: Vector3f) -> Float {
|
|
||||||
(2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -> Float {
|
|
||||||
let mut axb = a.cross(b);
|
|
||||||
let mut bxc = b.cross(c);
|
|
||||||
let mut cxd = c.cross(d);
|
|
||||||
let mut dxa = d.cross(a);
|
|
||||||
if axb.norm_squared() == 0.
|
|
||||||
|| bxc.norm_squared() == 0.
|
|
||||||
|| cxd.norm_squared() == 0.
|
|
||||||
|| dxa.norm_squared() == 0.
|
|
||||||
{
|
|
||||||
return 0.;
|
|
||||||
}
|
|
||||||
axb = axb.normalize();
|
|
||||||
bxc = bxc.normalize();
|
|
||||||
cxd = cxd.normalize();
|
|
||||||
dxa = dxa.normalize();
|
|
||||||
|
|
||||||
let alpha = dxa.angle_between(-axb);
|
|
||||||
let beta = axb.angle_between(-bxc);
|
|
||||||
let gamma = bxc.angle_between(-cxd);
|
|
||||||
let delta = cxd.angle_between(-dxa);
|
|
||||||
|
|
||||||
(alpha + beta + gamma + delta - 2. * PI).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_theta(v: Vector3f) -> Float {
|
|
||||||
clamp_t(v.z(), -1.0, 1.0).acos()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_phi(v: Vector3f) -> Float {
|
|
||||||
let p = v.y().atan2(v.x());
|
|
||||||
if p < 0.0 { p + 2.0 * PI } else { p }
|
|
||||||
}
|
|
||||||
|
|
@ -1,344 +0,0 @@
|
||||||
use super::{Float, NumFloat};
|
|
||||||
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
|
|
||||||
use crate::core::pbrt::lerp;
|
|
||||||
use crate::geometry::traits::{Sqrt, VectorLike};
|
|
||||||
use crate::geometry::{max, min};
|
|
||||||
use crate::utils::interval::Interval;
|
|
||||||
use num_traits::{Bounded, Num};
|
|
||||||
use std::mem;
|
|
||||||
use std::ops::{Add, Div, DivAssign, Mul, Sub};
|
|
||||||
|
|
||||||
// AABB BOUNDING BOXES
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct Bounds<T, const N: usize> {
|
|
||||||
pub p_min: Point<T, N>,
|
|
||||||
pub p_max: Point<T, N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
|
|
||||||
type Item = &'a Point<T, N>;
|
|
||||||
type IntoIter = std::array::IntoIter<&'a Point<T, N>, 2>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
[&self.p_min, &self.p_max].into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Bounds<T, N>
|
|
||||||
where
|
|
||||||
T: Num + PartialOrd + Copy,
|
|
||||||
{
|
|
||||||
pub fn from_point(p: Point<T, N>) -> Self {
|
|
||||||
Self { p_min: p, p_max: p }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_points(p1: Point<T, N>, p2: Point<T, N>) -> Self {
|
|
||||||
let mut p_min_arr = [T::zero(); N];
|
|
||||||
let mut p_max_arr = [T::zero(); N];
|
|
||||||
|
|
||||||
for i in 0..N {
|
|
||||||
if p1[i] < p2[i] {
|
|
||||||
p_min_arr[i] = p1[i];
|
|
||||||
p_max_arr[i] = p2[i];
|
|
||||||
} else {
|
|
||||||
p_min_arr[i] = p2[i];
|
|
||||||
p_max_arr[i] = p1[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
p_min: Point(p_min_arr),
|
|
||||||
p_max: Point(p_max_arr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union_point(self, p: Point<T, N>) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
for i in 0..N {
|
|
||||||
p_min[i] = min(p_min[i], p[i]);
|
|
||||||
p_max[i] = max(p_max[i], p[i]);
|
|
||||||
}
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union(self, b2: Self) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
for i in 0..N {
|
|
||||||
p_min[i] = min(p_min[i], b2.p_min[i]);
|
|
||||||
p_max[i] = max(p_max[i], b2.p_max[i]);
|
|
||||||
}
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diagonal(&self) -> Vector<T, N> {
|
|
||||||
self.p_max - self.p_min
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn centroid(&self) -> Point<T, N> {
|
|
||||||
let two = T::one() + T::one();
|
|
||||||
self.p_min + (self.diagonal() / two)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn volume(&self) -> T {
|
|
||||||
let d = self.diagonal();
|
|
||||||
d.0.iter().fold(T::one(), |acc, &val| acc * val)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand(&self, delta: T) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
p_min = p_min - Vector::fill(delta);
|
|
||||||
p_max = p_max + Vector::fill(delta);
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
|
|
||||||
let mut results_arr = [T::zero(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
results_arr[i] = lerp(t[i], self.p_min[i], self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
Point(results_arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_dimension(&self) -> usize
|
|
||||||
where
|
|
||||||
Point<T, N>: Sub<Output = Vector<T, N>>,
|
|
||||||
{
|
|
||||||
let d = self.diagonal();
|
|
||||||
let mut max_dim = 0;
|
|
||||||
let mut max_span = d[0];
|
|
||||||
|
|
||||||
for i in 1..N {
|
|
||||||
if d[i] > max_span {
|
|
||||||
max_span = d[i];
|
|
||||||
max_dim = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_dim
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset(&self, p: &Point<T, N>) -> Vector<T, N>
|
|
||||||
where
|
|
||||||
Point<T, N>: Sub<Output = Vector<T, N>>,
|
|
||||||
Vector<T, N>: DivAssign<T>,
|
|
||||||
{
|
|
||||||
let mut o = *p - self.p_min;
|
|
||||||
let d = self.diagonal();
|
|
||||||
for i in 0..N {
|
|
||||||
if d[i] > T::zero() {
|
|
||||||
o[i] = o[i] / d[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn corner(&self, corner_index: usize) -> Point<T, N> {
|
|
||||||
Point(std::array::from_fn(|i| {
|
|
||||||
if (corner_index >> i) & 1 == 1 {
|
|
||||||
self.p_max[i]
|
|
||||||
} else {
|
|
||||||
self.p_min[i]
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn overlaps(&self, rhs: &Self) -> bool {
|
|
||||||
for i in 0..N {
|
|
||||||
if self.p_max[i] < rhs.p_min[i] || self.p_min[i] > rhs.p_max[i] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, p: Point<T, N>) -> bool {
|
|
||||||
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_exclusive(&self, p: Point<T, N>) -> bool {
|
|
||||||
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] < self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
(0..N).any(|i| self.p_min[i] >= self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_degenerate(&self) -> bool {
|
|
||||||
(0..N).any(|i| self.p_min[i] > self.p_max[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Default for Bounds<T, N>
|
|
||||||
where
|
|
||||||
T: Bounded + Copy,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
p_min: Point([T::max_value(); N]),
|
|
||||||
p_max: Point([T::min_value(); N]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Bounds2<T> = Bounds<T, 2>;
|
|
||||||
pub type Bounds2f = Bounds2<Float>;
|
|
||||||
pub type Bounds2i = Bounds2<i32>;
|
|
||||||
pub type Bounds2fi = Bounds2<Interval>;
|
|
||||||
pub type Bounds3<T> = Bounds<T, 3>;
|
|
||||||
pub type Bounds3i = Bounds3<i32>;
|
|
||||||
pub type Bounds3f = Bounds3<Float>;
|
|
||||||
pub type Bounds3fi = Bounds3<Interval>;
|
|
||||||
|
|
||||||
impl<T> Bounds3<T>
|
|
||||||
where
|
|
||||||
T: Num + PartialOrd + Copy + Default,
|
|
||||||
{
|
|
||||||
pub fn surface_area(&self) -> T {
|
|
||||||
let d = self.diagonal();
|
|
||||||
let two = T::one() + T::one();
|
|
||||||
two * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds3<T>
|
|
||||||
where
|
|
||||||
T: NumFloat + PartialOrd + Copy + Default + Sqrt,
|
|
||||||
{
|
|
||||||
pub fn bounding_sphere(&self) -> (Point3<T>, T) {
|
|
||||||
let two = T::one() + T::one();
|
|
||||||
let center = self.p_min + self.diagonal() / two;
|
|
||||||
let radius = if self.contains(center) {
|
|
||||||
center.distance(self.p_max)
|
|
||||||
} else {
|
|
||||||
T::zero()
|
|
||||||
};
|
|
||||||
(center, radius)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> {
|
|
||||||
let mut t0 = T::zero();
|
|
||||||
let mut t1 = t_max;
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
let inv_ray_dir = T::one() / d[i];
|
|
||||||
let mut t_near = (self.p_min[i] - o[i]) * inv_ray_dir;
|
|
||||||
let mut t_far = (self.p_max[i] - o[i]) * inv_ray_dir;
|
|
||||||
if t_near > t_far {
|
|
||||||
mem::swap(&mut t_near, &mut t_far);
|
|
||||||
}
|
|
||||||
t0 = if t_near > t0 { t_near } else { t0 };
|
|
||||||
t1 = if t_far < t1 { t_far } else { t1 };
|
|
||||||
if t0 > t1 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some((t0, t1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds2<T>
|
|
||||||
where
|
|
||||||
T: Num + Copy + Default,
|
|
||||||
{
|
|
||||||
pub fn area(&self) -> T {
|
|
||||||
let d: Vector2<T> = self.p_max - self.p_min;
|
|
||||||
d.x() * d.y()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bounds3f {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn intersect_p(
|
|
||||||
&self,
|
|
||||||
o: Point3f,
|
|
||||||
ray_t_max: Float,
|
|
||||||
inv_dir: Vector3f,
|
|
||||||
dir_is_neg: &[usize; 3],
|
|
||||||
) -> Option<(Float, Float)> {
|
|
||||||
let bounds = [&self.p_min, &self.p_max];
|
|
||||||
|
|
||||||
// Check X
|
|
||||||
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
|
|
||||||
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
|
|
||||||
|
|
||||||
// Check Y
|
|
||||||
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
|
|
||||||
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
|
|
||||||
|
|
||||||
if t_min > ty_max || ty_min > t_max {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if ty_min > t_min {
|
|
||||||
t_min = ty_min;
|
|
||||||
}
|
|
||||||
if ty_max < t_max {
|
|
||||||
t_max = ty_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check Z
|
|
||||||
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
|
|
||||||
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
|
|
||||||
|
|
||||||
if t_min > tz_max || tz_min > t_max {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if tz_min > t_min {
|
|
||||||
t_min = tz_min;
|
|
||||||
}
|
|
||||||
if tz_max < t_max {
|
|
||||||
t_max = tz_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t_min < ray_t_max) && (t_max > 0.0) {
|
|
||||||
Some((t_min, t_max))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intersect_with_inverse(&self, o: Point3f, d: Vector3f, ray_t_max: Float) -> bool {
|
|
||||||
let inv_dir = Vector3::new(1.0 / d.x(), 1.0 / d.y(), 1.0 / d.z());
|
|
||||||
let dir_is_neg: [usize; 3] = [
|
|
||||||
(d.x() < 0.0) as usize,
|
|
||||||
(d.y() < 0.0) as usize,
|
|
||||||
(d.z() < 0.0) as usize,
|
|
||||||
];
|
|
||||||
|
|
||||||
let bounds = [&self.p_min, &self.p_max];
|
|
||||||
|
|
||||||
// Check for ray intersection against x and y slabs
|
|
||||||
let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
|
|
||||||
let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
|
|
||||||
let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
|
|
||||||
let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();
|
|
||||||
|
|
||||||
if t_min > ty_max || ty_min > t_max {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ty_min > t_min {
|
|
||||||
t_min = ty_min;
|
|
||||||
}
|
|
||||||
if ty_max < t_max {
|
|
||||||
t_max = ty_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
|
|
||||||
let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();
|
|
||||||
|
|
||||||
if t_min > tz_max || tz_min > t_max {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if tz_min > t_min {
|
|
||||||
t_min = tz_min;
|
|
||||||
}
|
|
||||||
if tz_max < t_max {
|
|
||||||
t_max = tz_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
(t_min < ray_t_max) && (t_max > 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,880 +0,0 @@
|
||||||
use super::traits::{Sqrt, Tuple, VectorLike};
|
|
||||||
use super::{Float, NumFloat, PI, clamp_t};
|
|
||||||
use crate::utils::interval::Interval;
|
|
||||||
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
|
|
||||||
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::iter::Sum;
|
|
||||||
use std::ops::{
|
|
||||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
|
||||||
};
|
|
||||||
|
|
||||||
// N-dimensional displacement
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Vector<T, const N: usize>(pub [T; N]);
|
|
||||||
// N-dimensional location
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Point<T, const N: usize>(pub [T; N]);
|
|
||||||
// N-dimensional surface normal
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Normal<T, const N: usize>(pub [T; N]);
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! impl_tuple_core {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T: Copy, const N: usize> Tuple<T, N> for $Struct<T, N> {
|
|
||||||
#[inline]
|
|
||||||
fn data(&self) -> &[T; N] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn data_mut(&mut self) -> &mut [T; N] {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn from_array(arr: [T; N]) -> Self {
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default + Copy, const N: usize> Default for $Struct<T, N> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self([T::default(); N])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Zero + Copy,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
pub fn zero() -> Self {
|
|
||||||
Self([T::zero(); N])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> $Struct<f32, N> {
|
|
||||||
#[inline]
|
|
||||||
pub fn floor(&self) -> $Struct<i32, N> {
|
|
||||||
$Struct(self.0.map(|v| v.floor() as i32))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn average(&self) -> f32 {
|
|
||||||
let sum: f32 = self.0.iter().sum();
|
|
||||||
sum / (N as f32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Copy + PartialOrd,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
pub fn min(&self, other: Self) -> Self {
|
|
||||||
let mut out = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
if other.0[i] < out[i] {
|
|
||||||
out[i] = other.0[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn max(&self, other: Self) -> Self {
|
|
||||||
let mut out = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
if other.0[i] > out[i] {
|
|
||||||
out[i] = other.0[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn max_component_value(&self) -> T {
|
|
||||||
let mut m = self.0[0];
|
|
||||||
for i in 1..N {
|
|
||||||
if self.0[i] > m {
|
|
||||||
m = self.0[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
pub fn fill(value: T) -> Self {
|
|
||||||
Self([value; N])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cast<U>(&self) -> $Struct<U, N>
|
|
||||||
where
|
|
||||||
U: 'static + Copy,
|
|
||||||
T: 'static + Copy + AsPrimitive<U>,
|
|
||||||
{
|
|
||||||
$Struct(self.0.map(|c| c.as_()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Index<usize> for $Struct<T, N> {
|
|
||||||
type Output = T;
|
|
||||||
#[inline]
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
|
||||||
&self.0[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, const N: usize> IndexMut<usize> for $Struct<T, N> {
|
|
||||||
#[inline]
|
|
||||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
|
||||||
&mut self.0[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Neg for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Neg<Output = T> + Copy,
|
|
||||||
{
|
|
||||||
type Output = Self;
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
Self(self.0.map(|c| -c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! impl_scalar_ops {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T, const N: usize> Mul<T> for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Mul<Output = T> + Copy,
|
|
||||||
{
|
|
||||||
type Output = Self;
|
|
||||||
fn mul(self, rhs: T) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = result[i] * rhs;
|
|
||||||
}
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Mul<$Struct<Float, N>> for Float {
|
|
||||||
type Output = $Struct<Float, N>;
|
|
||||||
fn mul(self, rhs: $Struct<Float, N>) -> Self::Output {
|
|
||||||
rhs * self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> MulAssign<T> for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: MulAssign + Copy,
|
|
||||||
{
|
|
||||||
fn mul_assign(&mut self, rhs: T) {
|
|
||||||
for i in 0..N {
|
|
||||||
self.0[i] *= rhs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Div<T> for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Div<Output = T> + Copy,
|
|
||||||
{
|
|
||||||
type Output = Self;
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = result[i] / rhs;
|
|
||||||
}
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, const N: usize> DivAssign<T> for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: DivAssign + Copy,
|
|
||||||
{
|
|
||||||
fn div_assign(&mut self, rhs: T) {
|
|
||||||
for i in 0..N {
|
|
||||||
self.0[i] /= rhs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! impl_op {
|
|
||||||
($Op:ident, $op:ident, $Lhs:ident, $Rhs:ident, $Output:ident) => {
|
|
||||||
impl<T, const N: usize> $Op<$Rhs<T, N>> for $Lhs<T, N>
|
|
||||||
where
|
|
||||||
T: $Op<Output = T> + Copy,
|
|
||||||
{
|
|
||||||
type Output = $Output<T, N>;
|
|
||||||
fn $op(self, rhs: $Rhs<T, N>) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = $Op::$op(self.0[i], rhs.0[i]);
|
|
||||||
}
|
|
||||||
$Output(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! impl_op_assign {
|
|
||||||
($OpAssign:ident, $op_assign:ident, $Lhs:ident, $Rhs:ident) => {
|
|
||||||
impl<T, const N: usize> $OpAssign<$Rhs<T, N>> for $Lhs<T, N>
|
|
||||||
where
|
|
||||||
T: $OpAssign + Copy,
|
|
||||||
{
|
|
||||||
fn $op_assign(&mut self, rhs: $Rhs<T, N>) {
|
|
||||||
for i in 0..N {
|
|
||||||
$OpAssign::$op_assign(&mut self.0[i], rhs.0[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! impl_float_vector_ops {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T, const N: usize> VectorLike for $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Copy
|
|
||||||
+ Zero
|
|
||||||
+ Add<Output = T>
|
|
||||||
+ Mul<Output = T>
|
|
||||||
+ Sub<Output = T>
|
|
||||||
+ Div<Output = T>
|
|
||||||
+ Sqrt,
|
|
||||||
{
|
|
||||||
type Scalar = T;
|
|
||||||
fn dot(self, rhs: Self) -> T {
|
|
||||||
let mut sum = T::zero();
|
|
||||||
for i in 0..N {
|
|
||||||
sum = sum + self[i] * rhs[i];
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_abs {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Signed + Copy,
|
|
||||||
{
|
|
||||||
pub fn abs(self) -> Self {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = result[i].abs();
|
|
||||||
}
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_accessors {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T: Copy> $Struct<T, 2> {
|
|
||||||
pub fn x(&self) -> T {
|
|
||||||
self.0[0]
|
|
||||||
}
|
|
||||||
pub fn y(&self) -> T {
|
|
||||||
self.0[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> $Struct<T, 3> {
|
|
||||||
pub fn x(&self) -> T {
|
|
||||||
self.0[0]
|
|
||||||
}
|
|
||||||
pub fn y(&self) -> T {
|
|
||||||
self.0[1]
|
|
||||||
}
|
|
||||||
pub fn z(&self) -> T {
|
|
||||||
self.0[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_tuple_core!(Vector);
|
|
||||||
impl_tuple_core!(Point);
|
|
||||||
impl_tuple_core!(Normal);
|
|
||||||
|
|
||||||
impl_scalar_ops!(Vector);
|
|
||||||
impl_scalar_ops!(Normal);
|
|
||||||
|
|
||||||
// Addition
|
|
||||||
impl_op!(Add, add, Vector, Vector, Vector);
|
|
||||||
impl_op!(Add, add, Point, Vector, Point);
|
|
||||||
impl_op!(Add, add, Vector, Point, Point);
|
|
||||||
impl_op!(Add, add, Normal, Normal, Normal);
|
|
||||||
|
|
||||||
// Subtraction
|
|
||||||
impl_op!(Sub, sub, Vector, Vector, Vector);
|
|
||||||
impl_op!(Sub, sub, Point, Vector, Point);
|
|
||||||
impl_op!(Sub, sub, Point, Point, Vector);
|
|
||||||
impl_op!(Sub, sub, Normal, Normal, Normal);
|
|
||||||
|
|
||||||
// AddAssign
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Vector, Vector);
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Point, Vector);
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Normal, Normal);
|
|
||||||
|
|
||||||
// SubAssign
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Vector, Vector);
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Point, Vector);
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Normal, Normal);
|
|
||||||
|
|
||||||
impl_float_vector_ops!(Vector);
|
|
||||||
impl_float_vector_ops!(Normal);
|
|
||||||
impl_abs!(Vector);
|
|
||||||
impl_abs!(Normal);
|
|
||||||
impl_abs!(Point);
|
|
||||||
impl_accessors!(Vector);
|
|
||||||
impl_accessors!(Point);
|
|
||||||
impl_accessors!(Normal);
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
|
||||||
fn from(v: Vector<T, N>) -> Self {
|
|
||||||
Self(v.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy, const N: usize> From<Normal<T, N>> for Vector<T, N> {
|
|
||||||
fn from(n: Normal<T, N>) -> Self {
|
|
||||||
Self(n.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Point<T, N> {
|
|
||||||
fn from(v: Vector<T, N>) -> Self {
|
|
||||||
Self(v.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
|
|
||||||
fn from(n: Point<T, N>) -> Self {
|
|
||||||
Self(n.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Point<T, N>
|
|
||||||
where
|
|
||||||
T: NumFloat + Sqrt,
|
|
||||||
{
|
|
||||||
pub fn distance(self, other: Self) -> T {
|
|
||||||
(self - other).norm()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn distance_squared(self, other: Self) -> T {
|
|
||||||
(self - other).norm_squared()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Point2f {
|
|
||||||
pub fn invert_bilinear(p: Point2f, vert: &[Point2f]) -> Point2f {
|
|
||||||
let a = vert[0];
|
|
||||||
let b = vert[1];
|
|
||||||
let c = vert[3];
|
|
||||||
let d = vert[2];
|
|
||||||
let e = b - a;
|
|
||||||
let f = d - a;
|
|
||||||
let g = (a - b) + (c - d);
|
|
||||||
let h = p - a;
|
|
||||||
|
|
||||||
let cross2d = |a: Vector2f, b: Vector2f| difference_of_products(a.x(), b.y(), a.y(), b.x());
|
|
||||||
|
|
||||||
let k2 = cross2d(g, f);
|
|
||||||
let k1 = cross2d(e, f) + cross2d(h, g);
|
|
||||||
let k0 = cross2d(h, e);
|
|
||||||
|
|
||||||
// if edges are parallel, this is a linear equation
|
|
||||||
if k2.abs() < 0.001 {
|
|
||||||
if (e.x() * k1 - g.x() * k0).abs() < 1e-5 {
|
|
||||||
return Point2f::new(
|
|
||||||
(h.y() * k1 + f.y() * k0) / (e.y() * k1 - g.y() * k0),
|
|
||||||
-k0 / k1,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Point2f::new(
|
|
||||||
(h.x() * k1 + f.x() * k0) / (e.x() * k1 - g.x() * k0),
|
|
||||||
-k0 / k1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((v0, v1)) = quadratic(k2, k1, k0) {
|
|
||||||
let u = (h.x() - f.x() * v0) / (e.x() + g.x() * v0);
|
|
||||||
if !(0.0..=1.).contains(&u) || !(0.0..=1.0).contains(&v0) {
|
|
||||||
return Point2f::new((h.x() - f.x() * v1) / (e.x() + g.x() * v1), v1);
|
|
||||||
}
|
|
||||||
Point2f::new(u, v0)
|
|
||||||
} else {
|
|
||||||
Point2f::zero()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility aliases and functions
|
|
||||||
pub type Point2<T> = Point<T, 2>;
|
|
||||||
pub type Point2f = Point2<Float>;
|
|
||||||
pub type Point2i = Point2<i32>;
|
|
||||||
pub type Point2fi = Point2<Interval>;
|
|
||||||
pub type Point3<T> = Point<T, 3>;
|
|
||||||
pub type Point3f = Point3<Float>;
|
|
||||||
pub type Point3i = Point3<i32>;
|
|
||||||
pub type Point3fi = Point3<Interval>;
|
|
||||||
pub type Vector2<T> = Vector<T, 2>;
|
|
||||||
pub type Vector2f = Vector2<Float>;
|
|
||||||
pub type Vector2i = Vector2<i32>;
|
|
||||||
pub type Vector2fi = Vector2<Interval>;
|
|
||||||
pub type Vector3<T> = Vector<T, 3>;
|
|
||||||
pub type Vector3f = Vector3<Float>;
|
|
||||||
pub type Vector3i = Vector3<i32>;
|
|
||||||
pub type Vector3fi = Vector3<Interval>;
|
|
||||||
pub type Normal3<T> = Normal<T, 3>;
|
|
||||||
pub type Normal3f = Normal3<Float>;
|
|
||||||
pub type Normal3i = Normal3<i32>;
|
|
||||||
|
|
||||||
impl<T: Copy> Vector2<T> {
|
|
||||||
pub fn new(x: T, y: T) -> Self {
|
|
||||||
Self([x, y])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> Point2<T> {
|
|
||||||
pub fn new(x: T, y: T) -> Self {
|
|
||||||
Self([x, y])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> Vector3<T> {
|
|
||||||
pub fn new(x: T, y: T, z: T) -> Self {
|
|
||||||
Self([x, y, z])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> Point3<T> {
|
|
||||||
pub fn new(x: T, y: T, z: T) -> Self {
|
|
||||||
Self([x, y, z])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> Normal3<T> {
|
|
||||||
pub fn new(x: T, y: T, z: T) -> Self {
|
|
||||||
Self([x, y, z])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vector operations
|
|
||||||
impl<T> Vector3<T>
|
|
||||||
where
|
|
||||||
T: Num + Copy + Neg<Output = T>,
|
|
||||||
{
|
|
||||||
pub fn cross(self, rhs: Self) -> Self {
|
|
||||||
Self([
|
|
||||||
self[1] * rhs[2] - self[2] * rhs[1],
|
|
||||||
self[2] * rhs[0] - self[0] * rhs[2],
|
|
||||||
self[0] * rhs[1] - self[1] * rhs[0],
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Normal3<T>
|
|
||||||
where
|
|
||||||
T: Num + Copy + Neg<Output = T>,
|
|
||||||
{
|
|
||||||
pub fn cross(self, rhs: Self) -> Self {
|
|
||||||
Self([
|
|
||||||
self[1] * rhs[2] - self[2] * rhs[1],
|
|
||||||
self[2] * rhs[0] - self[0] * rhs[2],
|
|
||||||
self[0] * rhs[1] - self[1] * rhs[0],
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Vector3<T>
|
|
||||||
where
|
|
||||||
T: Num + NumFloat + Copy + Neg<Output = T>,
|
|
||||||
{
|
|
||||||
pub fn coordinate_system(&self) -> (Self, Self)
|
|
||||||
where
|
|
||||||
T: NumFloat,
|
|
||||||
{
|
|
||||||
let v2 = if self[0].abs() > self[1].abs() {
|
|
||||||
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
|
|
||||||
} else {
|
|
||||||
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
|
|
||||||
};
|
|
||||||
(v2, self.cross(v2))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
|
|
||||||
let sign = self.z().copysign(T::one());
|
|
||||||
let a = -T::one() / (sign + self.z());
|
|
||||||
let b = self.x() * self.y() * a;
|
|
||||||
let v2 = Self::new(
|
|
||||||
T::one() + sign * self.x().powi(2) * a,
|
|
||||||
sign * b,
|
|
||||||
-sign * self.x(),
|
|
||||||
);
|
|
||||||
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
|
|
||||||
(v2, v3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Normal3<T>
|
|
||||||
where
|
|
||||||
T: Num + NumFloat + Copy + Neg<Output = T>,
|
|
||||||
{
|
|
||||||
pub fn coordinate_system(&self) -> (Self, Self)
|
|
||||||
where
|
|
||||||
T: NumFloat,
|
|
||||||
{
|
|
||||||
let v2 = if self[0].abs() > self[1].abs() {
|
|
||||||
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
|
|
||||||
} else {
|
|
||||||
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
|
|
||||||
};
|
|
||||||
(v2, self.cross(v2))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
|
|
||||||
let sign = self.z().copysign(T::one());
|
|
||||||
let a = -T::one() / (sign + self.z());
|
|
||||||
let b = self.x() * self.y() * a;
|
|
||||||
let v2 = Self::new(
|
|
||||||
T::one() + sign * self.x().powi(2) * a,
|
|
||||||
sign * b,
|
|
||||||
-sign * self.x(),
|
|
||||||
);
|
|
||||||
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
|
|
||||||
(v2, v3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Hash for Vector<Float, N> {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
for item in self.0.iter() {
|
|
||||||
item.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Hash for Point<Float, N> {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
for item in self.0.iter() {
|
|
||||||
item.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Hash for Normal<Float, N> {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
for item in self.0.iter() {
|
|
||||||
item.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERVAL STUFF
|
|
||||||
impl<const N: usize> Point<Interval, N> {
|
|
||||||
pub fn new_from_point(p: Point<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new(p[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_error(p: Point<Float, N>, e: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new_from_value_and_error(p[i], e[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].width() / 2.0;
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn midpoint(&self) -> Point<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].midpoint();
|
|
||||||
}
|
|
||||||
Point(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exact(&self) -> bool {
|
|
||||||
self.0.iter().all(|interval| interval.width() == 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Vector<Interval, N> {
|
|
||||||
pub fn new_from_vector(v: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new(v[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_error(v: Vector<Float, N>, e: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new_from_value_and_error(v[i], e[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].width() / 2.0;
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn midpoint(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].midpoint();
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exact(&self) -> bool {
|
|
||||||
self.0.iter().all(|interval| interval.width() == 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Point<Interval, N>> for Point<Float, N> {
|
|
||||||
fn from(pi: Point<Interval, N>) -> Self {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = pi[i].midpoint();
|
|
||||||
}
|
|
||||||
Point(arr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Vector<Interval, N>> for Vector<Float, N> {
|
|
||||||
fn from(pi: Vector<Interval, N>) -> Self {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = pi[i].midpoint();
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Vector<Float, N>> for Vector<Interval, N> {
|
|
||||||
fn from(v: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new(v[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Mul<Vector<Interval, N>> for Interval {
|
|
||||||
type Output = Vector<Interval, N>;
|
|
||||||
fn mul(self, rhs: Vector<Interval, N>) -> Self::Output {
|
|
||||||
rhs * self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Div<Vector<Interval, N>> for Interval {
|
|
||||||
type Output = Vector<Interval, N>;
|
|
||||||
fn div(self, rhs: Vector<Interval, N>) -> Self::Output {
|
|
||||||
let mut result = rhs.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = self / rhs[i];
|
|
||||||
}
|
|
||||||
Vector(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Vector<i32, N>> for Vector<f32, N> {
|
|
||||||
fn from(v: Vector<i32, N>) -> Self {
|
|
||||||
Self(v.0.map(|c| c as f32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Point<i32, N>> for Point<Float, N> {
|
|
||||||
fn from(p: Point<i32, N>) -> Self {
|
|
||||||
Point(p.0.map(|c| c as Float))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Normal3<T>
|
|
||||||
where
|
|
||||||
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
|
|
||||||
{
|
|
||||||
pub fn face_forward(self, v: Vector3<T>) -> Self {
|
|
||||||
if Vector3::<T>::from(self).dot(v) < T::zero() {
|
|
||||||
-self
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct OctahedralVector {
|
|
||||||
x: u16,
|
|
||||||
y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OctahedralVector {
|
|
||||||
pub fn new(mut v: Vector3f) -> Self {
|
|
||||||
v /= v.x().abs() + v.y().abs() + v.z().abs();
|
|
||||||
|
|
||||||
let (x_enc, y_enc) = if v.z() >= 0.0 {
|
|
||||||
(Self::encode(v.x()), Self::encode(v.y()))
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
Self::encode((1.0 - v.y().abs()) * Self::sign(v.x())),
|
|
||||||
Self::encode((1.0 - v.x().abs()) * Self::sign(v.y())),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
Self { x: x_enc, y: y_enc }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vector(self) -> Vector3f {
|
|
||||||
let mut v = Vector3f::default();
|
|
||||||
|
|
||||||
// Map [0, 65535] back to [-1, 1]
|
|
||||||
v[0] = -1.0 + 2.0 * (self.x as Float / 65535.0);
|
|
||||||
v[1] = -1.0 + 2.0 * (self.y as Float / 65535.0);
|
|
||||||
v[2] = 1.0 - (v.x().abs() + v.y().abs());
|
|
||||||
|
|
||||||
if v.z() < 0.0 {
|
|
||||||
let xo = v.x();
|
|
||||||
v[0] = (1.0 - v.y().abs()) * Self::sign(xo);
|
|
||||||
v[1] = (1.0 - xo.abs()) * Self::sign(v.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
v.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn sign(v: Float) -> Float {
|
|
||||||
1.0.copysign(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encode(f: Float) -> u16 {
|
|
||||||
(clamp_t((f + 1.0) / 2.0, 0.0, 1.0) * 65535.0).round() as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vector3f> for OctahedralVector {
|
|
||||||
fn from(v: Vector3f) -> Self {
|
|
||||||
Self::new(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OctahedralVector> for Vector3f {
|
|
||||||
fn from(ov: OctahedralVector) -> Self {
|
|
||||||
ov.to_vector()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
pub struct Frame {
|
|
||||||
pub x: Vector3f,
|
|
||||||
pub y: Vector3f,
|
|
||||||
pub z: Vector3f,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
pub fn new(x: Vector3f, z: Vector3f) -> Self {
|
|
||||||
Self {
|
|
||||||
x,
|
|
||||||
y: z.cross(x),
|
|
||||||
z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_x(x: Vector3f) -> Self {
|
|
||||||
let (y, z) = x.normalize().coordinate_system();
|
|
||||||
Self {
|
|
||||||
x: x.normalize(),
|
|
||||||
y,
|
|
||||||
z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_xz(x: Vector3f, z: Vector3f) -> Self {
|
|
||||||
Self {
|
|
||||||
x,
|
|
||||||
y: z.cross(x),
|
|
||||||
z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_xy(x: Vector3f, y: Vector3f) -> Self {
|
|
||||||
Self {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z: x.cross(y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_y(y: Vector3f) -> Self {
|
|
||||||
let (z, x) = y.normalize().coordinate_system();
|
|
||||||
Self {
|
|
||||||
x,
|
|
||||||
y: y.normalize(),
|
|
||||||
z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_z(z: Vector3f) -> Self {
|
|
||||||
let (x, y) = z.normalize().coordinate_system();
|
|
||||||
Self {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z: z.normalize(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_local(&self, v: Vector3f) -> Vector3f {
|
|
||||||
Vector3f::new(v.dot(self.x), v.dot(self.y), v.dot(self.z))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_local_normal(&self, n: Normal3f) -> Normal3f {
|
|
||||||
let n: Vector3f = n.into();
|
|
||||||
Normal3f::new(n.dot(self.x), n.dot(self.y), n.dot(self.z))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_local(&self, v: Vector3f) -> Vector3f {
|
|
||||||
self.x * v.x() + self.y * v.y() + self.z * v.z()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_local_normal(&self, v: Normal3f) -> Normal3f {
|
|
||||||
Normal3f::from(self.x * v.x() + self.y * v.y() + self.z * v.z())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
|
|
||||||
use crate::core::medium::Medium;
|
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::utils::math::{next_float_down, next_float_up};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Ray {
|
|
||||||
pub o: Point3f,
|
|
||||||
pub d: Vector3f,
|
|
||||||
pub medium: Option<Arc<Medium>>,
|
|
||||||
pub time: Float,
|
|
||||||
// We do this instead of creating a trait for Rayable or some gnarly thing like that
|
|
||||||
pub differential: Option<RayDifferential>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Ray {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
o: Point3f::new(0.0, 0.0, 0.0),
|
|
||||||
d: Vector3f::new(0.0, 0.0, 0.0),
|
|
||||||
medium: None,
|
|
||||||
time: 0.0,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ray {
|
|
||||||
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Option<Arc<Medium>>) -> Self {
|
|
||||||
Self {
|
|
||||||
o,
|
|
||||||
d,
|
|
||||||
time: time.unwrap_or_else(|| Self::default().time),
|
|
||||||
medium,
|
|
||||||
..Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn at(&self, t: Float) -> Point3f {
|
|
||||||
self.o + self.d * t
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset_origin(p: &Point3fi, n: &Normal3f, w: &Vector3f) -> Point3f {
|
|
||||||
let d: Float = Vector3f::from(n.abs()).dot(p.error());
|
|
||||||
let normal: Vector3f = Vector3f::from(*n);
|
|
||||||
|
|
||||||
let mut offset = p.midpoint();
|
|
||||||
if w.dot(normal) < 0.0 {
|
|
||||||
offset -= normal * d;
|
|
||||||
} else {
|
|
||||||
offset += normal * d;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
if n[i] > 0.0 {
|
|
||||||
offset[i] = next_float_up(offset[i]);
|
|
||||||
} else if n[i] < 0.0 {
|
|
||||||
offset[i] = next_float_down(offset[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offset
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(pi: &Point3fi, n: &Normal3f, time: Float, d: Vector3f) -> Ray {
|
|
||||||
let origin = Self::offset_origin(pi, n, &d);
|
|
||||||
Ray {
|
|
||||||
o: origin,
|
|
||||||
d,
|
|
||||||
time,
|
|
||||||
medium: None,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_to_point(p_from: &Point3fi, n: &Normal3f, time: Float, p_to: Point3f) -> Ray {
|
|
||||||
let d = p_to - p_from.midpoint();
|
|
||||||
Self::spawn(p_from, n, time, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_to_interaction(
|
|
||||||
p_from: &Point3fi,
|
|
||||||
n_from: &Normal3f,
|
|
||||||
time: Float,
|
|
||||||
p_to: &Point3fi,
|
|
||||||
n_to: &Normal3f,
|
|
||||||
) -> Ray {
|
|
||||||
let dir_for_offset = p_to.midpoint() - p_from.midpoint();
|
|
||||||
let pf = Self::offset_origin(p_from, n_from, &dir_for_offset);
|
|
||||||
let pt = Self::offset_origin(p_to, n_to, &(pf - p_to.midpoint()));
|
|
||||||
|
|
||||||
let d = pt - pf;
|
|
||||||
|
|
||||||
Ray {
|
|
||||||
o: pf,
|
|
||||||
d,
|
|
||||||
time,
|
|
||||||
medium: None,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_differentials(&mut self, s: Float) {
|
|
||||||
if let Some(differential) = &mut self.differential {
|
|
||||||
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
|
|
||||||
differential.ry_origin = self.o + (differential.ry_origin - self.o) * s;
|
|
||||||
differential.rx_direction = self.d + (differential.rx_direction - self.d) * s;
|
|
||||||
differential.ry_direction = self.d + (differential.ry_direction - self.d) * s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct RayDifferential {
|
|
||||||
pub rx_origin: Point3f,
|
|
||||||
pub ry_origin: Point3f,
|
|
||||||
pub rx_direction: Vector3f,
|
|
||||||
pub ry_direction: Vector3f,
|
|
||||||
}
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::utils::interval::Interval;
|
|
||||||
use crate::utils::math::{next_float_down, next_float_up};
|
|
||||||
use num_integer::Roots;
|
|
||||||
use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero};
|
|
||||||
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
|
|
||||||
|
|
||||||
pub trait Tuple<T, const N: usize>:
|
|
||||||
Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
|
|
||||||
{
|
|
||||||
fn data(&self) -> &[T; N];
|
|
||||||
|
|
||||||
fn data_mut(&mut self) -> &mut [T; N];
|
|
||||||
|
|
||||||
fn from_array(arr: [T; N]) -> Self;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn permute(&self, p: [usize; N]) -> Self
|
|
||||||
where
|
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
let new_data = p.map(|index| self[index]);
|
|
||||||
Self::from_array(new_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_component_value(&self) -> T
|
|
||||||
where
|
|
||||||
T: PartialOrd + Copy,
|
|
||||||
{
|
|
||||||
self.data()
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.reduce(|a, b| if a > b { a } else { b })
|
|
||||||
.expect("Cannot get max component of a zero-length tuple")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min_component_value(&self) -> T
|
|
||||||
where
|
|
||||||
T: PartialOrd + Copy,
|
|
||||||
{
|
|
||||||
self.data()
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.reduce(|a, b| if a < b { a } else { b })
|
|
||||||
.expect("Cannot get min component of a zero-length tuple")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_component_index(&self) -> usize
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
self.data()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
|
||||||
.map(|(index, _)| index)
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min_component_index(&self) -> usize
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
self.data()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
|
||||||
.map(|(index, _)| index)
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VectorLike:
|
|
||||||
Sized
|
|
||||||
+ Copy
|
|
||||||
+ Add<Output = Self>
|
|
||||||
+ Sub<Output = Self>
|
|
||||||
+ Div<Self::Scalar, Output = Self>
|
|
||||||
+ Mul<Self::Scalar, Output = Self>
|
|
||||||
{
|
|
||||||
type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + Sqrt;
|
|
||||||
|
|
||||||
fn dot(self, rhs: Self) -> Self::Scalar;
|
|
||||||
fn norm_squared(self) -> Self::Scalar {
|
|
||||||
self.dot(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn abs_dot(self, rhs: Self) -> Self::Scalar
|
|
||||||
where
|
|
||||||
Self::Scalar: Signed,
|
|
||||||
{
|
|
||||||
self.dot(rhs).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gram_schmidt(self, rhs: Self) -> Self {
|
|
||||||
self - rhs * self.dot(rhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn norm(&self) -> Self::Scalar {
|
|
||||||
self.norm_squared().sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize(self) -> Self
|
|
||||||
where
|
|
||||||
Self::Scalar: NumFloat,
|
|
||||||
{
|
|
||||||
let n = self.norm();
|
|
||||||
if n.is_zero() { self } else { self / n }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn angle_between(self, rhs: Self) -> Self::Scalar
|
|
||||||
where
|
|
||||||
Self::Scalar: NumFloat,
|
|
||||||
{
|
|
||||||
let dot_product = self.normalize().dot(rhs.normalize());
|
|
||||||
let clamped_dot = dot_product
|
|
||||||
.min(Self::Scalar::one())
|
|
||||||
.max(-Self::Scalar::one());
|
|
||||||
clamped_dot.acos()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Sqrt {
|
|
||||||
fn sqrt(self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sqrt for Float {
|
|
||||||
fn sqrt(self) -> Self {
|
|
||||||
self.sqrt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sqrt for f64 {
|
|
||||||
fn sqrt(self) -> Self {
|
|
||||||
self.sqrt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sqrt for i32 {
|
|
||||||
fn sqrt(self) -> Self {
|
|
||||||
self.isqrt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sqrt for u32 {
|
|
||||||
fn sqrt(self) -> Self {
|
|
||||||
self.isqrt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sqrt for Interval {
|
|
||||||
fn sqrt(self) -> Self {
|
|
||||||
let low = if self.low < 0.0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
next_float_down(self.low.sqrt())
|
|
||||||
};
|
|
||||||
|
|
||||||
let high = if self.high < 0.0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
next_float_up(self.high.sqrt())
|
|
||||||
};
|
|
||||||
|
|
||||||
Self { low, high }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Lerp<Factor = Float>: Sized + Copy {
|
|
||||||
fn lerp(t: Factor, a: Self, b: Self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, F, Diff> Lerp<F> for T
|
|
||||||
where
|
|
||||||
T: Copy + Sub<Output = Diff> + Add<Diff, Output = T>,
|
|
||||||
Diff: Mul<F, Output = Diff>,
|
|
||||||
F: Copy,
|
|
||||||
{
|
|
||||||
#[inline(always)]
|
|
||||||
fn lerp(t: F, a: Self, b: Self) -> Self {
|
|
||||||
a + (b - a) * t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
src/lib.rs
15
src/lib.rs
|
|
@ -1,13 +1,2 @@
|
||||||
#![allow(unused_imports, dead_code)]
|
pub mod scene;
|
||||||
#![feature(float_erf)]
|
pub mod utils;
|
||||||
#![feature(f16)]
|
|
||||||
|
|
||||||
mod camera;
|
|
||||||
mod core;
|
|
||||||
mod geometry;
|
|
||||||
mod image;
|
|
||||||
mod integrators;
|
|
||||||
mod lights;
|
|
||||||
mod shapes;
|
|
||||||
mod spectra;
|
|
||||||
mod utils;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
use super::{
|
use super::{
|
||||||
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
|
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
|
||||||
LightType, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
LightType, RGB, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths,
|
||||||
|
Spectrum, SpectrumTrait,
|
||||||
|
};
|
||||||
|
use crate::core::geometry::{
|
||||||
|
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
||||||
|
|
@ -11,15 +15,10 @@ use crate::core::texture::{
|
||||||
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
||||||
UniversalTextureEvaluator,
|
UniversalTextureEvaluator,
|
||||||
};
|
};
|
||||||
use crate::geometry::{
|
|
||||||
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
|
||||||
};
|
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
|
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
|
||||||
use crate::utils::color::RGB;
|
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
|
||||||
use crate::utils::hash::hash_float;
|
use crate::utils::hash::hash_float;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -39,7 +38,7 @@ pub struct DiffuseAreaLight {
|
||||||
impl DiffuseAreaLight {
|
impl DiffuseAreaLight {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: Transform<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
le: Spectrum,
|
le: Spectrum,
|
||||||
scale: Float,
|
scale: Float,
|
||||||
|
|
|
||||||
861
src/scene.rs
Normal file
861
src/scene.rs
Normal file
|
|
@ -0,0 +1,861 @@
|
||||||
|
use crate::Float;
|
||||||
|
use crate::camera::{Camera, CameraTransform};
|
||||||
|
use crate::core::film::{Film, FilmTrait};
|
||||||
|
use crate::core::filter::Filter;
|
||||||
|
use crate::core::geometry::{Point3f, Vector3f};
|
||||||
|
use crate::core::medium::Medium;
|
||||||
|
use crate::core::options::RenderingCoordinateSystem;
|
||||||
|
use crate::core::sampler::Sampler;
|
||||||
|
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||||
|
use crate::image::Image;
|
||||||
|
use crate::lights::Light;
|
||||||
|
use crate::spectra::RGBColorSpace;
|
||||||
|
use crate::utils::error::FileLoc;
|
||||||
|
use crate::utils::math::SquareMatrix;
|
||||||
|
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
|
||||||
|
use crate::utils::parser::ParserTarget;
|
||||||
|
use crate::utils::transform::look_at;
|
||||||
|
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||||
|
use crate::utils::{normalize_utf8, resolve_filename};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::char::MAX;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::ops::{Index as IndexTrait, IndexMut as IndexMutTrait};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum MaterialRef {
|
||||||
|
Index(usize),
|
||||||
|
Name(String),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct SceneEntityBase {
|
||||||
|
pub name: String,
|
||||||
|
pub loc: FileLoc,
|
||||||
|
pub parameters: ParameterDictionary,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TransformedSceneEntity {
|
||||||
|
pub base: SceneEntityBase,
|
||||||
|
pub render_from_object: AnimatedTransform,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MediumSceneEntity = TransformedSceneEntity;
|
||||||
|
pub type TextureSceneEntity = TransformedSceneEntity;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CameraSceneEntity {
|
||||||
|
pub base: SceneEntityBase,
|
||||||
|
pub camera_transform: CameraTransform,
|
||||||
|
pub medium: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ShapeSceneEntity {
|
||||||
|
pub base: SceneEntityBase,
|
||||||
|
pub render_from_object: Arc<TransformGeneric<Float>>,
|
||||||
|
pub object_from_render: Arc<TransformGeneric<Float>>,
|
||||||
|
pub reverse_orientation: bool,
|
||||||
|
pub material: MaterialRef,
|
||||||
|
pub light_index: Option<usize>,
|
||||||
|
|
||||||
|
pub inside_medium: String,
|
||||||
|
pub outside_medium: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AnimatedShapeSceneEntity {
|
||||||
|
pub transformed_base: TransformedSceneEntity,
|
||||||
|
|
||||||
|
pub identity: Arc<TransformGeneric<Float>>,
|
||||||
|
pub reverse_orientation: bool,
|
||||||
|
pub material: MaterialRef,
|
||||||
|
pub light_index: Option<usize>,
|
||||||
|
pub inside_medium: String,
|
||||||
|
pub outside_medium: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InstanceDefinitionSceneEntity {
|
||||||
|
pub name: String,
|
||||||
|
pub loc: FileLoc,
|
||||||
|
pub shapes: Vec<ShapeSceneEntity>,
|
||||||
|
pub animated_shapes: Vec<AnimatedShapeSceneEntity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LightSceneEntity {
|
||||||
|
pub transformed_base: TransformedSceneEntity,
|
||||||
|
pub medium: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum InstanceTransform {
|
||||||
|
Animated(AnimatedTransform),
|
||||||
|
Static(Arc<TransformGeneric<Float>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InstanceSceneEntity {
|
||||||
|
pub name: String,
|
||||||
|
pub loc: FileLoc,
|
||||||
|
pub transform: InstanceTransform,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextureData {
|
||||||
|
pub serial_float_textures: Vec<(String, TextureSceneEntity)>,
|
||||||
|
pub serial_spectrum_textures: Vec<(String, TextureSceneEntity)>,
|
||||||
|
pub async_spectrum_textures: Vec<(String, TextureSceneEntity)>,
|
||||||
|
pub loading_texture_filenames: HashSet<String>,
|
||||||
|
pub float_texture_jobs: HashMap<String, JoinHandle<Arc<FloatTexture>>>,
|
||||||
|
pub spectrum_texture_jobs: HashMap<String, JoinHandle<Arc<SpectrumTexture>>>,
|
||||||
|
pub n_missing_textures: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NamedTextures {
|
||||||
|
pub float_textures: HashMap<String, Arc<FloatTexture>>,
|
||||||
|
pub albedo_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
|
||||||
|
pub unbounded_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
|
||||||
|
pub illuminant_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LightData {
|
||||||
|
pub light_jobs: Vec<JoinHandle<Light>>,
|
||||||
|
pub area_lights: Vec<SceneEntityBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MaterialData {
|
||||||
|
pub named_materials: Vec<(String, SceneEntityBase)>,
|
||||||
|
pub materials: Vec<SceneEntityBase>,
|
||||||
|
pub normal_map_jobs: HashMap<String, JoinHandle<Arc<Image>>>,
|
||||||
|
pub normal_maps: HashMap<String, Arc<Image>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BasicScene {
|
||||||
|
pub integrator: Mutex<Option<SceneEntityBase>>,
|
||||||
|
pub accelerator: Mutex<Option<SceneEntityBase>>,
|
||||||
|
pub film_colorspace: Mutex<Option<Arc<RGBColorSpace>>>,
|
||||||
|
|
||||||
|
// Collections
|
||||||
|
pub shapes: Mutex<Vec<ShapeSceneEntity>>,
|
||||||
|
pub animated_shapes: Mutex<Vec<AnimatedShapeSceneEntity>>,
|
||||||
|
pub instances: Mutex<Vec<InstanceSceneEntity>>,
|
||||||
|
pub instance_definitions: Mutex<HashMap<String, Arc<InstanceDefinitionSceneEntity>>>,
|
||||||
|
|
||||||
|
// Simple mutexes in C++
|
||||||
|
pub medium_jobs: Mutex<HashMap<String, JoinHandle<Medium>>>,
|
||||||
|
pub media_map: Mutex<HashMap<String, Medium>>,
|
||||||
|
pub material_data: Mutex<MaterialData>,
|
||||||
|
pub light_data: Mutex<LightData>,
|
||||||
|
pub texture_data: Mutex<TextureData>,
|
||||||
|
|
||||||
|
// Top level
|
||||||
|
pub camera: Mutex<Option<Arc<Camera>>>,
|
||||||
|
pub film: Mutex<Option<Arc<Film>>>,
|
||||||
|
pub sampler: Mutex<Option<Arc<Sampler>>>,
|
||||||
|
|
||||||
|
pub camera_job: Mutex<Option<JoinHandle<Camera>>>,
|
||||||
|
pub sampler_job: Mutex<Option<JoinHandle<Sampler>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BasicScene {
|
||||||
|
fn set_options(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
filter: SceneEntityBase,
|
||||||
|
film: SceneEntityBase,
|
||||||
|
camera: CameraSceneEntity,
|
||||||
|
sampler: SceneEntityBase,
|
||||||
|
integ: SceneEntityBase,
|
||||||
|
accel: SceneEntityBase,
|
||||||
|
) {
|
||||||
|
*self.integrator.lock() = Some(integ);
|
||||||
|
*self.accelerator.lock() = Some(accel);
|
||||||
|
|
||||||
|
if let Some(cs) = film.parameters.color_space.as_ref() {
|
||||||
|
*self.film_colorspace.lock() = Some(Arc::clone(cs));
|
||||||
|
}
|
||||||
|
|
||||||
|
let filt = Filter::create(&filter.name, filter.parameters, &filter.loc);
|
||||||
|
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.);
|
||||||
|
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.);
|
||||||
|
let exposure_time = shutter_close - shutter_open;
|
||||||
|
let film_instance = Arc::new(
|
||||||
|
Film::create(
|
||||||
|
&film.name,
|
||||||
|
&film.parameters,
|
||||||
|
exposure_time,
|
||||||
|
filt.expect("Must have a filter"),
|
||||||
|
Some(camera.camera_transform.clone()),
|
||||||
|
&film.loc,
|
||||||
|
)
|
||||||
|
.expect("Must have a film"),
|
||||||
|
);
|
||||||
|
|
||||||
|
*self.film.lock() = Some(Arc::clone(&film_instance));
|
||||||
|
let sampler_film = Arc::clone(&film_instance);
|
||||||
|
let sampler_job = std::thread::spawn(move || {
|
||||||
|
let res = sampler_film.as_ref().base().full_resolution;
|
||||||
|
Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc)
|
||||||
|
.expect("Sampler was not correctly created")
|
||||||
|
});
|
||||||
|
*self.sampler_job.lock() = Some(sampler_job);
|
||||||
|
|
||||||
|
let camera_film = Arc::clone(&film_instance);
|
||||||
|
let scene_ptr = self.clone();
|
||||||
|
let camera_job = std::thread::spawn(move || {
|
||||||
|
let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc);
|
||||||
|
Camera::create(
|
||||||
|
&camera.base.name,
|
||||||
|
&camera.base.parameters,
|
||||||
|
medium.unwrap(),
|
||||||
|
&camera.camera_transform,
|
||||||
|
camera_film,
|
||||||
|
&camera.base.loc,
|
||||||
|
)
|
||||||
|
.expect("Failed to create camera")
|
||||||
|
});
|
||||||
|
*self.camera_job.lock() = Some(camera_job);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Medium> {
|
||||||
|
if name.is_empty() || name == "none" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let media_map = self.media_map.lock();
|
||||||
|
if let Some(m) = media_map.get(name) {
|
||||||
|
return Some(m.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut jobs = self.medium_jobs.lock();
|
||||||
|
if let Some(handle) = jobs.remove(name) {
|
||||||
|
drop(jobs);
|
||||||
|
drop(media_map);
|
||||||
|
|
||||||
|
log::info!("Waiting for medium '{}' to finish loading...", name);
|
||||||
|
let m = handle.join().expect("Medium loading thread panicked");
|
||||||
|
|
||||||
|
self.media_map.lock().insert(name.to_string(), m.clone());
|
||||||
|
return Some(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.medium_jobs.lock().contains_key(name)
|
||||||
|
&& !self.media_map.lock().contains_key(name)
|
||||||
|
{
|
||||||
|
log::error!("[{:?}] {}: medium is not defined.", loc, name);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::yield_now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_float_texture(&self, name: &str, texture: &TextureSceneEntity) {
|
||||||
|
if texture.render_from_object.is_animated() {
|
||||||
|
log::info!(
|
||||||
|
"{}: Animated world to texture not supported, using start",
|
||||||
|
texture.base.loc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut texture_state = self.texture_data.lock();
|
||||||
|
if name != "imagemap" && name != "ptex" {
|
||||||
|
texture_state
|
||||||
|
.serial_float_textures
|
||||||
|
.push((name.to_string(), texture.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
|
||||||
|
if filename.is_empty() {
|
||||||
|
log::error!(
|
||||||
|
"[{:?}] \"string filename\" not provided for image texture.",
|
||||||
|
texture.base.loc
|
||||||
|
);
|
||||||
|
texture_state.n_missing_textures += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !std::fs::exists(&filename).expect("Not sure what to do here, just moving on") {
|
||||||
|
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename);
|
||||||
|
texture_state.n_missing_textures += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if texture_state.loading_texture_filenames.contains(&filename) {
|
||||||
|
texture_state
|
||||||
|
.serial_float_textures
|
||||||
|
.push((name.to_string(), texture.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_TRANSFORMS: usize = 2;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
struct TransformSet {
|
||||||
|
t: [TransformGeneric<Float>; MAX_TRANSFORMS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransformSet {
|
||||||
|
pub fn is_animated(&self) -> bool {
|
||||||
|
self.t[0] != self.t[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
t: [self.t[0].inverse(), self.t[1].inverse()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<F>(&mut self, bits: u32, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(&TransformGeneric<Float>) -> TransformGeneric<Float>,
|
||||||
|
{
|
||||||
|
if (bits & 1) != 0 {
|
||||||
|
self.t[0] = f(&self.t[0]);
|
||||||
|
}
|
||||||
|
if (bits & 2) != 0 {
|
||||||
|
self.t[1] = f(&self.t[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexTrait<usize> for TransformSet {
|
||||||
|
type Output = TransformGeneric<Float>;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.t[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMutTrait<usize> for TransformSet {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
&mut self.t[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
struct GraphicsState {
|
||||||
|
current_inside_medium: String,
|
||||||
|
current_outside_medium: String,
|
||||||
|
current_material_name: String,
|
||||||
|
area_light_name: String,
|
||||||
|
area_light_params: ParsedParameterVector,
|
||||||
|
area_light_loc: FileLoc,
|
||||||
|
shape_attributes: ParsedParameterVector,
|
||||||
|
light_attributes: ParsedParameterVector,
|
||||||
|
material_attributes: ParsedParameterVector,
|
||||||
|
medium_attributes: ParsedParameterVector,
|
||||||
|
texture_attributes: ParsedParameterVector,
|
||||||
|
|
||||||
|
reverse_orientation: bool,
|
||||||
|
// None implies sRGB usually
|
||||||
|
color_space: Option<Arc<RGBColorSpace>>,
|
||||||
|
|
||||||
|
ctm: TransformSet,
|
||||||
|
// 1=Start, 2=End, 3=All
|
||||||
|
active_transform_bits: u32,
|
||||||
|
transform_start_time: Float,
|
||||||
|
transform_end_time: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum BlockState {
|
||||||
|
OptionsBlock,
|
||||||
|
WorldBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BasicSceneBuilder {
|
||||||
|
scene: Arc<BasicScene>,
|
||||||
|
current_block: BlockState,
|
||||||
|
graphics_state: GraphicsState,
|
||||||
|
pushed_graphics_states: Vec<GraphicsState>,
|
||||||
|
push_stack: Vec<(char, FileLoc)>,
|
||||||
|
render_from_world: TransformGeneric<Float>,
|
||||||
|
named_coordinate_systems: HashMap<String, TransformSet>,
|
||||||
|
// Object Instancing State
|
||||||
|
// If this is Some, we are inside an "ObjectBegin" block
|
||||||
|
active_instance_definition: Option<InstanceDefinitionSceneEntity>,
|
||||||
|
|
||||||
|
shapes_buffer: Vec<ShapeSceneEntity>,
|
||||||
|
instance_uses_buffer: Vec<InstanceSceneEntity>,
|
||||||
|
|
||||||
|
named_material_names: HashSet<String>,
|
||||||
|
medium_names: HashSet<String>,
|
||||||
|
current_camera: Option<CameraSceneEntity>,
|
||||||
|
current_film: Option<SceneEntityBase>,
|
||||||
|
current_integrator: Option<SceneEntityBase>,
|
||||||
|
current_sampler: Option<SceneEntityBase>,
|
||||||
|
current_filter: Option<SceneEntityBase>,
|
||||||
|
current_accelerator: Option<SceneEntityBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BasicSceneBuilder {
|
||||||
|
pub const START_TRANSFORM_BITS: u32 = 1 << 0;
|
||||||
|
pub const END_TRANSFORM_BITS: u32 = 1 << 1;
|
||||||
|
pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1;
|
||||||
|
|
||||||
|
pub fn new(scene: Arc<BasicScene>) -> Self {
|
||||||
|
Self {
|
||||||
|
scene,
|
||||||
|
current_block: BlockState::OptionsBlock,
|
||||||
|
graphics_state: GraphicsState::default(),
|
||||||
|
pushed_graphics_states: Vec::new(),
|
||||||
|
push_stack: Vec::new(),
|
||||||
|
render_from_world: TransformGeneric::identity(),
|
||||||
|
named_coordinate_systems: HashMap::new(),
|
||||||
|
active_instance_definition: None,
|
||||||
|
shapes_buffer: Vec::new(),
|
||||||
|
instance_uses_buffer: Vec::new(),
|
||||||
|
named_material_names: HashSet::new(),
|
||||||
|
medium_names: HashSet::new(),
|
||||||
|
|
||||||
|
current_camera: Some(CameraSceneEntity {
|
||||||
|
base: SceneEntityBase {
|
||||||
|
name: "perspective".into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
camera_transform: CameraTransform::from_world(
|
||||||
|
AnimatedTransform::default(),
|
||||||
|
RenderingCoordinateSystem::World,
|
||||||
|
),
|
||||||
|
medium: String::new(),
|
||||||
|
}),
|
||||||
|
current_sampler: Some(SceneEntityBase {
|
||||||
|
name: "zsobol".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
current_filter: Some(SceneEntityBase {
|
||||||
|
name: "gaussian".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
current_integrator: Some(SceneEntityBase {
|
||||||
|
name: "volpath".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
current_accelerator: Some(SceneEntityBase {
|
||||||
|
name: "bvh".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
current_film: Some(SceneEntityBase {
|
||||||
|
name: "rgb".into(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn for_active_transforms<F>(&mut self, f: F)
|
||||||
|
where
|
||||||
|
F: Fn(&TransformGeneric<Float>) -> TransformGeneric<Float>,
|
||||||
|
{
|
||||||
|
let bits = self.graphics_state.active_transform_bits;
|
||||||
|
|
||||||
|
if (bits & 1) != 0 {
|
||||||
|
self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]);
|
||||||
|
}
|
||||||
|
if (bits & 2) != 0 {
|
||||||
|
self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_world(&self, name: &str, loc: &FileLoc) {
|
||||||
|
if self.current_block != BlockState::WorldBlock {
|
||||||
|
eprintln!("Error: {} not allowed outside WorldBlock at {}", name, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_options(&self, name: &str, loc: &FileLoc) {
|
||||||
|
if self.current_block != BlockState::OptionsBlock {
|
||||||
|
eprintln!("Error: {} not allowed inside WorldBlock at {}", name, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserTarget for BasicSceneBuilder {
|
||||||
|
fn reverse_orientation(&mut self, loc: FileLoc) {
|
||||||
|
self.verify_world("ReverseOrientation", &loc);
|
||||||
|
self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_space(&mut self, name: &str, loc: FileLoc) {
|
||||||
|
let _ = match RGBColorSpace::get_named(name) {
|
||||||
|
Ok(cs) => {
|
||||||
|
self.graphics_state.color_space = Some(cs);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Error: Color space '{}' unknown at {}", name, loc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identity(&mut self, _loc: FileLoc) {
|
||||||
|
self.for_active_transforms(|_| TransformGeneric::identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) {
|
||||||
|
let t = TransformGeneric::translate(Vector3f::new(dx, dy, dz));
|
||||||
|
self.for_active_transforms(|cur| cur * &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) {
|
||||||
|
let t = TransformGeneric::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
|
||||||
|
self.for_active_transforms(|cur| cur * &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) {
|
||||||
|
let t = TransformGeneric::scale(sx, sy, sz);
|
||||||
|
self.for_active_transforms(|cur| cur * &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn look_at(
|
||||||
|
&mut self,
|
||||||
|
ex: Float,
|
||||||
|
ey: Float,
|
||||||
|
ez: Float,
|
||||||
|
lx: Float,
|
||||||
|
ly: Float,
|
||||||
|
lz: Float,
|
||||||
|
ux: Float,
|
||||||
|
uy: Float,
|
||||||
|
uz: Float,
|
||||||
|
loc: FileLoc,
|
||||||
|
) {
|
||||||
|
let result = look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
|
||||||
|
match result {
|
||||||
|
Ok(t) => {
|
||||||
|
self.for_active_transforms(|cur| cur * &t);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {} at {}", e, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) {
|
||||||
|
let result = TransformGeneric::from_flat(m);
|
||||||
|
match result {
|
||||||
|
Ok(t) => {
|
||||||
|
self.for_active_transforms(|cur| cur * &t);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {} at {}", e, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform(&mut self, m: &[Float; 16], loc: FileLoc) {
|
||||||
|
let result = TransformGeneric::from_flat(m);
|
||||||
|
match result {
|
||||||
|
Ok(t) => {
|
||||||
|
self.for_active_transforms(|_| t);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {} at {}", e, loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coordinate_system(&mut self, name: &str, _loc: FileLoc) {
|
||||||
|
self.named_coordinate_systems
|
||||||
|
.insert(name.to_string(), self.graphics_state.ctm);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) {
|
||||||
|
if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
|
||||||
|
self.graphics_state.ctm = *saved_ctm;
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Couldn't find named coordinate system \"{}\" at {}",
|
||||||
|
name, loc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
self.verify_options("Camera", &loc);
|
||||||
|
|
||||||
|
let camera_from_world = self.graphics_state.ctm;
|
||||||
|
|
||||||
|
let world_from_camera = camera_from_world.inverse();
|
||||||
|
|
||||||
|
self.named_coordinate_systems
|
||||||
|
.insert("camera".to_string(), world_from_camera);
|
||||||
|
|
||||||
|
let animated_world_from_cam = AnimatedTransform::new(
|
||||||
|
&world_from_camera.t[0],
|
||||||
|
self.graphics_state.transform_start_time,
|
||||||
|
&world_from_camera.t[1],
|
||||||
|
self.graphics_state.transform_end_time,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rendering_space = RenderingCoordinateSystem::CameraWorld;
|
||||||
|
let camera_transform =
|
||||||
|
CameraTransform::from_world(animated_world_from_cam, rendering_space);
|
||||||
|
self.render_from_world = camera_from_world.t[0];
|
||||||
|
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
|
||||||
|
self.current_camera = Some(CameraSceneEntity {
|
||||||
|
base: SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
},
|
||||||
|
camera_transform,
|
||||||
|
|
||||||
|
medium: self.graphics_state.current_outside_medium.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_transform_all(&mut self, _loc: FileLoc) {
|
||||||
|
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_transform_end_time(&mut self, _loc: FileLoc) {
|
||||||
|
self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS;
|
||||||
|
}
|
||||||
|
fn active_transform_start_time(&mut self, _loc: FileLoc) {
|
||||||
|
self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) {
|
||||||
|
self.verify_options("TransformTimes", &loc);
|
||||||
|
self.graphics_state.transform_start_time = start;
|
||||||
|
self.graphics_state.transform_end_time = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
self.verify_options("PixelFilter", &loc);
|
||||||
|
self.current_filter = Some(SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
self.verify_options("Film", &loc);
|
||||||
|
self.current_filter = Some(SceneEntityBase {
|
||||||
|
name: type_name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
self.verify_options("PixelFilter", &loc);
|
||||||
|
self.current_filter = Some(SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
self.verify_options("PixelFilter", &loc);
|
||||||
|
self.current_filter = Some(SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let curr_name = normalize_utf8(name);
|
||||||
|
self.verify_world("MakeNamedMaterial", &loc);
|
||||||
|
|
||||||
|
if !self.named_material_names.insert(curr_name.to_string()) {
|
||||||
|
eprintln!("Error: {}: named material '{}' redefined.", loc, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
|
||||||
|
let entity = SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.scene
|
||||||
|
.named_materials
|
||||||
|
.lock()
|
||||||
|
.push((curr_name.to_string(), entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) {
|
||||||
|
let inside = normalize_utf8(inside_name);
|
||||||
|
let outside = normalize_utf8(outside_name);
|
||||||
|
|
||||||
|
self.graphics_state.current_inside_medium = inside;
|
||||||
|
self.graphics_state.current_outside_medium = outside;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let parameters =
|
||||||
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
||||||
|
self.verify_options("Sampler", &loc);
|
||||||
|
self.current_sampler = Some(SceneEntityBase {
|
||||||
|
name: name.to_string(),
|
||||||
|
loc,
|
||||||
|
parameters,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn world_begin(&mut self, loc: FileLoc) {
|
||||||
|
self.verify_options("WorldBegin", &loc);
|
||||||
|
self.current_block = BlockState::WorldBlock;
|
||||||
|
for i in 0..MAX_TRANSFORMS {
|
||||||
|
self.graphics_state.ctm[i] = TransformGeneric::<Float>::default();
|
||||||
|
}
|
||||||
|
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
|
||||||
|
self.named_coordinate_systems
|
||||||
|
.insert("world".to_string(), self.graphics_state.ctm);
|
||||||
|
let scene = Arc::clone(&self.scene);
|
||||||
|
scene.set_options(
|
||||||
|
self.current_filter
|
||||||
|
.take()
|
||||||
|
.expect("Filter not set before WorldBegin"),
|
||||||
|
self.current_film
|
||||||
|
.take()
|
||||||
|
.expect("Film not set before WorldBegin"),
|
||||||
|
self.current_camera
|
||||||
|
.take()
|
||||||
|
.expect("Camera not set before WorldBegin"),
|
||||||
|
self.current_sampler
|
||||||
|
.take()
|
||||||
|
.expect("Sampler not set before WorldBegin"),
|
||||||
|
self.current_integrator
|
||||||
|
.take()
|
||||||
|
.expect("Integrator not set before WorldBegin"),
|
||||||
|
self.current_accelerator
|
||||||
|
.take()
|
||||||
|
.expect("Accelerator not set before WorldBegin"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_begin(&mut self, loc: FileLoc) {
|
||||||
|
self.verify_world("AttributeBegin", &loc);
|
||||||
|
self.pushed_graphics_states
|
||||||
|
.push(self.graphics_state.clone());
|
||||||
|
self.push_stack.push(('a', loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_end(&mut self, loc: FileLoc) {
|
||||||
|
self.verify_world("AttributeEnd", &loc);
|
||||||
|
if self.pushed_graphics_states.is_empty() {
|
||||||
|
log::error!(
|
||||||
|
"[{:?}] Unmatched AttributeEnd encountered. Ignoring it.",
|
||||||
|
loc
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(state) = self.pushed_graphics_states.pop() {
|
||||||
|
self.graphics_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((kind, start_loc)) = self.push_stack.pop() {
|
||||||
|
if kind == 'o' {
|
||||||
|
log::error!(
|
||||||
|
"[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd",
|
||||||
|
loc,
|
||||||
|
start_loc
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) {
|
||||||
|
let current_attributes = match target {
|
||||||
|
"shape" => &mut self.graphics_state.shape_attributes,
|
||||||
|
"light" => &mut self.graphics_state.light_attributes,
|
||||||
|
"material" => &mut self.graphics_state.material_attributes,
|
||||||
|
"medium" => &mut self.graphics_state.medium_attributes,
|
||||||
|
"texture" => &mut self.graphics_state.texture_attributes,
|
||||||
|
_ => {
|
||||||
|
log::error!(
|
||||||
|
"[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
|
||||||
|
\"material\", \"medium\", or \"texture\".",
|
||||||
|
loc,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_color_space = self.graphics_state.color_space.clone();
|
||||||
|
|
||||||
|
for mut p in params {
|
||||||
|
p.may_be_unused = true;
|
||||||
|
p.color_space = active_color_space.clone();
|
||||||
|
|
||||||
|
current_attributes.push(p.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn texture(
|
||||||
|
&mut self,
|
||||||
|
_name: &str,
|
||||||
|
_type_name: &str,
|
||||||
|
_tex_name: &str,
|
||||||
|
_params: &ParsedParameterVector,
|
||||||
|
loc: FileLoc,
|
||||||
|
) {
|
||||||
|
self.verify_world("Texture", &loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn named_material(&mut self, _name: &str, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn object_begin(&mut self, _name: &str, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn object_end(&mut self, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn object_instance(&mut self, _name: &str, _loc: FileLoc) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn end_of_files(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
use super::Spectrum;
|
|
||||||
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|
||||||
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
|
|
||||||
use crate::core::cie;
|
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
fn create_cie_spectrum(data: &[Float]) -> Spectrum {
|
|
||||||
let pls = PiecewiseLinearSpectrum::from_interleaved(data);
|
|
||||||
|
|
||||||
let dss = DenselySampledSpectrum::from_spectrum(
|
|
||||||
&Spectrum::PiecewiseLinear(pls),
|
|
||||||
LAMBDA_MIN,
|
|
||||||
LAMBDA_MAX,
|
|
||||||
);
|
|
||||||
|
|
||||||
Spectrum::DenselySampled(dss)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_x() -> &'static Spectrum {
|
|
||||||
static X: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_X));
|
|
||||||
&X
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_y() -> &'static Spectrum {
|
|
||||||
static Y: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_Y));
|
|
||||||
&Y
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_z() -> &'static Spectrum {
|
|
||||||
static Z: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&cie::CIE_Z));
|
|
||||||
&Z
|
|
||||||
}
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::spectra::{
|
|
||||||
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
|
||||||
};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ConstantSpectrum {
|
|
||||||
c: Float,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConstantSpectrum {
|
|
||||||
pub fn new(c: Float) -> Self {
|
|
||||||
Self { c }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for ConstantSpectrum {
|
|
||||||
fn evaluate(&self, _lambda: Float) -> Float {
|
|
||||||
self.c
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value(&self) -> Float {
|
|
||||||
self.c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DenselySampledSpectrum {
|
|
||||||
lambda_min: i32,
|
|
||||||
lambda_max: i32,
|
|
||||||
values: Vec<Float>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DenselySampledSpectrum {
|
|
||||||
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
|
||||||
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
|
||||||
Self {
|
|
||||||
lambda_min,
|
|
||||||
lambda_max,
|
|
||||||
values: vec![0.0; n_values],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_spectrum(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
|
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
|
||||||
if s.values.is_empty() {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for lambda in lambda_min..=lambda_max {
|
|
||||||
let index = (lambda - lambda_min) as usize;
|
|
||||||
s.values[index] = spec.evaluate(lambda as Float);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Float) -> Float,
|
|
||||||
{
|
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
|
||||||
if s.values.is_empty() {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for lambda in lambda_min..=lambda_max {
|
|
||||||
let index = (lambda - lambda_min) as usize;
|
|
||||||
s.values[index] = f(lambda as Float);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
let mut s = SampledSpectrum::default();
|
|
||||||
|
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
|
||||||
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
|
|
||||||
if offset >= self.values.len() {
|
|
||||||
s[i] = 0.;
|
|
||||||
} else {
|
|
||||||
s[i] = self.values[offset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn min_component_value(&self) -> Float {
|
|
||||||
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_component_value(&self) -> Float {
|
|
||||||
self.values
|
|
||||||
.iter()
|
|
||||||
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn average(&self) -> Float {
|
|
||||||
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
|
||||||
let mut r = Self::new(1, 1);
|
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
|
||||||
r.values[i] = if rhs[i] != 0.0 {
|
|
||||||
self.values[i] / rhs.values[i]
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale(&mut self, factor: Float) {
|
|
||||||
for v in &mut self.values {
|
|
||||||
*v *= factor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for DenselySampledSpectrum {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
if self.lambda_min != other.lambda_min
|
|
||||||
|| self.lambda_max != other.lambda_max
|
|
||||||
|| self.values.len() != other.values.len()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values
|
|
||||||
.iter()
|
|
||||||
.zip(&other.values)
|
|
||||||
.all(|(a, b)| a.to_bits() == b.to_bits())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for DenselySampledSpectrum {}
|
|
||||||
|
|
||||||
impl Hash for DenselySampledSpectrum {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.lambda_min.hash(state);
|
|
||||||
self.lambda_max.hash(state);
|
|
||||||
|
|
||||||
for v in &self.values {
|
|
||||||
v.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for DenselySampledSpectrum {
|
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
|
||||||
let offset = (lambda.round() as i32) - self.lambda_min;
|
|
||||||
if offset < 0 || offset as usize >= self.values.len() {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
self.values[offset as usize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value(&self) -> Float {
|
|
||||||
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PiecewiseLinearSpectrum {
|
|
||||||
samples: Vec<(Float, Float)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PiecewiseLinearSpectrum {
|
|
||||||
pub fn from_interleaved(data: &[Float]) -> Self {
|
|
||||||
assert!(
|
|
||||||
data.len().is_multiple_of(2),
|
|
||||||
"Interleaved data must have an even number of elements"
|
|
||||||
);
|
|
||||||
let mut samples = Vec::new();
|
|
||||||
for pair in data.chunks(2) {
|
|
||||||
samples.push((pair[0], pair[1]));
|
|
||||||
}
|
|
||||||
samples.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
|
||||||
Self { samples }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for PiecewiseLinearSpectrum {
|
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
|
||||||
if self.samples.is_empty() {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
// Handle boundary conditions
|
|
||||||
if lambda <= self.samples[0].0 {
|
|
||||||
return self.samples[0].1;
|
|
||||||
}
|
|
||||||
if lambda >= self.samples.last().unwrap().0 {
|
|
||||||
return self.samples.last().unwrap().1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = self.samples.partition_point(|s| s.0 < lambda);
|
|
||||||
let s1 = self.samples[i - 1];
|
|
||||||
let s2 = self.samples[i];
|
|
||||||
|
|
||||||
let t = (lambda - s1.0) / (s2.0 - s1.0);
|
|
||||||
(1.0 - t) * s1.1 + t * s2.1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value(&self) -> Float {
|
|
||||||
if self.samples.is_empty() {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
self.samples
|
|
||||||
.iter()
|
|
||||||
.map(|(_, value)| *value)
|
|
||||||
.fold(0.0, |a, b| a.max(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct BlackbodySpectrum {
|
|
||||||
temperature: Float,
|
|
||||||
normalization_factor: Float,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Planck's Law
|
|
||||||
impl BlackbodySpectrum {
|
|
||||||
const C: Float = 299792458.0;
|
|
||||||
const H: Float = 6.62606957e-34;
|
|
||||||
const KB: Float = 1.3806488e-23;
|
|
||||||
|
|
||||||
pub fn new(temperature: Float) -> Self {
|
|
||||||
// Physical constants
|
|
||||||
let lambda_max = 2.8977721e-3 / temperature * 1e9;
|
|
||||||
let max_val = Self::planck_law(lambda_max, temperature);
|
|
||||||
Self {
|
|
||||||
temperature,
|
|
||||||
normalization_factor: if max_val > 0.0 { 1.0 / max_val } else { 0.0 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
|
|
||||||
if temp <= 0.0 {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
let lambda_m = lambda_nm * 1e-9;
|
|
||||||
let c1 = 2.0 * Self::H * Self::C * Self::C;
|
|
||||||
let c2 = (Self::H * Self::C) / Self::KB;
|
|
||||||
|
|
||||||
let numerator = c1 / lambda_m.powi(5);
|
|
||||||
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
|
|
||||||
|
|
||||||
if denominator.is_infinite() {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
numerator / denominator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
SampledSpectrum::from_fn(|i| {
|
|
||||||
Self::planck_law(lambda[i], self.temperature) * self.normalization_factor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for BlackbodySpectrum {
|
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
|
||||||
Self::planck_law(lambda, self.temperature) * self.normalization_factor
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_value(&self) -> Float {
|
|
||||||
let lambda_max = 2.8977721e-3 / self.temperature * 1e9;
|
|
||||||
Self::planck_law(lambda_max, self.temperature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1002
src/utils/color.rs
1002
src/utils/color.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,107 +0,0 @@
|
||||||
use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
|
|
||||||
use super::math::SquareMatrix;
|
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::geometry::Point2f;
|
|
||||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum};
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use std::cmp::{Eq, PartialEq};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RGBColorSpace {
|
|
||||||
pub r: Point2f,
|
|
||||||
pub g: Point2f,
|
|
||||||
pub b: Point2f,
|
|
||||||
pub w: Point2f,
|
|
||||||
pub illuminant: Spectrum,
|
|
||||||
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
|
|
||||||
pub xyz_from_rgb: SquareMatrix<Float, 3>,
|
|
||||||
pub rgb_from_xyz: SquareMatrix<Float, 3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RGBColorSpace {
|
|
||||||
pub fn new(
|
|
||||||
r: Point2f,
|
|
||||||
g: Point2f,
|
|
||||||
b: Point2f,
|
|
||||||
illuminant: Spectrum,
|
|
||||||
rgb_to_spectrum_table: RGBToSpectrumTable,
|
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let w_xyz: XYZ = illuminant.to_xyz();
|
|
||||||
let w = w_xyz.xy();
|
|
||||||
let r_xyz = XYZ::from_xyy(r, Some(1.0));
|
|
||||||
let g_xyz = XYZ::from_xyy(g, Some(1.0));
|
|
||||||
let b_xyz = XYZ::from_xyy(b, Some(1.0));
|
|
||||||
let rgb_values = [
|
|
||||||
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
|
||||||
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
|
||||||
[r_xyz.z(), g_xyz.z(), g_xyz.z()],
|
|
||||||
];
|
|
||||||
let rgb = SquareMatrix::new(rgb_values);
|
|
||||||
let c: RGB = rgb.inverse()? * w_xyz;
|
|
||||||
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
|
|
||||||
let rgb_from_xyz = xyz_from_rgb
|
|
||||||
.inverse()
|
|
||||||
.expect("XYZ from RGB matrix is singular");
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
w,
|
|
||||||
illuminant,
|
|
||||||
rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table),
|
|
||||||
xyz_from_rgb,
|
|
||||||
rgb_from_xyz,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
|
||||||
self.xyz_from_rgb * rgb
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_rgb(&self, xyz: XYZ) -> RGB {
|
|
||||||
self.rgb_from_xyz * xyz
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
|
||||||
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix<Float, 3> {
|
|
||||||
if self == other {
|
|
||||||
return SquareMatrix::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rgb_from_xyz * other.xyz_from_rgb
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn srgb() -> &'static Self {
|
|
||||||
static SRGB_SPACE: Lazy<RGBColorSpace> = Lazy::new(|| {
|
|
||||||
let r = Point2f::new(0.64, 0.33);
|
|
||||||
let g = Point2f::new(0.30, 0.60);
|
|
||||||
let b = Point2f::new(0.15, 0.06);
|
|
||||||
|
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
|
||||||
let table = RGBToSpectrumTable::srgb();
|
|
||||||
|
|
||||||
RGBColorSpace::new(r, g, b, illuminant, table)
|
|
||||||
.expect("Failed to initialize standard sRGB color space")
|
|
||||||
});
|
|
||||||
|
|
||||||
&SRGB_SPACE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for RGBColorSpace {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.r == other.r
|
|
||||||
&& self.g == other.g
|
|
||||||
&& self.b == other.b
|
|
||||||
&& self.w == other.w
|
|
||||||
&& Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +1,33 @@
|
||||||
use image_rs::{ImageError as IError, error};
|
#[derive(Clone, Debug)]
|
||||||
use std::fmt;
|
pub struct FileLoc {
|
||||||
use thiserror::Error;
|
pub filename: Arc<str>,
|
||||||
|
pub line: i32,
|
||||||
use crate::image::PixelFormat;
|
pub column: i32,
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum LlsError {
|
|
||||||
SingularMatrix,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
impl Default for FileLoc {
|
||||||
pub enum InversionError {
|
fn default() -> Self {
|
||||||
SingularMatrix,
|
Self {
|
||||||
EmptyMatrix,
|
filename: Arc::from(""),
|
||||||
}
|
line: 1,
|
||||||
|
column: 0,
|
||||||
impl fmt::Display for LlsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
LlsError::SingularMatrix => write!(f, "Matrix is singular and cannot be inverted."),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for InversionError {
|
impl FileLoc {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
pub fn new(filename: &str) -> Self {
|
||||||
match self {
|
Self {
|
||||||
InversionError::SingularMatrix => {
|
filename: Arc::from(filename),
|
||||||
write!(f, "Matrix is singular and cannot be inverted.")
|
line: 1,
|
||||||
|
column: 0,
|
||||||
}
|
}
|
||||||
InversionError::EmptyMatrix => write!(f, "Matrix is empty and cannot be inverted."),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FileLoc {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}:{}", self.filename, self.line, self.column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
72
src/utils/file.rs
Normal file
72
src/utils/file.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use pbrt::Float;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, BufReader, Error, ErrorKind, Read};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
static SEARCH_DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
|
||||||
|
fn set_search_directory(filename: &str) {
|
||||||
|
let path = Path::new(filename);
|
||||||
|
let dir = if path.is_dir() {
|
||||||
|
path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
path.parent().unwrap_or(Path::new("")).to_path_buf()
|
||||||
|
};
|
||||||
|
let _ = SEARCH_DIRECTORY.set(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
|
||||||
|
let content = fs::read_to_string(filename)?;
|
||||||
|
|
||||||
|
let mut values = Vec::new();
|
||||||
|
|
||||||
|
for (line_num, line) in content.lines().enumerate() {
|
||||||
|
// Strip comments
|
||||||
|
let cleaned = line.split('#').next().unwrap_or("");
|
||||||
|
|
||||||
|
for token in cleaned.split_whitespace() {
|
||||||
|
match token.parse::<Float>() {
|
||||||
|
Ok(val) => values.push(val),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"Failed to parse float '{}' at line {}: {}",
|
||||||
|
token,
|
||||||
|
line_num + 1,
|
||||||
|
e
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_filename(filename: &str) -> String {
|
||||||
|
let search_directory = SEARCH_DIRECTORY
|
||||||
|
.get()
|
||||||
|
.map(|p| p.as_path())
|
||||||
|
.unwrap_or(Path::new(""));
|
||||||
|
|
||||||
|
if search_directory.as_os_str().is_empty()
|
||||||
|
|| filename.is_empty()
|
||||||
|
|| Path::new(filename).is_absolute()
|
||||||
|
{
|
||||||
|
return filename.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let filepath = search_directory.join(filename);
|
||||||
|
if filepath.exists() {
|
||||||
|
filepath
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap_or_else(|_| filepath.to_path_buf())
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
filename.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::spectra::color::{ColorEncoding, LINEAR, SRGB};
|
||||||
use crate::image::{
|
|
||||||
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
|
|
||||||
};
|
|
||||||
use crate::utils::color::{ColorEncoding, LINEAR, SRGB};
|
|
||||||
use crate::utils::error::ImageError;
|
use crate::utils::error::ImageError;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
|
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
|
||||||
use image_rs::ImageReader;
|
use image_rs::ImageReader;
|
||||||
use image_rs::{DynamicImage, ImageBuffer, Rgb, Rgba};
|
use image_rs::{DynamicImage, ImageBuffer, Rgb, Rgba};
|
||||||
|
use pbrt::Float;
|
||||||
|
use pbrt::image::{
|
||||||
|
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
|
||||||
|
};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
impl Image {
|
pub trait ImageIO {
|
||||||
pub fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
|
fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata>;
|
||||||
|
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<()>;
|
||||||
|
fn write_png(&self, path: &Path) -> Result<()>;
|
||||||
|
fn write_exr(&self, path: &Path, metadata: &ImageMetadata) -> Result<()>;
|
||||||
|
fn write_qoi(&self, path: &Path) -> Result<()>;
|
||||||
|
fn write_pfm(&self, path: &Path) -> Result<()>;
|
||||||
|
fn to_u8_buffer(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageIO for Image {
|
||||||
|
fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
|
||||||
let ext = path
|
let ext = path
|
||||||
.extension()
|
.extension()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
|
|
@ -27,7 +37,7 @@ impl Image {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> {
|
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> {
|
||||||
let path = Path::new(filename);
|
let path = Path::new(filename);
|
||||||
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||||
let res = match ext.to_lowercase().as_str() {
|
let res = match ext.to_lowercase().as_str() {
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
|
||||||
use crate::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
|
use crate::core::pbrt::Float;
|
||||||
use crate::image::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
use crate::image::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
||||||
use crate::utils::color::{ColorEncoding, RGB};
|
use crate::spectra::{RGB, RGBColorSpace, color::ColorEncoding};
|
||||||
use crate::utils::colorspace::RGBColorSpace;
|
use crate::utils::math::{lerp, safe_sqrt, square};
|
||||||
use crate::utils::math::{safe_sqrt, square};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue