diff --git a/guest/rust/hearth-guest/src/lib.rs b/guest/rust/hearth-guest/src/lib.rs index b8ee1473..1af2370f 100644 --- a/guest/rust/hearth-guest/src/lib.rs +++ b/guest/rust/hearth-guest/src/lib.rs @@ -306,6 +306,7 @@ impl Message { } /// A loaded lump. +#[derive(Debug)] pub struct Lump(u32); impl Drop for Lump { @@ -315,8 +316,8 @@ impl Drop for Lump { } impl Lump { - /// Loads a new lump from in-process data. - pub fn load(data: &[u8]) -> Self { + /// Loads a new lump directly from in-process bytes. + pub fn load_raw(data: &[u8]) -> Self { unsafe { let ptr = data.as_ptr() as u32; let len = data.len() as u32; @@ -325,6 +326,12 @@ impl Lump { } } + /// Loads a JSON-encoded lump from a serializable data type. + pub fn load(data: &impl Serialize) -> Self { + let bytes = serde_json::to_vec(data).unwrap(); + Self::load_raw(&bytes) + } + /// Loads a lump from the ID of an already existing lump. pub fn load_by_id(id: &LumpId) -> Self { unsafe { diff --git a/kindling/host/src/lib.rs b/kindling/host/src/lib.rs index a9fd8bb4..80532692 100644 --- a/kindling/host/src/lib.rs +++ b/kindling/host/src/lib.rs @@ -27,6 +27,7 @@ pub mod canvas; pub mod debug_draw; pub mod fs; pub mod registry; +pub mod renderer; pub mod terminal; pub mod time; pub mod wasm; diff --git a/kindling/host/src/renderer.rs b/kindling/host/src/renderer.rs new file mode 100644 index 00000000..c2735c7d --- /dev/null +++ b/kindling/host/src/renderer.rs @@ -0,0 +1,166 @@ +// Copyright (c) 2024 the Hearth contributors. +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Hearth. +// +// Hearth is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// Hearth is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Hearth. If not, see . + +use super::*; + +use glam::{Mat4, Vec3}; +use hearth_guest::{renderer::*, Lump}; + +lazy_static::lazy_static! { + static ref RENDERER: RequestResponse = + RequestResponse::expect_service("hearth.Renderer"); +} + +/// Set the global ambient lighting levels. +pub fn set_ambient_lighting(color: Vec3) { + let (result, _) = RENDERER.request( + RendererRequest::SetAmbientLighting { + ambient: color.extend(1.0), + }, + &[], + ); + + let _ = result.unwrap(); +} + +/// Update the skybox with the given lump containing [TextureData]. +pub fn set_skybox(texture: &Lump) { + let (result, _) = RENDERER.request( + RendererRequest::SetSkybox { + texture: texture.get_id(), + }, + &[], + ); + + let _ = result.unwrap(); +} + +/// A directional light. +pub struct DirectionalLight(Capability); + +impl Drop for DirectionalLight { + fn drop(&mut self) { + self.0.kill(); + } +} + +impl DirectionalLight { + /// Create a new directional light. + pub fn new(state: DirectionalLightState) -> Self { + let (result, caps) = RENDERER.request( + RendererRequest::AddDirectionalLight { + initial_state: state, + }, + &[], + ); + + let _ = result.expect("failed to create directional light"); + + Self(caps.first().unwrap().clone()) + } + + /// Internal helper function to update this light. + fn update(&self, update: DirectionalLightUpdate) { + self.0.send(&update, &[]); + } + + /// Set this directional light's color. + pub fn set_color(&self, color: Vec3) { + self.update(DirectionalLightUpdate::Color(color)); + } + + /// Set this directional light's intensity. + pub fn set_intensity(&self, intensity: f32) { + self.update(DirectionalLightUpdate::Intensity(intensity)); + } + + /// Set this directional light's direction. + pub fn set_direction(&self, direction: Vec3) { + self.update(DirectionalLightUpdate::Direction(direction)); + } + + /// Set this distanceal light's distance. + pub fn set_distance(&self, distance: f32) { + self.update(DirectionalLightUpdate::Distance(distance)); + } +} + +/// Configuration for the creation of an [Object]. +#[derive(Clone, Debug)] +pub struct ObjectConfig<'a> { + /// A reference to the lump containing this object's [MeshData]. + pub mesh: &'a Lump, + + /// An optional list of skeleton joint matrices for this object. + pub skeleton: Option>, + + /// The lump containing this object's [MaterialData]. + pub material: &'a Lump, + + /// The initial transform of this object. + pub transform: Mat4, +} + +/// An object. +pub struct Object(Capability); + +impl Drop for Object { + fn drop(&mut self) { + self.0.kill(); + } +} + +impl Object { + /// Create a new object in the scene with the given [ObjectConfig]. + pub fn new(config: ObjectConfig) -> Self { + let (result, caps) = RENDERER.request( + RendererRequest::AddObject { + mesh: config.mesh.get_id(), + skeleton: config.skeleton, + material: config.material.get_id(), + transform: config.transform, + }, + &[], + ); + + let _ = result.expect("failed to create object"); + + Self(caps.first().unwrap().clone()) + } + + /// Updates the transform of this object. + pub fn set_transform(&self, transform: Mat4) { + self.0.send(&ObjectUpdate::Transform(transform), &[]); + } + + /// Update the joint matrices of this mesh. + pub fn set_joint_matrices(&self, joints: Vec) { + self.0.send(&ObjectUpdate::JointMatrices(joints), &[]); + } + + /// Update the joint transforms of this mesh. + pub fn set_joint_transforms(&self, joint_global: Vec, inverse_bind: Vec) { + self.0.send( + &ObjectUpdate::JointTransforms { + joint_global, + inverse_bind, + }, + &[], + ); + } +} diff --git a/kindling/services/skybox/src/lib.rs b/kindling/services/skybox/src/lib.rs index e67bc466..927e7923 100644 --- a/kindling/services/skybox/src/lib.rs +++ b/kindling/services/skybox/src/lib.rs @@ -17,9 +17,7 @@ // along with Hearth. If not, see . use hearth_guest::{renderer::*, Lump}; -use kindling_host::prelude::RequestResponse; - -type Renderer = RequestResponse; +use kindling_host::renderer::set_skybox; /// Helper function to append a skybox image to the cube texture data. fn add_face(data: &mut Vec, image: &[u8]) { @@ -37,17 +35,11 @@ pub extern "C" fn run() { add_face(&mut data, include_bytes!("elyvisions/sh_rt.png")); add_face(&mut data, include_bytes!("elyvisions/sh_lf.png")); - let texture = Lump::load( - &serde_json::to_vec(&TextureData { - label: None, - size: (1024, 1024).into(), - data, - }) - .unwrap(), - ) - .get_id(); + let texture = Lump::load(&TextureData { + label: None, + size: (1024, 1024).into(), + data, + }); - let renderer = Renderer::expect_service("hearth.Renderer"); - let (result, _) = renderer.request(RendererRequest::SetSkybox { texture }, &[]); - result.unwrap(); + set_skybox(&texture); }