-
Notifications
You must be signed in to change notification settings - Fork 33
HTML Rendering System #48
Changes from all commits
9f1b1cc
a2611f4
2d0665b
6dcbb7a
851a45a
e215b5f
afba7cc
0d55dc5
d545799
e079756
2b32031
31cc11c
e6afdb2
65a44d3
9539c3e
7e5b28b
cc530fa
708dabe
a035a5b
0f9e501
1d668e4
ae97325
2765fce
71e19fe
37e5331
f0b9545
de83c22
3331f84
102d9d0
6e7d5f2
a61544a
bbd9cc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ authors = ["Wojciech Danilo <[email protected]>"] | |
edition = "2018" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
crate-type = ["rlib", "cdylib"] | ||
|
||
[features] | ||
default = ["console_error_panic_hook"] | ||
|
@@ -29,7 +29,11 @@ console_error_panic_hook = { version = "0.1.1", optional = true } | |
version = "0.3.4" | ||
features = [ | ||
'Document', | ||
'Node', | ||
'Element', | ||
'HtmlElement', | ||
'HtmlCollection', | ||
'CssStyleDeclaration', | ||
'HtmlCanvasElement', | ||
'WebGlBuffer', | ||
'WebGlRenderingContext', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod opt_vec; | ||
pub mod types; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
use crate::prelude::*; | ||
use super::types::Index; | ||
|
||
// ============== | ||
// === OptVec === | ||
|
@@ -11,21 +12,25 @@ use crate::prelude::*; | |
pub struct OptVec<T> { | ||
#[shrinkwrap(main_field)] | ||
pub items: Vec<Option<T>>, | ||
pub free_ixs: Vec<usize>, | ||
pub free_ixs: Vec<Index>, | ||
} | ||
|
||
impl<T> Default for OptVec<T> { | ||
fn default() -> Self { | ||
let items = Default::default(); | ||
let free_ixs = Default::default(); | ||
Self { items, free_ixs } | ||
} | ||
} | ||
|
||
impl<T> OptVec<T> { | ||
/// Constructs a new, empty `Vec<T>`. It will not allocate until elements | ||
/// are pushed onto it. | ||
pub const fn new() -> Self { | ||
let items = Vec::new(); | ||
let free_ixs = Vec::new(); | ||
Self { items, free_ixs } | ||
} | ||
pub fn new() -> Self { default() } | ||
|
||
/// Finds a free index and inserts the element. The index is re-used in case | ||
/// the array is sparse or is added in case of no free places. | ||
pub fn insert<F: FnOnce(usize) -> T>(&mut self, f: F) -> usize { | ||
pub fn insert<F: FnOnce(usize) -> T>(&mut self, f: F) -> Index { | ||
match self.free_ixs.pop() { | ||
None => { | ||
let ix = self.items.len(); | ||
|
@@ -42,11 +47,48 @@ impl<T> OptVec<T> { | |
/// Removes the element at provided index and marks the index to be reused. | ||
/// Does nothing if the index was already empty. Panics if the index was out | ||
/// of bounds. | ||
pub fn remove(&mut self, ix: usize) -> Option<T> { | ||
pub fn remove(&mut self, ix: Index) -> Option<T> { | ||
let item = self.items[ix].take(); | ||
item.iter().for_each(|_| self.free_ixs.push(ix)); | ||
item | ||
} | ||
|
||
/// Returns the number of elements in the vector, also referred to as its 'length'. | ||
pub fn len(&self) -> usize { | ||
self.items.len() - self.free_ixs.len() | ||
} | ||
|
||
/// Returns true if vector contains no element. | ||
pub fn is_empty(&self) -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No docs. I know this sounds stupid to add docs to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /// Returns true if vector contains no element. |
||
self.items.len() == self.free_ixs.len() | ||
} | ||
} | ||
|
||
// ============ | ||
// === Iter === | ||
// ============ | ||
|
||
/// We can use Iter to iterate over OptVec<T> similarly to Vec<T>. | ||
pub struct Iter<'a, T> { | ||
iter : std::slice::Iter<'a, Option<T>> | ||
} | ||
|
||
impl<'a, T> Iterator for Iter<'a, T> { | ||
type Item = &'a T; | ||
fn next(&mut self) -> Option<Self::Item> { | ||
if let Some(opt_item) = self.iter.next() { | ||
// If opt_item has some item, we return it, else we try the next one | ||
if let Some(item) = opt_item { Some(item) } else { self.next() } | ||
} else { None } | ||
} | ||
} | ||
|
||
impl<'a, T> IntoIterator for &'a OptVec<T> { | ||
type Item = &'a T; | ||
type IntoIter = Iter<'a, T>; | ||
fn into_iter(self) -> Self::IntoIter { | ||
Iter { iter : (&self.items).iter() } | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
|
@@ -56,14 +98,51 @@ mod tests { | |
#[test] | ||
fn test_add() { | ||
let mut v = OptVec::new(); | ||
assert!(v.is_empty(), "OptVec should be created empty"); | ||
|
||
let ix1 = v.insert(|_| 1); | ||
assert_eq!(ix1, 0, "ix1 should be indexed at 0"); | ||
assert_eq!(v.len(), 1, "OptVec should have 1 item now"); | ||
assert!(!v.is_empty(), "OptVec is no longer empty now"); | ||
|
||
let ix2 = v.insert(|_| 2); | ||
v.remove(ix1); | ||
assert_eq!(ix2, 1, "ix2 should be indexed at 1"); | ||
assert_eq!(v.len(), 2); | ||
|
||
v.remove(ix1); // remove ix1 (0) and make 0 index free | ||
assert_eq!(v.len(), 1); // removing should decrease len by 1 | ||
|
||
v.remove(ix2); // remove ix2 (1) and make 1 index free | ||
assert_eq!(v.len(), 0); | ||
assert!(v.is_empty(), "OptVec should be empty now"); | ||
|
||
let ix3 = v.insert(|_| 3); | ||
assert_eq!(v.len(), 1); | ||
let ix4 = v.insert(|_| 4); | ||
assert_eq!(ix1, 0); | ||
assert_eq!(ix2, 1); | ||
assert_eq!(ix3, 0); | ||
assert_eq!(ix4, 2); | ||
assert_eq!(ix3, 1, "ix3 should be the first freed index"); | ||
assert_eq!(ix4, 0, "ix4 should be the second freed index"); | ||
assert_eq!(v.len(), 2); | ||
} | ||
|
||
#[test] | ||
fn test_iter() { | ||
let mut v = OptVec::new(); | ||
|
||
let ix1 = v.insert(|_| 0); | ||
let _ix2 = v.insert(|_| 1); | ||
let _ix3 = v.insert(|_| 2); | ||
|
||
assert_eq!(v.len(), 3, "OptVec should have 3 items"); | ||
|
||
for (i, value) in v.into_iter().enumerate() { | ||
assert_eq!(i, *value); | ||
} | ||
|
||
v.remove(ix1); | ||
assert_eq!(v.len(), 2, "OptVec should have 2 items"); | ||
for (i, value) in v.into_iter().enumerate() { | ||
// we add + 1, because the fisrt item is 1 now. | ||
assert_eq!(i + 1, *value); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub type Index = usize; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pub mod rendering; | ||
pub mod symbol; | ||
pub mod workspace; | ||
pub mod world; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use crate::prelude::*; | ||
|
||
use super::Object; | ||
|
||
use nalgebra::base::Matrix4; | ||
use nalgebra::geometry::Perspective3; | ||
use std::f32::consts::PI; | ||
|
||
// ============== | ||
// === Camera === | ||
// ============== | ||
|
||
/// A 3D camera representation with its own 3D `Transform` and | ||
/// projection matrix. | ||
#[derive(Shrinkwrap, Debug)] | ||
#[shrinkwrap(mutable)] | ||
pub struct Camera { | ||
#[shrinkwrap(main_field)] | ||
pub object : Object, | ||
pub projection : Matrix4<f32>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alignment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the expected alignment here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean to align ":"? If that's so, it's fixed. |
||
} | ||
|
||
impl Camera { | ||
/// Creates a Camera with perspective projection. | ||
pub fn perspective(fov: f32, aspect: f32, z_near: f32, z_far: f32) -> Self { | ||
let fov = fov / 180.0 * PI; | ||
let projection = Perspective3::new(aspect, fov, z_near, z_far); | ||
let projection = *projection.as_matrix(); | ||
let object = default(); | ||
Self { object, projection } | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
#[test] | ||
fn perspective() { | ||
use super::Camera; | ||
use nalgebra::Matrix4; | ||
let camera = Camera::perspective(45.0, 1920.0 / 1080.0, 1.0, 1000.0); | ||
let expected = Matrix4::new | ||
( 1.357995, 0.0, 0.0, 0.0 | ||
, 0.0 , 2.4142134, 0.0, 0.0 | ||
, 0.0 , 0.0, -1.002002, -2.002002 | ||
, 0.0 , 0.0, -1.0, 0.0 | ||
); | ||
assert_eq!(camera.projection, expected); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use crate::prelude::*; | ||
|
||
use super::Object; | ||
|
||
use crate::system::web::create_element; | ||
use crate::system::web::dyn_into; | ||
use crate::system::web::Result; | ||
use crate::system::web::Error; | ||
use crate::system::web::StyleSetter; | ||
use nalgebra::Vector2; | ||
use web_sys::HtmlElement; | ||
|
||
// ================== | ||
// === HTMLObject === | ||
// ================== | ||
|
||
/// A structure for representing a 3D HTMLElement in a `HTMLScene`. | ||
#[derive(Shrinkwrap, Debug)] | ||
#[shrinkwrap(mutable)] | ||
pub struct HTMLObject { | ||
#[shrinkwrap(main_field)] | ||
pub object : Object, | ||
pub element : HtmlElement, | ||
pub dimensions : Vector2<f32>, | ||
wdanilo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
impl HTMLObject { | ||
/// Creates a HTMLObject from element name. | ||
pub fn new(dom_name: &str) -> Result<Self> { | ||
let element = dyn_into(create_element(dom_name)?)?; | ||
Ok(Self::from_element(element)) | ||
} | ||
|
||
/// Creates a HTMLObject from a web_sys::HtmlElement. | ||
pub fn from_element(element: HtmlElement) -> Self { | ||
element.set_property_or_panic("transform-style", "preserve-3d"); | ||
element.set_property_or_panic("position" , "absolute"); | ||
element.set_property_or_panic("width" , "0px"); | ||
element.set_property_or_panic("height" , "0px"); | ||
let object = default(); | ||
let dimensions = Vector2::new(0.0, 0.0); | ||
Self { object, element, dimensions } | ||
} | ||
|
||
/// Creates a HTMLObject from a HTML string. | ||
pub fn from_html_string<T>(html_string: T) -> Result<Self> | ||
where T : AsRef<str> { | ||
let element = create_element("div")?; | ||
element.set_inner_html(html_string.as_ref()); | ||
match element.first_element_child() { | ||
Some(element) => Ok(Self::from_element(dyn_into(element)?)), | ||
None => Err(Error::missing("valid HTML")), | ||
} | ||
} | ||
|
||
/// Sets the underlying HtmlElement dimension. | ||
pub fn set_dimensions(&mut self, width: f32, height: f32) { | ||
self.dimensions = Vector2::new(width, height); | ||
self.element.set_property_or_panic("width", format!("{}px", width)); | ||
self.element.set_property_or_panic("height", format!("{}px", height)); | ||
} | ||
|
||
/// Gets the underlying HtmlElement dimension. | ||
pub fn get_dimensions(&self) -> &Vector2<f32> { | ||
&self.dimensions | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use crate::prelude::*; | ||
|
||
use super::Camera; | ||
use super::HTMLScene; | ||
use crate::math::utils::IntoCSSMatrix; | ||
use crate::math::utils::eps; | ||
use crate::math::utils::invert_y; | ||
|
||
use crate::system::web::StyleSetter; | ||
|
||
// ==================== | ||
// === HTMLRenderer === | ||
// ==================== | ||
|
||
/// A renderer for `HTMLObject`s. | ||
#[derive(Default, Debug)] | ||
pub struct HTMLRenderer {} | ||
|
||
impl HTMLRenderer { | ||
/// Creates a HTMLRenderer. | ||
pub fn new() -> Self { default() } | ||
|
||
/// Renders the `Scene` from `Camera`'s point of view. | ||
pub fn render(&self, camera: &mut Camera, scene: &HTMLScene) { | ||
// Note [znear from projection matrix] | ||
let half_dim = scene.get_dimensions() / 2.0; | ||
let expr = camera.projection[(1, 1)]; | ||
let near = format!("{}px", expr * half_dim.y); | ||
let trans_cam = camera.transform.to_homogeneous().try_inverse(); | ||
let trans_cam = trans_cam.expect("Camera's matrix is not invertible."); | ||
let trans_cam = trans_cam.map(eps); | ||
let trans_cam = invert_y(trans_cam); | ||
let trans_z = format!("translateZ({})", near); | ||
let matrix3d = trans_cam.into_css_matrix(); | ||
let trans = format!("translate({}px,{}px)", half_dim.x, half_dim.y); | ||
let css = format!("{} {} {}", trans_z, matrix3d, trans); | ||
|
||
scene.div .element.set_property_or_panic("perspective", near); | ||
scene.camera.element.set_property_or_panic("transform" , css); | ||
|
||
for object in &scene.objects { | ||
let mut transform = object.transform.to_homogeneous(); | ||
transform.iter_mut().for_each(|a| *a = eps(*a)); | ||
let matrix3d = transform.into_css_matrix(); | ||
let css = format!("translate(-50%, -50%) {}", matrix3d); | ||
object.element.set_property_or_panic("transform", css); | ||
} | ||
} | ||
} | ||
|
||
// Note [znear from projection matrix] | ||
// ================================= | ||
// https://github.com/mrdoob/three.js/blob/22ed6755399fa180ede84bf18ff6cea0ad66f6c0/examples/js/renderers/CSS3DRenderer.js#L275 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need
rlib
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need it to make the crate available in the tests.
See rust-lang/cargo#6659