diff --git a/Cargo.toml b/Cargo.toml index bf30fe3..e63a2d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ path = "examples/animated_mesh.rs" name = "custom_attribute" path = "examples/custom_attribute.rs" +[[example]] +name = "lights" +path = "examples/lights.rs" + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index cebbef7..ad795cf 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -1091,3 +1091,58 @@ pub extern "C" fn processing_geometry_box(width: f32, height: f32, depth: f32) - .map(|e| e.to_bits()) .unwrap_or(0) } + +#[unsafe(no_mangle)] +pub extern "C" fn processing_light_create_directional( + graphics_id: u64, + color: Color, + illuminance: f32, +) -> u64 { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| light_create_directional(graphics_entity, color.into(), illuminance)) + .map(|e| e.to_bits()) + .unwrap_or(0) +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_light_create_point( + graphics_id: u64, + color: Color, + intensity: f32, + range: f32, + radius: f32, +) -> u64 { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| light_create_point(graphics_entity, color.into(), intensity, range, radius)) + .map(|e| e.to_bits()) + .unwrap_or(0) +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_light_create_spot( + graphics_id: u64, + color: Color, + intensity: f32, + range: f32, + radius: f32, + inner_angle: f32, + outer_angle: f32, +) -> u64 { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| { + light_create_spot( + graphics_entity, + color.into(), + intensity, + range, + radius, + inner_angle, + outer_angle, + ) + }) + .map(|e| e.to_bits()) + .unwrap_or(0) +} diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 3b37229..b9f4348 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -3,6 +3,7 @@ pub mod error; pub mod geometry; mod graphics; pub mod image; +pub mod light; pub mod render; mod surface; pub mod transform; @@ -25,7 +26,7 @@ use tracing::debug; use crate::geometry::{AttributeFormat, AttributeValue}; use crate::graphics::flush; use crate::{ - graphics::GraphicsPlugin, image::ImagePlugin, render::command::DrawCommand, + graphics::GraphicsPlugin, image::ImagePlugin, light::LightPlugin, render::command::DrawCommand, surface::SurfacePlugin, }; @@ -248,6 +249,7 @@ fn create_app(config: Config) -> App { GraphicsPlugin, SurfacePlugin, geometry::GeometryPlugin, + LightPlugin, )); app.add_systems(First, (clear_transient_meshes, activate_cameras)) .add_systems(Update, flush_draw_commands.before(AssetEventSystems)); @@ -774,6 +776,65 @@ pub fn image_destroy(entity: Entity) -> error::Result<()> { }) } +pub fn light_create_directional( + graphics_entity: Entity, + color: Color, + illuminance: f32, +) -> error::Result { + app_mut(|app| { + app.world_mut() + .run_system_cached_with( + light::create_directional, + (graphics_entity, color, illuminance), + ) + .unwrap() + }) +} + +pub fn light_create_point( + graphics_entity: Entity, + color: Color, + intensity: f32, + range: f32, + radius: f32, +) -> error::Result { + app_mut(|app| { + app.world_mut() + .run_system_cached_with( + light::create_point, + (graphics_entity, color, intensity, range, radius), + ) + .unwrap() + }) +} + +pub fn light_create_spot( + graphics_entity: Entity, + color: Color, + intensity: f32, + range: f32, + radius: f32, + inner_angle: f32, + outer_angle: f32, +) -> error::Result { + app_mut(|app| { + app.world_mut() + .run_system_cached_with( + light::create_spot, + ( + graphics_entity, + color, + intensity, + range, + radius, + inner_angle, + outer_angle, + ), + ) + .unwrap() + }) +} + pub fn geometry_layout_create() -> error::Result { app_mut(|app| { Ok(app diff --git a/crates/processing_render/src/light.rs b/crates/processing_render/src/light.rs new file mode 100644 index 0000000..423703c --- /dev/null +++ b/crates/processing_render/src/light.rs @@ -0,0 +1,86 @@ +//! A light in Processing +//! + +use bevy::{camera::visibility::RenderLayers, prelude::*}; + +use crate::{error::ProcessingError, graphics::Graphics}; + +pub struct LightPlugin; + +impl Plugin for LightPlugin { + fn build(&self, _app: &mut App) {} +} + +pub fn create_directional( + In((entity, color, illuminance)): In<(Entity, Color, f32)>, + mut commands: Commands, + graphics: Query<&RenderLayers, With>, +) -> Result { + let layer = graphics + .get(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + Ok(commands + .spawn(( + DirectionalLight { + illuminance, + color, + ..default() + }, + layer.clone(), + )) + .id()) +} + +pub fn create_point( + In((entity, color, intensity, range, radius)): In<(Entity, Color, f32, f32, f32)>, + mut commands: Commands, + graphics: Query<&RenderLayers, With>, +) -> Result { + let layer = graphics + .get(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + Ok(commands + .spawn(( + PointLight { + intensity, + color, + range, + radius, + ..default() + }, + layer.clone(), + )) + .id()) +} + +pub fn create_spot( + In((entity, color, intensity, range, radius, inner_angle, outer_angle)): In<( + Entity, + Color, + f32, + f32, + f32, + f32, + f32, + )>, + mut commands: Commands, + graphics: Query<&RenderLayers, With>, +) -> Result { + let layer = graphics + .get(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)?; + Ok(commands + .spawn(( + SpotLight { + color, + intensity, + range, + radius, + inner_angle, + outer_angle, + ..default() + }, + layer.clone(), + )) + .id()) +} diff --git a/crates/processing_render/src/render/material.rs b/crates/processing_render/src/render/material.rs index fc2030c..e256056 100644 --- a/crates/processing_render/src/render/material.rs +++ b/crates/processing_render/src/render/material.rs @@ -10,7 +10,7 @@ impl MaterialKey { pub fn to_material(&self) -> StandardMaterial { StandardMaterial { base_color: Color::WHITE, - unlit: true, + unlit: false, cull_mode: None, base_color_texture: self.background_image.clone(), alpha_mode: if self.transparent { diff --git a/docs/api.md b/docs/api.md index c0cc4ce..21511fd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -34,6 +34,10 @@ rendering texture that the camera draws to (`ViewTarget`), which is not typicall For consistency, all image functions should accept a graphics object, although its internal image representation is not guaranteed to be the same as a user-created image. +### Light + +[//]: # (TODO: Document Image API object) + ### Image Images are 2D or 3D arrays of pixels that can be drawn onto surfaces. They can be created from files, generated procedurally, @@ -85,4 +89,4 @@ can also be used to implement 2D image processing effects, etc. ### Shader -[//]: # (TODO: Document Shader API object, do we even need this with a sufficiently robust Material API?) \ No newline at end of file +[//]: # (TODO: Document Shader API object, do we even need this with a sufficiently robust Material API?) diff --git a/examples/lights.rs b/examples/lights.rs new file mode 100644 index 0000000..d2aa4b8 --- /dev/null +++ b/examples/lights.rs @@ -0,0 +1,98 @@ +mod glfw; + +use glfw::GlfwContext; +use processing::prelude::*; +use processing_render::render::command::DrawCommand; + +fn main() { + match sketch() { + Ok(_) => { + eprintln!("Sketch completed successfully"); + exit(0).unwrap(); + } + Err(e) => { + eprintln!("Sketch error: {:?}", e); + exit(1).unwrap(); + } + }; +} + +fn sketch() -> error::Result<()> { + let mut glfw_ctx = GlfwContext::new(400, 400)?; + init(Config::default())?; + + let width = 400; + let height = 400; + let scale_factor = 1.0; + + let surface = glfw_ctx.create_surface(width, height, scale_factor)?; + let graphics = graphics_create(surface, width, height)?; + let box_geo = geometry_box(100.0, 100.0, 100.0)?; + + // We will only declare lights in `setup` + // rather than calling some sort of `light()` method inside of `draw` + + // create a directional light + let _dir_light = + light_create_directional(graphics, bevy::color::Color::srgb(0.35, 0.25, 0.5), 500.0)?; + + // create a point light + let point_light_a = light_create_point( + graphics, + bevy::color::Color::srgb(1.0, 0.5, 0.25), + 1_000_000.0, + 200.0, + 0.5, + )?; + transform_set_position(point_light_a, -25.0, 5.0, 51.0)?; + transform_look_at(point_light_a, 0.0, 0.0, 0.0)?; + + // create another point light + let point_light_b = light_create_point( + graphics, + bevy::color::Color::srgb(0.0, 0.5, 0.75), + 2_000_000.0, + 200.0, + 0.25, + )?; + transform_set_position(point_light_b, 0.0, 5.0, 50.5)?; + transform_look_at(point_light_b, 0.0, 0.0, 0.0)?; + + // and a spot light, too! + let spot_light = light_create_spot( + graphics, + bevy::color::Color::srgb(0.25, 0.8, 0.19), + 15.0 * 1_000_000.0, + 200.0, + 0.84, + 0.0, + core::f32::consts::FRAC_PI_4, + )?; + transform_set_position(spot_light, 40.0, 0.0, 70.0)?; + transform_look_at(spot_light, 0.0, 0.0, 0.0)?; + + graphics_mode_3d(graphics)?; + transform_set_position(graphics, 100.0, 100.0, 300.0)?; + transform_look_at(graphics, 0.0, 0.0, 0.0)?; + + let mut angle = 0.0; + + while glfw_ctx.poll_events() { + graphics_begin_draw(graphics)?; + + graphics_record_command( + graphics, + DrawCommand::BackgroundColor(bevy::color::Color::srgb(0.18, 0.20, 0.15)), + )?; + + graphics_record_command(graphics, DrawCommand::PushMatrix)?; + graphics_record_command(graphics, DrawCommand::Rotate { angle })?; + graphics_record_command(graphics, DrawCommand::Geometry(box_geo))?; + graphics_record_command(graphics, DrawCommand::PopMatrix)?; + + graphics_end_draw(graphics)?; + + angle += 0.02; + } + Ok(()) +}