World Integration 🌎🕊️ to Css3dSystem (https://github.com/enso-org/ide/pull/133)

CSS 3D System x World integration

Original commit: 7c47432a09
This commit is contained in:
Danilo Guanabara 2020-02-06 13:09:29 -03:00 committed by GitHub
parent 692432e498
commit 685c6d0c37
27 changed files with 1101 additions and 740 deletions

View File

@ -45,9 +45,6 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
- name: Install Firefox
run: brew cask install firefox
if: runner.os == 'macOS'
- name: Run wasm-pack tests
uses: actions-rs/cargo@v1
with:
@ -55,7 +52,7 @@ jobs:
args: >
--manifest-path=script/rust/Cargo.toml
--bin test-all
-- --headless --firefox
-- --headless --chrome
size:
name: Check size

View File

@ -38,7 +38,7 @@ pub struct EventListener<T:?Sized> {
}
impl<T:?Sized> EventListener<T> {
fn new<Str:AsRef<str>>(target:EventTarget, name:Str, callback:Closure<T>) -> Self {
fn new<S:Str>(target:EventTarget, name:S, callback:Closure<T>) -> Self {
let name = name.as_ref().to_string();
Self { target,name,callback }
}

View File

@ -11,8 +11,8 @@ use crate::control::io::mouse::button::*;
macro_rules! define_events {
( $( $js_event:ident :: $name:ident ),* $(,)? ) => {$(
#[derive(Debug,Clone,From,Shrinkwrap)]
/// Mouse event wrapper.
#[derive(Debug,Clone,From,Shrinkwrap)]
pub struct $name {
raw: web_sys::$js_event
}

View File

@ -344,6 +344,10 @@ impl DisplayObjectData {
Self {rc}
}
pub fn with_logger<F:FnOnce(&Logger)>(&self, f:F) {
f(&self.rc.borrow().logger)
}
pub fn dispatch(&self, event:&DynEvent) {
self.rc.borrow_mut().dispatch(event)
}

View File

@ -20,9 +20,12 @@ use crate::system::web::resize_observer::ResizeObserver;
use crate::system::web;
use crate::display::object::DisplayObjectOps;
use crate::system::gpu::data::uniform::Uniform;
use crate::system::web::dom::html::Css3dRenderer;
use crate::system::web::dyn_into;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsValue;
use web_sys::HtmlElement;
use crate::control::io::mouse2::MouseManager;
use crate::control::io::mouse2;
@ -62,11 +65,17 @@ impl Shape {
Self {rc}
}
pub fn from_element(element:&HtmlElement) -> Self {
let bounding_box = element.get_bounding_client_rect();
let width = bounding_box.width() as f32;
let height = bounding_box.height() as f32;
Self::new(width,height)
}
pub fn from_window(window:&web_sys::Window) -> Self {
let width = window.inner_width().unwrap().as_f64().unwrap() as f32;
let height = window.inner_height().unwrap().as_f64().unwrap() as f32;
let rc = Rc::new(RefCell::new(ShapeData::new(width,height)));
Self{rc}
Self::new(width,height)
}
@ -215,23 +224,24 @@ shared! { Scene
#[derive(Derivative)]
#[derivative(Debug)]
pub struct SceneData {
root : DisplayObjectData,
canvas : web_sys::HtmlCanvasElement,
context : Context,
symbols : SymbolRegistry,
symbols_dirty : SymbolRegistryDirty,
camera : Camera2d,
shape : Shape,
shape_dirty : ShapeDirty,
logger : Logger,
listeners : Listeners,
variables : UniformScope,
pipeline : RenderPipeline,
composer : RenderComposer,
stats : Stats,
pixel_ratio : Uniform<f32>,
zoom_uniform : Uniform<f32>,
mouse : Mouse,
root : DisplayObjectData,
canvas : web_sys::HtmlCanvasElement,
context : Context,
css3d_renderer : Css3dRenderer,
symbols : SymbolRegistry,
symbols_dirty : SymbolRegistryDirty,
camera : Camera2d,
shape : Shape,
shape_dirty : ShapeDirty,
logger : Logger,
listeners : Listeners,
variables : UniformScope,
pipeline : RenderPipeline,
composer : RenderComposer,
stats : Stats,
pixel_ratio : Uniform<f32>,
zoom_uniform : Uniform<f32>,
mouse : Mouse,
#[derivative(Debug="ignore")]
@ -255,11 +265,11 @@ impl {
let sub_logger = logger.sub("symbols");
let variables = UniformScope::new(logger.sub("global_variables"),&context);
let symbols = SymbolRegistry::new(&variables,&stats,&context,sub_logger,on_change);
let window = crate::system::web::window();
let shape = Shape::from_window(&window);
let shape_data = shape.screen_shape();
let width = shape_data.width;
let height = shape_data.height;
let canvas_parent = dyn_into::<_,HtmlElement>(canvas.parent_node().unwrap()).unwrap();
let shape = Shape::from_element(&canvas_parent);
let screen_shape = shape.screen_shape();
let width = screen_shape.width;
let height = screen_shape.height;
let listeners = Self::init_listeners(&logger,&canvas,&shape,&shape_dirty);
let symbols_dirty = dirty_flag;
let camera = Camera2d::new(logger.sub("camera"),width,height);
@ -284,8 +294,15 @@ impl {
let height = shape.canvas_shape().height as i32;
let composer = RenderComposer::new(&pipeline,&context,&variables,width,height);
let css3d_renderer = Css3dRenderer::from_element_or_panic(&logger,canvas_parent);
Self { pipeline,composer,root,canvas,context,symbols,camera,symbols_dirty,shape,shape_dirty
, logger,listeners,variables,on_resize,stats,pixel_ratio,mouse,zoom_uniform }
, logger,listeners,variables,on_resize,stats,pixel_ratio,mouse,zoom_uniform
, css3d_renderer }
}
pub fn css3d_renderer(&self) -> Css3dRenderer {
self.css3d_renderer.clone()
}
pub fn canvas(&self) -> web_sys::HtmlCanvasElement {
@ -351,6 +368,7 @@ impl {
}
self.logger.info("Rendering meshes.");
self.symbols.render(&self.camera);
self.css3d_renderer.render(&self.camera);
self.composer.run();
})

View File

@ -1,8 +1,9 @@
//! Root module for all example scenes.
pub mod sprite_system;
pub mod css3d_system;
pub mod shapes;
pub mod easing_animator;
pub mod glyph_system;
pub mod shapes;
pub mod sprite_system;
pub mod text_selecting;
pub mod text_typing;

View File

@ -0,0 +1,86 @@
#![allow(missing_docs)]
use crate::system::web;
use web::dom::html::Css3dSystem;
use web::dom::html::Css3dObject;
use web::dom::html::Css3dOrder;
use web::StyleSetter;
use crate::display::object::DisplayObject;
use crate::display::object::DisplayObjectOps;
use crate::display::symbol::geometry::Sprite;
use crate::display::symbol::geometry::SpriteSystem;
use crate::display::world::*;
use crate::prelude::*;
use crate::animation::animator::fixed_step::FixedStepAnimator;
use nalgebra::Vector2;
use nalgebra::Vector3;
use wasm_bindgen::prelude::*;
use crate::display::navigation::navigator::Navigator;
#[wasm_bindgen]
#[allow(dead_code)]
pub fn run_example_css3d_system() {
web::forward_panic_hook_to_console();
web::set_stdout();
init(WorldData::new("canvas"));
}
fn init(world:World) {
let scene = world.scene();
let camera = scene.camera();
let screen = camera.screen();
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
let sprite_system = SpriteSystem::new(&world);
let css3d_system = Css3dSystem::new(&world);
world.add_child(&sprite_system);
world.add_child(&css3d_system);
let mut sprites: Vec<Sprite> = default();
let mut css3d_objects: Vec<Css3dObject> = default();
let count = 10;
for i in 0 .. count {
let x = i as f32;
let width = screen.width * 1.5 / count as f32;
let height = screen.height;
if i % 2 == 0 {
let height = height * 0.75;
let dimensions = Vector2::new(width, height);
let y = screen.height / 2.0;
let position = Vector3::new(width / 1.5 * x + width / 2.0, y, 0.0);
let sprite = sprite_system.new_instance();
sprite.size().set(dimensions);
sprite.mod_position(|t| *t = position);
sprites.push(sprite);
} else {
let dimensions = Vector2::new(width, height);
let position = Vector3::new(width / 1.5 * x + width / 2.0, height / 2.0, 0.0);
let mut object = css3d_system.new_instance("div").expect("Couldn't create div");
let r = ((x + 0.0) * 16.0) as u8;
let g = ((x + 2.0) * 32.0) as u8;
let b = ((x + 4.0) * 64.0) as u8;
let color = iformat!("rgb({r},{g},{b})");
object.dom().set_style_or_panic("background-color",color);
object.set_dimensions(dimensions);
object.mod_position(|t| *t = position);
css3d_objects.push(object);
}
}
world.display_object().update();
let css3d_position = vec![Css3dOrder::Front, Css3dOrder::Back];
let mut i = 0;
let animator = FixedStepAnimator::new(2.0, move |_| {
let _keep_alive = &world;
let _keep_alive = &navigator;
let _keep_alive = &sprites;
let _keep_alive = &sprite_system;
let _keep_alive = &css3d_system;
i = (i + 1) % 2;
for (j, object) in css3d_objects.iter_mut().enumerate() {
object.set_css3d_order(css3d_position[(i + j) % 2]);
}
});
std::mem::forget(animator);
}

View File

@ -23,15 +23,15 @@ use js_sys::Math;
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Clone,Debug)]
/// A simplified Canvas object used in the EasingAnimator example.
#[derive(Clone,Debug)]
pub struct Canvas {
canvas : HtmlCanvasElement,
context : CanvasRenderingContext2d
}
#[derive(Clone,Copy,Debug)]
/// Interpolable properties for our example code.
#[derive(Clone,Copy,Debug)]
pub struct Properties {
/// Position property.
pub position : Vector2<f32>,
@ -78,7 +78,7 @@ impl Canvas {
pub fn new(container_id:&str) -> Self {
let canvas = create_element("canvas").unwrap();
let canvas: HtmlCanvasElement = canvas.dyn_into().unwrap();
canvas.set_property_or_panic("border", "1px solid black");
canvas.set_style_or_panic("border", "1px solid black");
canvas.set_width (256);
canvas.set_height(256);
@ -228,11 +228,11 @@ impl Example {
where F1:FnEasing, F2:FnEasing, F3:FnEasing {
let example : HtmlElement = create_element("div").unwrap().dyn_into().unwrap();
example.set_attribute_or_panic("id", name);
example.set_property_or_panic("margin", "10px");
example.set_style_or_panic("margin", "10px");
let container : HtmlElement = get_element_by_id("examples").unwrap().dyn_into().unwrap();
let header : HtmlElement = create_element("center").unwrap().dyn_into().unwrap();
header.set_property_or_panic("background-color", "black");
header.set_property_or_panic("color", "white");
header.set_style_or_panic("background-color", "black");
header.set_style_or_panic("color", "white");
header.set_inner_html(name);
example.append_or_panic(&header);
container.append_or_panic(&example);
@ -305,10 +305,10 @@ macro_rules! example {
pub fn run_example_easing_animator() {
let container : HtmlElement = create_element("div").unwrap().dyn_into().unwrap();
container.set_attribute_or_panic("id", "examples");
container.set_property_or_panic("display", "flex");
container.set_property_or_panic("flex-wrap", "wrap");
container.set_property_or_panic("position", "absolute");
container.set_property_or_panic("top", "0px");
container.set_style_or_panic("display", "flex");
container.set_style_or_panic("flex-wrap", "wrap");
container.set_style_or_panic("position", "absolute");
container.set_style_or_panic("top", "0px");
get_element_by_id("app").unwrap().append_or_panic(&container);
example!(expo);
example!(bounce);

View File

@ -1,9 +1,10 @@
//! This module contains all the submodules of the CSS3D rendering system.
mod html_scene;
mod html_object;
mod html_renderer;
mod css3d_system;
mod css3d_object;
mod css3d_renderer;
pub use html_scene::HtmlScene;
pub use html_object::HtmlObject;
pub use html_renderer::HtmlRenderer;
pub use css3d_object::Css3dObject;
pub use css3d_object::Css3dOrder;
pub use css3d_renderer::Css3dRenderer;
pub use css3d_system::Css3dSystem;

View File

@ -0,0 +1,220 @@
//! This module contains the implementation of Css3dObject, a struct used to represent CSS3D
//! elements.
use crate::prelude::*;
use crate::display::object::DisplayObjectData;
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 crate::system::web::NodeRemover;
use nalgebra::Vector2;
use nalgebra::Vector3;
use web_sys::HtmlElement;
// ==================
// === Css3dOrder ===
// ==================
/// This enumeration is used for moving the object from front to back of the surrounding HtmlElement
/// (usually a WebGL canvas).
#[allow(missing_docs)]
#[derive(Debug,Clone,Copy)]
pub enum Css3dOrder {
Front,
Back
}
impl Default for Css3dOrder {
fn default() -> Self {
Css3dOrder::Front
}
}
// =============================
// === Css3dObjectProperties ===
// =============================
#[derive(Debug)]
struct Css3dObjectProperties {
display_object : DisplayObjectData,
dom : HtmlElement,
dimensions : Vector2<f32>,
css3d_order : Css3dOrder
}
impl Drop for Css3dObjectProperties {
fn drop(&mut self) {
self.display_object.with_logger(|logger| {
self.dom.remove_from_parent_or_warn(logger);
});
self.display_object.unset_parent();
}
}
// =======================
// === Css3dObjectData ===
// =======================
#[derive(Clone,Debug)]
pub(super) struct Css3dObjectData {
properties : Rc<RefCell<Css3dObjectProperties>>
}
impl Css3dObjectData {
fn new
( display_object : DisplayObjectData
, dom : HtmlElement
, dimensions : Vector2<f32>
, css3d_order : Css3dOrder) -> Self {
let properties = Css3dObjectProperties {display_object,dom,dimensions,css3d_order};
let properties = Rc::new(RefCell::new(properties));
Self {properties}
}
fn set_css3d_order(&self, css3d_order: Css3dOrder) {
self.properties.borrow_mut().css3d_order = css3d_order
}
fn css3d_order(&self) -> Css3dOrder {
self.properties.borrow().css3d_order
}
fn position(&self) -> Vector3<f32> {
self.properties.borrow().display_object.position()
}
fn set_dimensions(&self, dimensions:Vector2<f32>) {
let mut properties = self.properties.borrow_mut();
properties.dimensions = dimensions;
properties.display_object.with_logger(|logger| {
properties.dom.set_style_or_warn("width", format!("{}px", dimensions.x), logger);
properties.dom.set_style_or_warn("height", format!("{}px", dimensions.y), logger);
});
}
fn dimensions(&self) -> Vector2<f32> {
self.properties.borrow().dimensions
}
fn dom(&self) -> HtmlElement {
self.properties.borrow().dom.clone()
}
fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
let mut position = self.position();
f(&mut position);
self.properties.borrow().display_object.set_position(position);
}
fn mod_scale<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
self.properties.borrow().display_object.mod_scale(f);
}
}
// ===================
// === Css3dObject ===
// ===================
/// A structure for representing a HtmlElement in the 3d world.
#[derive(Debug,Clone)]
pub struct Css3dObject {
pub(super) data : Css3dObjectData
}
impl Css3dObject {
/// Creates a Css3dObject from element name.
pub(super) fn new<L:Into<Logger>,S:Str>(logger:L, dom_name:S) -> Result<Self> {
let dom = dyn_into(create_element(dom_name.as_ref())?)?;
Ok(Self::from_element(logger,dom))
}
/// Creates a Css3dObject from a web_sys::HtmlElement.
pub(super) fn from_element<L:Into<Logger>>(logger:L, element:HtmlElement) -> Self {
let logger = logger.into();
element.set_style_or_warn("position", "absolute", &logger);
element.set_style_or_warn("width" , "0px" , &logger);
element.set_style_or_warn("height" , "0px" , &logger);
let dom = element;
let display_object = DisplayObjectData::new(logger);
let dimensions = Vector2::new(0.0, 0.0);
let css3d_order = default();
let data = Css3dObjectData::new(
display_object,
dom,
dimensions,
css3d_order
);
Self {data}
}
/// Creates a Css3dObject from a HTML string.
pub(super) fn from_html_string<L:Into<Logger>,T:Str>(logger:L, html_string:T) -> Result<Self> {
let element = create_element("div")?;
element.set_inner_html(html_string.as_ref());
match element.first_element_child() {
Some(element) => {
let element = dyn_into(element)?;
Ok(Self::from_element(logger,element))
},
None => Err(Error::missing("valid HTML")),
}
}
/// Sets Css3dOrder.
pub fn set_css3d_order(&mut self, css3d_order: Css3dOrder) {
self.data.set_css3d_order(css3d_order)
}
/// Gets Css3dOrder.
pub fn css3d_order(&self) -> Css3dOrder {
self.data.css3d_order()
}
/// Sets the underlying HtmlElement dimension.
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
self.data.set_dimensions(dimensions)
}
/// Gets the underlying HtmlElement dimension.
pub fn dimensions(&self) -> Vector2<f32> {
self.data.dimensions()
}
/// Gets Css3dObject's dom.
pub fn dom(&self) -> HtmlElement {
self.data.dom()
}
/// Gets object's position.
pub fn position(&self) -> Vector3<f32> {
self.data.position()
}
/// Modifies the position of the object.
pub fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
self.data.mod_position(f);
}
/// Modifies the scale of the object.
pub fn mod_scale<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
self.data.mod_scale(f);
}
}
impl From<&Css3dObject> for DisplayObjectData {
fn from(t:&Css3dObject) -> Self {
t.data.properties.borrow().display_object.clone_ref()
}
}

View File

@ -0,0 +1,359 @@
//! This module contains the Css3dRenderer, a struct used to render CSS3D elements.
use crate::prelude::*;
use crate::display::object::DisplayObjectData;
use crate::display::camera::Camera2d;
use crate::display::camera::camera2d::Projection;
use crate::system::web::dom::html::{Css3dObject, Css3dSystem};
use crate::system::gpu::data::JsBufferView;
use crate::system::web::Result;
use crate::system::web::create_element;
use crate::system::web::dyn_into;
use crate::system::web::NodeInserter;
use crate::system::web::NodeRemover;
use crate::system::web::StyleSetter;
use crate::system::web::dom::DomContainer;
use crate::system::web::dom::ResizeCallback;
use crate::system::web::get_element_by_id;
use super::css3d_object::Css3dOrder;
use nalgebra::Vector2;
use nalgebra::Matrix4;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use web_sys::HtmlElement;
use js_sys::Object;
// ===================
// === Js Bindings ===
// ===================
mod js {
use super::*;
#[wasm_bindgen(inline_js = "
function arr_to_css_matrix3d(a) {
return 'matrix3d(' + a.join(',') + ')'
}
export function set_object_transform(dom, matrix_array) {
let css = arr_to_css_matrix3d(matrix_array);
dom.style.transform = 'translate(-50%, -50%)' + css;
}
export function setup_perspective(dom, perspective) {
dom.style.perspective = perspective + 'px';
}
export function setup_camera_orthographic(dom, matrix_array) {
dom.style.transform = arr_to_css_matrix3d(matrix_array);
}
export function setup_camera_perspective
(dom, near, matrix_array) {
let translateZ = 'translateZ(' + near + 'px)';
let matrix3d = arr_to_css_matrix3d(matrix_array);
let transform = translateZ + matrix3d;
dom.style.transform = transform;
}
")]
extern "C" {
/// Setup perspective CSS 3D projection on DOM.
#[allow(unsafe_code)]
pub fn setup_perspective(dom: &JsValue, znear: &JsValue);
/// Setup Camera orthographic projection on DOM.
#[allow(unsafe_code)]
pub fn setup_camera_orthographic(dom:&JsValue, matrix_array:&JsValue);
/// Setup Camera perspective projection on DOM.
#[allow(unsafe_code)]
pub fn setup_camera_perspective(dom:&JsValue, near:&JsValue, matrix_array:&JsValue);
/// Sets object's CSS 3D transform.
#[allow(unsafe_code)]
pub fn set_object_transform(dom:&JsValue, matrix_array:&Object);
}
}
#[allow(unsafe_code)]
fn set_object_transform(dom:&JsValue, matrix:&Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::set_object_transform(&dom,&matrix_array);
}
}
#[allow(unsafe_code)]
fn setup_camera_perspective(dom:&JsValue, near:f32, matrix:&Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_perspective(
&dom,
&near.into(),
&matrix_array
)
}
}
#[allow(unsafe_code)]
fn setup_camera_orthographic(dom:&JsValue, matrix:&Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_orthographic(&dom, &matrix_array)
}
}
// =============
// === Utils ===
// =============
/// Inverts Matrix Y coordinates. It's equivalent to scaling by (1.0, -1.0, 1.0).
pub fn invert_y(mut m: Matrix4<f32>) -> Matrix4<f32> {
// Negating the second column to invert Y.
m.row_part_mut(1, 4).iter_mut().for_each(|a| *a = -*a);
m
}
// =========================
// === Css3dRendererData ===
// =========================
#[derive(Debug)]
struct Css3dRendererData {
pub front_dom : HtmlElement,
pub back_dom : HtmlElement,
pub front_dom_view_projection : HtmlElement,
pub back_dom_view_projection : HtmlElement,
logger : Logger
}
impl Css3dRendererData {
pub fn new
( front_dom : HtmlElement
, back_dom : HtmlElement
, front_dom_view_projection : HtmlElement
, back_dom_view_projection : HtmlElement
, logger : Logger) -> Self {
Self {logger,front_dom,back_dom, front_dom_view_projection, back_dom_view_projection }
}
fn set_dimensions(&self, dimensions:Vector2<f32>) {
let width = format!("{}px", dimensions.x);
let height = format!("{}px", dimensions.y);
let doms = vec![&self.front_dom, &self.back_dom, &self.front_dom_view_projection, &self.back_dom_view_projection];
for dom in doms {
dom.set_style_or_warn("width" , &width, &self.logger);
dom.set_style_or_warn("height", &height, &self.logger);
}
}
}
// =====================
// === Css3dRenderer ===
// =====================
/// `Css3dRenderer` is a renderer for `Css3dObject`s. It integrates with other rendering contexts,
/// such as WebGL, by placing two HtmlElements in front and behind of the Canvas element,
/// allowing the move `Css3dObject`s between these two layers, mimicking z-index ordering.
///
/// To make use of its functionalities, the API user can create a `Css3dSystem` by using
/// the `Css3dRenderer::new_system` method which creates and manages instances of
/// `Css3dObject`s.
#[derive(Clone,Debug)]
pub struct Css3dRenderer {
container : DomContainer,
data : Rc<Css3dRendererData>
}
impl Css3dRenderer {
/// Creates a Css3dRenderer inside an element.
pub fn from_element_or_panic(logger:&Logger, element:HtmlElement) -> Self {
let logger = logger.sub("Css3dRenderer");
let container = DomContainer::from_element(element);
let front_dom = create_div();
let back_dom = create_div();
let front_camera_dom = create_div();
let back_camera_dom = create_div();
front_dom.set_style_or_warn("position","absolute",&logger);
front_dom.set_style_or_warn("top","0px",&logger);
front_dom.set_style_or_warn("overflow","hidden",&logger);
front_dom.set_style_or_warn("overflow","hidden",&logger);
front_dom.set_style_or_warn("width","100%",&logger);
front_dom.set_style_or_warn("height","100%",&logger);
front_dom.set_style_or_warn("pointer-events","none",&logger);
back_dom.set_style_or_warn("position","absolute",&logger);
back_dom.set_style_or_warn("top","0px",&logger);
back_dom.set_style_or_warn("overflow","hidden",&logger);
back_dom.set_style_or_warn("overflow","hidden",&logger);
back_dom.set_style_or_warn("width","100%",&logger);
back_dom.set_style_or_warn("height","100%",&logger);
back_dom.set_style_or_warn("pointer-events","none",&logger);
back_dom.set_style_or_warn("z-index","-1",&logger);
front_camera_dom.set_style_or_warn("width", "100%", &logger);
front_camera_dom.set_style_or_warn("height", "100%", &logger);
front_camera_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
back_camera_dom.set_style_or_warn("width", "100%", &logger);
back_camera_dom.set_style_or_warn("height", "100%", &logger);
back_camera_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
container.dom.append_or_warn(&front_dom,&logger);
container.dom.append_or_warn(&back_dom,&logger);
front_dom.append_or_warn(&front_camera_dom, &logger);
back_dom.append_or_warn(&back_camera_dom, &logger);
let data = Css3dRendererData::new(
front_dom,
back_dom,
front_camera_dom,
back_camera_dom,
logger);
let data = Rc::new(data);
Self{container,data}.init()
}
/// Creates a Css3dRenderer.
pub fn new(logger:&Logger, dom_id:&str) -> Result<Self> {
Ok(Self::from_element_or_panic(logger,dyn_into(get_element_by_id(dom_id)?)?))
}
pub(super) fn new_system(&self) -> Css3dSystem {
let css3d_renderer = self.clone();
let logger = self.data.logger.sub("Css3dSystem");
let display_object = DisplayObjectData::new(&logger);
Css3dSystem {display_object,css3d_renderer,logger}
}
fn init(mut self) -> Self {
let dimensions = self.dimensions();
self.set_dimensions(dimensions);
let data = self.data.clone();
self.add_resize_callback(move |dimensions:&Vector2<f32>| {
data.set_dimensions(*dimensions);
});
self
}
/// Creates a new instance of Css3dObject and adds it to parent.
pub(super) fn new_instance<S:Str>
(&self, dom_name:S, parent:DisplayObjectData) -> Result<Css3dObject> {
let front_camera = self.data.front_dom_view_projection.clone();
let back_camera = self.data.back_dom_view_projection.clone();
let logger = self.data.logger.sub("object");
let object = Css3dObject::new(logger,dom_name);
object.as_ref().map(|object| {
parent.add_child(object);
let display_object : DisplayObjectData = object.into();
display_object.set_on_render(enclose!((object,display_object) move || {
let object_dom = object.dom();
let mut transform = display_object.matrix();
transform.iter_mut().for_each(|a| *a = eps(*a));
let camera_node = match object.css3d_order() {
Css3dOrder::Front => &front_camera,
Css3dOrder::Back => &back_camera
};
let parent_node = object.dom().parent_node();
if !camera_node.is_same_node(parent_node.as_ref()) {
display_object.with_logger(|logger| {
object_dom.remove_from_parent_or_warn(logger);
camera_node.append_or_warn(&object_dom,logger);
});
}
set_object_transform(&object_dom, &transform);
}));
}).ok();
object
}
/// Renders `Camera`'s point of view.
pub fn render(&self, camera:&Camera2d) {
let trans_cam = camera.transform().matrix().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 half_dim = self.container.dimensions() / 2.0;
let fovy_slope = camera.half_fovy_slope();
let near = half_dim.y / fovy_slope;
match camera.projection() {
Projection::Perspective{..} => {
js::setup_perspective(&self.data.front_dom, &near.into());
js::setup_perspective(&self.data.back_dom, &near.into());
setup_camera_perspective(&self.data.front_dom_view_projection, near, &trans_cam);
setup_camera_perspective(&self.data.back_dom_view_projection, near, &trans_cam);
},
Projection::Orthographic => {
setup_camera_orthographic(&self.data.front_dom_view_projection, &trans_cam);
setup_camera_orthographic(&self.data.back_dom_view_projection, &trans_cam);
}
}
}
/// Adds a ResizeCallback.
pub fn add_resize_callback<T:ResizeCallback>(&mut self, callback:T) {
self.container.add_resize_callback(callback);
}
/// Sets Css3dRenderer's container dimensions.
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
self.data.set_dimensions(dimensions);
self.container.set_dimensions(dimensions);
}
}
// === Getters ===
impl Css3dRenderer {
/// Gets Css3dRenderer's container.
pub fn container(&self) -> &DomContainer {
&self.container
}
/// Gets Css3dRenderer's DOM.
pub fn dom(&self) -> &HtmlElement {
&self.data.front_dom
}
/// Gets the Css3dRenderer's dimensions.
pub fn dimensions(&self) -> Vector2<f32> {
self.container.dimensions()
}
}
// =============
// === Utils ===
// =============
fn create_div() -> HtmlElement {
let element = create_element("div").expect("Couldn't create element");
dyn_into(element).expect("Couldn't cast to HtmlElement")
}
/// eps is used to round very small values to 0.0 for numerical stability
pub fn eps(value: f32) -> f32 {
if value.abs() < 1e-10 { 0.0 } else { value }
}

View File

@ -0,0 +1,37 @@
use crate::prelude::*;
use super::Css3dObject;
use crate::display::object::DisplayObjectData;
use crate::display::world::World;
use crate::system::web::Result;
use crate::system::web::dom::html::Css3dRenderer;
use logger::Logger;
/// Css3dSystem enables us to create instances of HtmlElement objects in the 3d world.
#[derive(Debug)]
pub struct Css3dSystem {
pub(super) display_object : DisplayObjectData,
pub(super) css3d_renderer : Css3dRenderer,
pub(super) logger : Logger
}
impl Css3dSystem {
/// Creates a new instance of Css3dSystem.
pub fn new(world:&World) -> Self {
let scene = world.scene();
let css3d_renderer = scene.css3d_renderer();
css3d_renderer.new_system()
}
/// Creates a new instance of Css3dObject.
pub fn new_instance<S:Str>(&self, dom_name:S) -> Result<Css3dObject> {
self.css3d_renderer.new_instance(dom_name, self.into())
}
}
impl From<&Css3dSystem> for DisplayObjectData {
fn from(t:&Css3dSystem) -> Self {
t.display_object.clone_ref()
}
}

View File

@ -1,75 +0,0 @@
//! This module contains the implementation of HTMLObject, a struct used to represent CSS3D
//! elements.
use crate::prelude::*;
use crate::display::object::DisplayObjectData;
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, Clone)]
#[shrinkwrap(mutable)]
pub struct HtmlObject {
#[shrinkwrap(main_field)]
/// HTMLObject's hierarchical transforms.
pub display_object : DisplayObjectData,
/// The DOM to be rendered with CSS3D.
pub dom : HtmlElement,
dimensions : Vector2<f32>,
}
impl HtmlObject {
/// Creates a HTMLObject from element name.
pub fn new<L:Into<Logger>>(logger:L, dom_name:&str) -> Result<Self> {
let dom = dyn_into(create_element(dom_name)?)?;
Ok(Self::from_element(logger,dom))
}
/// Creates a HTMLObject from a web_sys::HtmlElement.
pub fn from_element<L:Into<Logger>>(logger:L, element:HtmlElement) -> Self {
element.set_property_or_panic("position", "absolute");
element.set_property_or_panic("width" , "0px");
element.set_property_or_panic("height" , "0px");
let dom = element;
let display_object = DisplayObjectData::new(logger.into());
let dimensions = Vector2::new(0.0, 0.0);
Self {display_object,dom,dimensions}
}
/// Creates a HTMLObject from a HTML string.
pub fn from_html_string<L:Into<Logger>, T:AsRef<str>>(logger:L, html_string:T) -> Result<Self> {
let element = create_element("div")?;
element.set_inner_html(html_string.as_ref());
match element.first_element_child() {
Some(element) => Ok(Self::from_element(logger,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.dom.set_property_or_panic("width", format!("{}px", width));
self.dom.set_property_or_panic("height", format!("{}px", height));
}
/// Gets the underlying HtmlElement dimension.
pub fn dimensions(&self) -> &Vector2<f32> {
&self.dimensions
}
}

View File

@ -1,246 +0,0 @@
//! This module contains the HTMLRenderer, a struct used to render CSS3D elements.
use crate::prelude::*;
use crate::display::camera::Camera2d;
use crate::display::camera::camera2d::Projection;
use crate::system::web::dom::html::HtmlScene;
use crate::system::gpu::data::JsBufferView;
use crate::system::web::Result;
use crate::system::web::create_element;
use crate::system::web::dyn_into;
use crate::system::web::NodeInserter;
use crate::system::web::StyleSetter;
use crate::system::web::dom::DomContainer;
use crate::system::web::dom::ResizeCallback;
use js_sys::Object;
use nalgebra::Vector2;
use nalgebra::Matrix4;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use web_sys::HtmlElement;
// ===================
// === Js Bindings ===
// ===================
mod js {
use super::*;
#[wasm_bindgen(module = "/src/system/web/dom/html/snippets.js")]
extern "C" {
#[allow(unsafe_code)]
pub fn set_object_transform(dom:&JsValue, matrix_array:&Object);
#[allow(unsafe_code)]
pub fn setup_perspective(dom: &JsValue, znear: &JsValue);
#[allow(unsafe_code)]
pub fn setup_camera_orthographic(dom:&JsValue, matrix_array:&JsValue);
#[allow(unsafe_code)]
pub fn setup_camera_perspective
( dom : &JsValue
, near : &JsValue
, matrix_array : &JsValue
);
}
}
/// eps is used to round very small values to 0.0 for numerical stability
pub fn eps(value: f32) -> f32 {
if value.abs() < 1e-10 { 0.0 } else { value }
}
/// Inverts Matrix Y coordinates.
/// It's equivalent to scaling by (1.0, -1.0, 1.0).
pub fn invert_y(mut m: Matrix4<f32>) -> Matrix4<f32> {
// Negating the second column to invert Y.
m.row_part_mut(1, 4).iter_mut().for_each(|a| *a = -*a);
m
}
#[allow(unsafe_code)]
fn set_object_transform(dom: &JsValue, matrix: &Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::set_object_transform(&dom, &matrix_array);
}
}
#[allow(unsafe_code)]
fn setup_camera_perspective
(dom:&JsValue, near:f32, matrix:&Matrix4<f32>) { // Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_perspective(
&dom,
&near.into(),
&matrix_array
)
}
}
#[allow(unsafe_code)]
fn setup_camera_orthographic(dom:&JsValue, matrix:&Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_orthographic(&dom, &matrix_array)
}
}
// ========================
// === HTMLRendererData ===
// ========================
#[derive(Debug)]
pub struct HTMLRendererData {
pub dom : HtmlElement,
pub camera : HtmlElement
}
impl HTMLRendererData {
pub fn new(dom:HtmlElement, camera:HtmlElement) -> Self {
Self {dom,camera}
}
pub fn set_dimensions(&self, dimensions : Vector2<f32>) {
let width = format!("{}px", dimensions.x);
let height = format!("{}px", dimensions.y);
self.dom.set_property_or_panic("width", &width);
self.dom.set_property_or_panic("height", &height);
self.camera.set_property_or_panic("width" , &width);
self.camera.set_property_or_panic("height", &height);
}
}
// ====================
// === HTMLRenderer ===
// ====================
/// A renderer for `HTMLObject`s.
#[derive(Debug)]
pub struct HtmlRenderer {
container : DomContainer,
data : Rc<HTMLRendererData>
}
impl HtmlRenderer {
/// Creates a HTMLRenderer.
pub fn new(dom_id: &str) -> Result<Self> {
let container = DomContainer::from_id(dom_id)?;
let dom: HtmlElement = dyn_into(create_element("div")?)?;
let camera : HtmlElement = dyn_into(create_element("div")?)?;
dom.set_property_or_panic("position", "absolute");
dom.set_property_or_panic("top" , "0px");
dom.set_property_or_panic("overflow", "hidden");
dom.set_property_or_panic("overflow", "hidden");
dom.set_property_or_panic("width" , "100%");
dom.set_property_or_panic("height" , "100%");
camera.set_property_or_panic("width" , "100%");
camera.set_property_or_panic("height" , "100%");
camera.set_property_or_panic("transform-style", "preserve-3d");
container.dom.append_or_panic(&dom);
dom.append_or_panic(&camera);
let data = Rc::new(HTMLRendererData::new(dom,camera));
let mut htmlrenderer = Self {container,data};
htmlrenderer.init_listeners();
Ok(htmlrenderer)
}
fn init_listeners(&mut self) {
let dimensions = self.dimensions();
let data = self.data.clone();
self.add_resize_callback(move |dimensions:&Vector2<f32>| {
data.set_dimensions(*dimensions);
});
self.set_dimensions(dimensions);
}
/// Renders the `Scene` from `Camera`'s point of view.
pub fn render(&self, camera: &mut Camera2d, scene: &HtmlScene) {
camera.update();
let trans_cam = camera.transform().matrix().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 half_dim = self.container.dimensions() / 2.0;
let fovy_slope = camera.half_fovy_slope();
let near = half_dim.y / fovy_slope;
match camera.projection() {
Projection::Perspective{..} => {
js::setup_perspective(&self.data.dom, &near.into());
setup_camera_perspective(
&self.data.camera,
near,
&trans_cam
);
},
Projection::Orthographic => {
setup_camera_orthographic(&self.data.camera, &trans_cam);
}
}
let scene : &HtmlScene = &scene;
for object in &mut scene.into_iter() {
object.update();
let mut transform = object.matrix();
transform.iter_mut().for_each(|a| *a = eps(*a));
let parent_node = object.dom.parent_node();
if !self.data.camera.is_same_node(parent_node.as_ref()) {
self.data.camera.append_or_panic(&object.dom);
}
set_object_transform(&object.dom, &transform);
}
}
/// Adds a ResizeCallback.
pub fn add_resize_callback<T:ResizeCallback>(&mut self, callback : T) {
self.container.add_resize_callback(callback);
}
/// Sets HTMLRenderer's container dimensions.
pub fn set_dimensions(&mut self, dimensions : Vector2<f32>) {
self.container.set_dimensions(dimensions);
self.data.set_dimensions(dimensions);
}
}
// === Getters ===
impl HtmlRenderer {
/// Gets HTMLRenderer's container.
pub fn container(&self) -> &DomContainer {
&self.container
}
/// Gets HTMLRenderer's DOM.
pub fn dom(&self) -> &HtmlElement {
&self.data.dom
}
/// Gets the Scene Renderer's dimensions.
pub fn dimensions(&self) -> Vector2<f32> {
self.container.dimensions()
}
}

View File

@ -1,77 +0,0 @@
//! This module contains `HTMLScene`, a struct to hold `HTMLObject`s.
use crate::prelude::*;
use super::HtmlObject;
use crate::display::object::DisplayObjectData;
use data::opt_vec::*;
// =================
// === HtmlScene ===
// =================
/// A collection for holding 3D `Object`s.
#[derive(Debug)]
pub struct HtmlScene {
display_object : DisplayObjectData,
objects : OptVec<HtmlObject>
}
impl HtmlScene {
/// Searches for a HtmlElement identified by id and appends to it.
pub fn new<L:Into<Logger>>(logger:L) -> Self {
let display_object = DisplayObjectData::new(logger.into());
let objects = default();
Self{display_object,objects}
}
/// Moves a HTMLObject to the Scene and returns an index to it.
pub fn add_child(&mut self, object: HtmlObject) -> Ix {
self.display_object.add_child(&object.display_object);
self.objects.insert(object)
}
/// Removes and retrieves a HTMLObject based on the index provided by
pub fn remove_child(&mut self, index: Ix) -> Option<HtmlObject> {
let object = self.objects.remove(index);
if let Some(object) = &object {
self.display_object.remove_child(&object.display_object);
}
object
}
/// Returns the number of `Object`s in the Scene,
/// also referred to as its 'length'.
pub fn len(&self) -> usize {
self.objects.len()
}
/// Returns true if the Scene contains no `Object`s.
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
/// Gets mutable iterator.
pub fn iter_mut(&mut self) -> IterMut<'_, HtmlObject> { self.objects.iter_mut() }
/// Gets iterator.
pub fn iter(&self) -> Iter<'_, HtmlObject> { self.objects.iter() }
}
impl<'a> IntoIterator for &'a HtmlScene {
type Item = &'a HtmlObject;
type IntoIter = Iter<'a, HtmlObject>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut HtmlScene {
type Item = &'a mut HtmlObject;
type IntoIter = IterMut<'a, HtmlObject>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}

View File

@ -1,28 +0,0 @@
function arr_to_css_matrix3d(a) {
return "matrix3d(" + a.join(',') + ")"
}
// Sets object's CSS 3D transform.
export function set_object_transform(dom, matrix_array) {
let css = arr_to_css_matrix3d(matrix_array);
dom.style.transform = "translate(-50%, -50%)" + css;
}
// Setup perspective CSS 3D projection on DOM.
export function setup_perspective(dom, perspective) {
dom.style.perspective = perspective + "px";
}
// Setup Camera orthographic projection on DOM.
export function setup_camera_orthographic(dom, matrix_array) {
dom.style.transform = arr_to_css_matrix3d(matrix_array);
}
// Setup Camera perspective projection on DOM.
export function setup_camera_perspective
(dom, near, matrix_array) {
let translateZ = "translateZ(" + near + "px)";
let matrix3d = arr_to_css_matrix3d(matrix_array);
let transform = translateZ + matrix3d;
dom.style.transform = transform;
}

View File

@ -0,0 +1,171 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
use web_test::web_configure;
web_configure!(run_in_browser);
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen(module = "/tests/bench_test.js")]
extern "C" {
fn set_gradient_bg(
dom : &JsValue,
red : &JsValue,
green : &JsValue,
blue : &JsValue);
}
#[cfg(test)]
mod tests {
use basegl::system::web::dom::html::Css3dObject;
use basegl::system::web::dom::html::Css3dSystem;
use basegl::system::web::dom::html::Css3dRenderer;
use basegl::system::web::StyleSetter;
use basegl::system::web::get_performance;
use web_test::*;
use web_sys::Performance;
use nalgebra::Vector3;
use logger::Logger;
use basegl::system::web;
use basegl::display::world::{WorldData, World};
use basegl::display::navigation::navigator::Navigator;
use basegl::display::object::DisplayObjectOps;
use basegl::display::object::DisplayObject;
use nalgebra::Vector2;
use basegl::system::web::AttributeSetter;
use basegl::system::web::NodeInserter;
use basegl::system::web::dyn_into;
use basegl::system::web::get_element_by_id;
use basegl::system::web::create_element;
use web_sys::HtmlElement;
#[web_test(no_container)]
fn invalid_container() {
let logger = Logger::new("invalid_container");
let renderer = Css3dRenderer::new(&logger, "nonexistent_id");
assert!(renderer.is_err(),"Tried to attach to a non-existent HtmlElement and succeeded.");
}
fn initialize_system(name:&str,color:&str) -> (World,Css3dSystem) {
web::set_stdout();
let canvas_name = format!("canvas_{}",name);
let container = dyn_into::<_,HtmlElement>(get_element_by_id(name).unwrap()).unwrap();
let canvas = dyn_into::<_,HtmlElement>(create_element("canvas").unwrap()).unwrap();
canvas.set_attribute_or_panic("id", &canvas_name);
canvas.set_style_or_panic("width", "100%");
canvas.set_style_or_panic("height", "100%");
container.append_or_panic(&canvas);
let world = WorldData::new(&canvas_name);
let css3d_system = Css3dSystem::new(&world);
container.set_style_or_panic("background-color", color);
world.add_child(&css3d_system);
(world,css3d_system)
}
fn create_scene(system:&Css3dSystem) -> Vec<Css3dObject> {
let mut objects = Vec::new();
// Iterate over 3 axes.
for axis in vec![(1, 0, 0), (0, 1, 0), (0, 0, 1)] {
// Creates 10 HTMLObjects per axis.
for i in 0 .. 10 {
let mut object = system.new_instance("div").unwrap();
object.set_dimensions(Vector2::new(10.0, 10.0));
// Using axis for masking.
// For instance, the axis (0, 1, 0) creates:
// (x, y, z) = (0, 0, 0) .. (0, 9, 0)
let x = (i * axis.0) as f32;
let y = (i * axis.1) as f32;
let z = (i * axis.2) as f32;
let factor = 120.0 / 9.0;
let position = Vector3::new(x * factor + 160.0, y * factor + 120.0, z * factor);
object.mod_position(|t| *t = position);
// Creates a gradient color based on the axis.
let r = (x * 25.5) as u8;
let g = (y * 25.5) as u8;
let b = (z * 25.5) as u8;
let color = format!("rgba({}, {}, {}, {})", r, g, b, 1.0);
object.dom().set_style_or_panic("background-color", color);
objects.push(object);
}
}
objects
}
#[web_test]
fn rhs_coordinates() {
let (world,css3d_system) = initialize_system("rhs_coordinates", "black");
let scene = world.scene();
let camera = scene.camera();
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
let scene = create_scene(&css3d_system);
world.display_object().update();
world.on_frame(move |_| {
let _keep_alive = &scene;
let _keep_alive = &css3d_system;
let _keep_alive = &navigator;
}).forget();
std::mem::forget(world);
}
fn make_sphere(mut scene : &mut Vec<Css3dObject>, performance : &Performance) {
use super::set_gradient_bg;
let t = (performance.now() / 1000.0) as f32;
let length = scene.len() as f32;
for (i, object) in (&mut scene).into_iter().enumerate() {
let i = i as f32;
let d = (i / length - 0.5) * 2.0;
let mut y = d;
let r = (1.0 - y * y).sqrt();
let mut x = (y * 100.0 + t).cos() * r;
let mut z = (y * 100.0 + t).sin() * r;
x += (y * 1.25 + t * 2.50).cos() * 0.5;
y += (z * 1.25 + t * 2.00).cos() * 0.5;
z += (x * 1.25 + t * 3.25).cos() * 0.5;
let x = x * 10.0;
let y = y * 10.0;
let z = z * 10.0;
object.mod_position(|t| *t = Vector3::new(x, y, z));
let faster_t = t * 100.0;
let r = (i + 0.0 + faster_t) as u8 % 255;
let g = (i + 85.0 + faster_t) as u8 % 255;
let b = (i + 170.0 + faster_t) as u8 % 255;
set_gradient_bg(&object.dom(), &r.into(), &g.into(), &b.into());
}
}
#[web_bench]
fn object_x400_update(b: &mut Bencher) {
let (world,css3d_system) = initialize_system("object_x400_update", "black");
let scene = world.scene();
let camera = scene.camera();
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
let mut objects = Vec::new();
for _ in 0..400 {
let mut object = css3d_system.new_instance("div").expect("Failed to create object");
object.set_dimensions(Vector2::new(1.0, 1.0));
object.mod_scale(|t| *t = Vector3::new(0.5, 0.5, 0.5));
objects.push(object);
}
let performance = get_performance().expect("Couldn't get performance obj");
b.iter(move || {
let _keep_alive = &navigator;
let _keep_alive = &css3d_system;
make_sphere(&mut objects, &performance);
world.display_object().set_scale(Vector3::new(5.0, 5.0, 5.0));
world.display_object().set_position(Vector3::new(160.0, 120.0, 0.0));
world.display_object().update();
})
}
}

View File

@ -1,175 +0,0 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
use web_test::web_configure;
web_configure!(run_in_browser);
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen(module = "/tests/bench_test.js")]
extern "C" {
fn set_gradient_bg(
dom : &JsValue,
red : &JsValue,
green : &JsValue,
blue : &JsValue);
}
#[cfg(test)]
mod tests {
use basegl::display::camera::Camera2d;
use basegl::system::web::dom::html::HtmlScene;
use basegl::system::web::dom::html::HtmlObject;
use basegl::system::web::dom::html::HtmlRenderer;
use basegl::system::web::StyleSetter;
use basegl::system::web::get_performance;
use web_test::*;
use web_sys::Performance;
use nalgebra::Vector3;
use logger::Logger;
#[web_test(no_container)]
fn invalid_container() {
let renderer = HtmlRenderer::new("nonexistent_id");
assert!(renderer.is_err(), "nonexistent_id should not exist");
}
fn create_scene(logger:&Logger, renderer:&HtmlRenderer) -> HtmlScene {
let mut scene: HtmlScene = HtmlScene::new(logger);
assert_eq!(scene.len(), 0);
renderer.container().dom.set_property_or_panic("background-color", "black");
// Iterate over 3 axes.
for axis in vec![(1, 0, 0), (0, 1, 0), (0, 0, 1)] {
// Creates 10 HTMLObjects per axis.
for i in 0 .. 10 {
let mut object = HtmlObject::new(logger, "div").unwrap();
object.set_dimensions(10.0, 10.0);
// Using axis for masking.
// For instance, the axis (0, 1, 0) creates:
// (x, y, z) = (0, 0, 0) .. (0, 9, 0)
let x = (i * axis.0) as f32;
let y = (i * axis.1) as f32;
let z = (i * axis.2) as f32;
let factor = 120.0 / 9.0;
let position = Vector3::new(x * factor + 160.0, y * factor + 120.0, z * factor);
object.set_position(position);
// Creates a gradient color based on the axis.
let r = (x * 25.5) as u8;
let g = (y * 25.5) as u8;
let b = (z * 25.5) as u8;
let color = format!("rgba({}, {}, {}, {})", r, g, b, 1.0);
object.dom.set_property_or_panic("background-color", color);
scene.add_child(object);
}
}
assert_eq!(scene.len(), 30, "We should have 30 HTMLObjects");
scene
}
#[web_test]
fn rhs_coordinates() {
let logger = Logger::new("rhs_coordinates");
let renderer = HtmlRenderer::new("rhs_coordinates")
.expect("Renderer couldn't be created");
let scene = create_scene(&logger, &renderer);
let view_dim = renderer.dimensions();
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
renderer.render(&mut camera, &scene);
}
#[web_bench]
fn camera_movement(b: &mut Bencher) {
let logger = Logger::new("camera_movement");
let renderer = HtmlRenderer::new("camera_movement")
.expect("Renderer couldn't be created");
let scene = create_scene(&logger, &renderer);
let view_dim = renderer.dimensions();
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
let performance = get_performance()
.expect("Couldn't get performance obj");
b.iter(move || {
let t = (performance.now() / 1000.0) as f32;
// We move the Camera 29 units away from the center.
camera.set_position(Vector3::new(t.sin() * 50.0, t.cos() * 50.0, 200.0));
renderer.render(&mut camera, &scene);
})
}
fn make_sphere(mut scene : &mut HtmlScene, performance : &Performance) {
use super::set_gradient_bg;
let t = (performance.now() / 1000.0) as f32;
let length = scene.len() as f32;
let mut scene : &mut HtmlScene = &mut scene;
for (i, object) in (&mut scene).into_iter().enumerate() {
let i = i as f32;
let d = (i / length - 0.5) * 2.0;
let mut y = d;
let r = (1.0 - y * y).sqrt();
let mut x = (y * 100.0 + t).cos() * r;
let mut z = (y * 100.0 + t).sin() * r;
x += (y * 1.25 + t * 2.50).cos() * 0.5;
y += (z * 1.25 + t * 2.00).cos() * 0.5;
z += (x * 1.25 + t * 3.25).cos() * 0.5;
let x = x * 5.0 + 160.0;
let y = y * 5.0 + 120.0;
let z = z * 5.0;
object.set_position(Vector3::new(x, y, z));
let faster_t = t * 100.0;
let r = (i + 0.0 + faster_t) as u8 % 255;
let g = (i + 85.0 + faster_t) as u8 % 255;
let b = (i + 170.0 + faster_t) as u8 % 255;
set_gradient_bg(&object.dom, &r.into(), &g.into(), &b.into());
}
}
#[web_bench]
fn object_x400_update(b: &mut Bencher) {
let logger = Logger::new("object_x400_update");
let renderer = HtmlRenderer::new("object_x400_update")
.expect("Renderer couldn't be created");
let mut scene = HtmlScene::new(&logger);
renderer.container().dom.set_property_or_panic("background-color", "black");
for _ in 0..400 {
let mut object = HtmlObject::new(&logger, "div")
.expect("Failed to create object");
object.set_dimensions(1.0, 1.0);
object.set_scale(Vector3::new(0.5, 0.5, 0.5));
scene.add_child(object);
}
let view_dim = renderer.dimensions();
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
let performance = get_performance()
.expect("Couldn't get performance obj");
// We move the Camera 29 units away from the center.
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
b.iter(move || {
make_sphere(&mut scene, &performance);
renderer.render(&mut camera, &scene);
})
}
}

View File

@ -13,37 +13,44 @@ mod tests {
use basegl::animation::physics::inertia::PhysicsSimulator;
use basegl::animation::physics::inertia::PhysicsProperties;
use basegl::animation::animator::fixed_step::FixedStepAnimator;
use basegl::system::web::dom::html::HtmlRenderer;
use basegl::system::web::dom::html::HtmlObject;
use basegl::system::web::dom::html::HtmlScene;
use basegl::display::camera::Camera2d;
use basegl::system::web::dom::html::Css3dSystem;
use web_test::*;
use nalgebra::{zero, Vector3};
use js_sys::Math::random;
use logger::Logger;
use basegl::display::world::WorldData;
use basegl::display::object::DisplayObjectOps;
use basegl::system::web::dyn_into;
use basegl::system::web::get_element_by_id;
use basegl::system::web::create_element;
use web_sys::HtmlElement;
use nalgebra::Vector2;
use basegl::system::web::NodeInserter;
use basegl::system::web::AttributeSetter;
use basegl::system::web::set_stdout;
use basegl::display::object::DisplayObject;
#[web_bench]
fn simulator(b : &mut Bencher) {
let renderer = HtmlRenderer::new("simulator").expect("Renderer couldn't be created");
renderer.container().dom.set_property_or_panic("background-color", "black");
#[web_test]
fn simulator() {
set_stdout();
let name = "simulator";
let canvas_name = format!("canvas_{}",name);
let container = dyn_into::<_,HtmlElement>(get_element_by_id(name).unwrap()).unwrap();
let canvas = create_element("canvas").unwrap();
canvas.set_attribute_or_panic("id", &canvas_name);
container.append_or_panic(&canvas);
let world = WorldData::new(&canvas_name);
let css3d_system = Css3dSystem::new(&world);
world.add_child(&css3d_system);
let logger = Logger::new("simulator");
let mut scene = HtmlScene::new(&logger);
container.set_style_or_panic("background-color", "black");
let mut target = HtmlObject::new(&logger, "div").unwrap();
target.set_dimensions(10.0, 10.0);
target.dom.set_property_or_panic("background-color", "green");
scene.add_child(target.clone());
let mut target = css3d_system.new_instance("div").unwrap();
target.set_dimensions(Vector2::new(10.0, 10.0));
target.dom().set_style_or_panic("background-color", "green");
let mut object = HtmlObject::new(&logger, "div").unwrap();
object.set_dimensions(10.0, 10.0);
object.dom.set_property_or_panic("background-color", "red");
scene.add_child(object.clone());
let view_dim = renderer.dimensions();
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
let mut object = css3d_system.new_instance("div").unwrap();
object.set_dimensions(Vector2::new(10.0, 10.0));
object.dom().set_style_or_panic("background-color", "red");
let mass = 2.0;
let position = object.position();
@ -58,25 +65,25 @@ mod tests {
steps_per_second,
properties.clone(),
move |position| {
object.set_position(position);
object.mod_position(|t| *t = position);
world.display_object().update();
}
);
// Updates spring's fixed point every two seconds.
let every = 2.0;
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
let every = 2.0;
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
let _keep_alive = &simulator;
let _keep_alive = &css3d_system;
let x = 320.0 * random() as f32;
let y = 240.0 * random() as f32;
let z = 0.0;
let position = Vector3::new(x, y, z);
properties.mod_spring(|spring| spring.fixed_point = position);
target.set_position(position);
target.mod_position(|t| *t = position);
});
b.iter(move || {
let _keep_alive = &simulator;
let _keep_alive = &animator;
renderer.render(&mut camera, &scene);
});
std::mem::forget(animator);
}
}

View File

@ -11,6 +11,7 @@ default = []
[dependencies]
wasm-bindgen = { version = "^0.2", features = ["nightly"] }
enso-prelude = { version = "0.1.0" , path = "../prelude" }
[dependencies.web-sys]
version = "0.3.4"

View File

@ -6,6 +6,7 @@
use std::fmt::Debug;
use wasm_bindgen::JsValue;
use enso_prelude::*;
#[cfg(target_arch = "wasm32")]
use web_sys::console;
@ -26,7 +27,7 @@ impl LogMsg for &str {
}
}
impl<F: Fn() -> S, S: AsRef<str>> LogMsg for F {
impl<F: Fn() -> S, S:Str> LogMsg for F {
fn with_log_msg<G: FnOnce(&str) -> T, T>(&self, f:G) -> T {
f(self().as_ref())
}
@ -44,12 +45,12 @@ pub struct Logger {
#[allow(dead_code)]
impl Logger {
pub fn new<T: AsRef<str>>(path:T) -> Self {
pub fn new<T:Str>(path:T) -> Self {
let path = path.as_ref().to_string();
Self {path}
}
pub fn sub<T: AsRef<str>>(&self, path: T) -> Self {
pub fn sub<T:Str>(&self, path: T) -> Self {
Self::new(format!("{}.{}", self.path, path.as_ref()))
}

View File

@ -12,6 +12,7 @@ default = ["console_error_panic_hook"]
[dependencies]
data = { version = "0.1.0" , path = "../../data" }
enso-prelude = { version = "0.1.0" , path = "../../prelude" }
logger = { version = "0.1.0" , path = "../../logger" }
js-sys = { version = "0.3.28" }
wasm-bindgen = { version = "^0.2" , features = ["nightly"] }
failure = { version = "0.1.5" }

View File

@ -141,8 +141,8 @@ impl DomContainer {
/// Sets the Scene DOM's dimensions.
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
self.dom.set_property_or_panic("width" , format!("{}px", dimensions.x));
self.dom.set_property_or_panic("height", format!("{}px", dimensions.y));
self.dom.set_style_or_panic("width" , format!("{}px", dimensions.x));
self.dom.set_style_or_panic("height", format!("{}px", dimensions.y));
self.data.set_dimensions(dimensions);
}

View File

@ -19,6 +19,7 @@ use web_sys::MouseEvent;
use web_sys::EventTarget;
use js_sys::Function;
use std::fmt::Debug;
use logger::Logger;
pub use web_sys::console;
use wasm_bindgen::prelude::*;
@ -162,15 +163,11 @@ pub fn get_performance() -> Result<Performance> {
/// Trait used to set HtmlElement attributes.
pub trait AttributeSetter {
fn set_attribute_or_panic<T, U>(&self, name:T, value:U)
where T : AsRef<str>,
U : AsRef<str>;
fn set_attribute_or_panic<T:Str,U:Str>(&self, name:T, value:U);
}
impl AttributeSetter for web_sys::HtmlElement {
fn set_attribute_or_panic<T,U>(&self, name:T, value:U)
where T : AsRef<str>,
U : AsRef<str> {
impl AttributeSetter for web_sys::Element {
fn set_attribute_or_panic<T:Str,U:Str>(&self, name:T, value:U) {
let name = name.as_ref();
let value = value.as_ref();
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
@ -181,28 +178,43 @@ impl AttributeSetter for web_sys::HtmlElement {
/// Trait used to set css styles.
pub trait StyleSetter {
fn set_property_or_panic<T,U>(&self, name:T, value:U)
where T : AsRef<str>,
U : AsRef<str>;
fn set_style_or_panic<T:Str,U:Str>(&self, name:T, value:U);
fn set_style_or_warn<T:Str,U:Str>(&self, name:T, value:U, logger:&Logger);
}
impl StyleSetter for web_sys::HtmlElement {
fn set_property_or_panic<T,U>(&self, name:T, value:U)
where T : AsRef<str>,
U : AsRef<str> {
let name = name.as_ref();
let value = value.as_ref();
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
fn set_style_or_panic<T:Str,U:Str>(&self, name:T, value:U) {
let name = name.as_ref();
let value = value.as_ref();
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
let panic_msg = |_| panic!("Failed to set style {}",values);
self.style().set_property(name, value).unwrap_or_else(panic_msg);
}
fn set_style_or_warn<T:Str,U:Str>(&self, name:T, value:U, logger:&Logger) {
let name = name.as_ref();
let value = value.as_ref();
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
let warn_msg : &str = &format!("Failed to set style {}",values);
if self.style().set_property(name, value).is_err() {
logger.warning(warn_msg);
}
}
}
/// Trait used to insert `Node`s.
pub trait NodeInserter {
fn append_or_panic (&self, node:&Node);
fn append_or_panic(&self, node:&Node);
fn append_or_warn(&self, node:&Node, logger:&Logger);
fn prepend_or_panic(&self, node:&Node);
fn prepend_or_warn(&self, node:&Node, logger:&Logger);
fn insert_before_or_panic(&self,node:&Node,reference_node:&Node);
fn insert_before_or_warn(&self,node:&Node,reference_node:&Node, logger:&Logger);
}
impl NodeInserter for Node {
@ -212,34 +224,80 @@ impl NodeInserter for Node {
self.append_child(node).unwrap_or_else(panic_msg);
}
fn prepend_or_panic(&self, node : &Node) {
let panic_msg = |_|
panic!("Failed to prepend child \"{:?}\" to \"{:?}\"",node,self);
fn append_or_warn(&self, node:&Node, logger:&Logger) {
let warn_msg : &str = &format!("Failed to append child {:?} to {:?}",node,self);
if self.append_child(node).is_err() {
logger.warning(warn_msg)
};
}
fn prepend_or_panic(&self, node:&Node) {
let panic_msg = |_| panic!("Failed to prepend child \"{:?}\" to \"{:?}\"",node,self);
let first_c = self.first_child();
self.insert_before(node, first_c.as_ref()).unwrap_or_else(panic_msg);
}
fn prepend_or_warn(&self, node:&Node, logger:&Logger) {
let warn_msg : &str = &format!("Failed to prepend child \"{:?}\" to \"{:?}\"",node,self);
let first_c = self.first_child();
if self.insert_before(node, first_c.as_ref()).is_err() {
logger.warning(warn_msg)
}
}
fn insert_before_or_panic(&self, node:&Node, ref_node:&Node) {
let panic_msg = |_|
panic!("Failed to insert {:?} before {:?} in {:?}",
node,
ref_node,
self);
let panic_msg = |_| panic!("Failed to insert {:?} before {:?} in {:?}",node,ref_node,self);
self.insert_before(node, Some(ref_node)).unwrap_or_else(panic_msg);
}
fn insert_before_or_warn(&self, node:&Node, ref_node:&Node, logger:&Logger) {
let warn_msg : &str =
&format!("Failed to insert {:?} before {:?} in {:?}",node,ref_node,self);
if self.insert_before(node, Some(ref_node)).is_err() {
logger.warning(warn_msg)
}
}
}
/// Trait used to remove `Node`s.
pub trait NodeRemover {
fn remove_from_parent_or_panic(&self);
fn remove_from_parent_or_warn(&self, logger:&Logger);
fn remove_child_or_panic(&self, node:&Node);
fn remove_child_or_warn(&self, node:&Node, logger:&Logger);
}
impl NodeRemover for Node {
fn remove_from_parent_or_panic(&self) {
if let Some(parent) = self.parent_node() {
let panic_msg = |_| panic!("Failed to remove {:?} from parent", self);
parent.remove_child(self).unwrap_or_else(panic_msg);
}
}
fn remove_from_parent_or_warn(&self, logger:&Logger) {
if let Some(parent) = self.parent_node() {
let warn_msg : &str = &format!("Failed to remove {:?} from parent", self);
if parent.remove_child(self).is_err() {
logger.warning(warn_msg)
}
}
}
fn remove_child_or_panic(&self, node:&Node) {
let panic_msg = |_|
panic!("Failed to remove child {:?} from {:?}",node,self);
let panic_msg = |_| panic!("Failed to remove child {:?} from {:?}",node,self);
self.remove_child(node).unwrap_or_else(panic_msg);
}
fn remove_child_or_warn(&self, node:&Node, logger:&Logger) {
let warn_msg : &str = &format!("Failed to remove child {:?} from {:?}",node,self);
if self.remove_child(node).is_err() {
logger.warning(warn_msg)
}
}
}
#[wasm_bindgen(inline_js = "export function request_animation_frame2(f) { requestAnimationFrame(f) }")]

View File

@ -30,12 +30,12 @@ impl BenchContainer {
let div = create_element("div").expect("div");
let div : HtmlElement = dyn_into(div).expect("HtmlElement");
div.set_property_or_panic("margin" , "0px 2px");
div.set_property_or_panic("height" , "24px");
div.set_property_or_panic("bottom-border" , "1px solid black");
div.set_property_or_panic("display" , "flex");
div.set_property_or_panic("justify-content", "space-between");
div.set_property_or_panic("align-items" , "center");
div.set_style_or_panic("margin" , "0px 2px");
div.set_style_or_panic("height" , "24px");
div.set_style_or_panic("bottom-border" , "1px solid black");
div.set_style_or_panic("display" , "flex");
div.set_style_or_panic("justify-content", "space-between");
div.set_style_or_panic("align-items" , "center");
div.set_inner_html("<div>00.00ms</div>\
<div>0 iterations</div>\
@ -53,7 +53,7 @@ impl BenchContainer {
let header_height = 17.0;
let height = format!("{}px", height + header_height + 25.0);
container.div.set_property_or_panic("height", height);
container.div.set_style_or_panic("height", height);
container.div.insert_before_or_panic(&div, &container.container);
let measurement = div;

View File

@ -29,25 +29,25 @@ impl Container {
let header = create_element("center").expect("div");
let header = dyn_into::<_, HtmlElement>(header).expect("HtmlElement");
div.set_property_or_panic("width" , &width);
div.set_property_or_panic("height" , format!("{}px", height + 17.0));
div.set_property_or_panic("border" , "1px solid black");
div.set_property_or_panic("position", "relative");
div.set_property_or_panic("margin" , "10px");
div.set_style_or_panic("width" , &width);
div.set_style_or_panic("height" , format!("{}px", height + 17.0));
div.set_style_or_panic("border" , "1px solid black");
div.set_style_or_panic("position", "relative");
div.set_style_or_panic("margin" , "10px");
header.set_inner_html(name);
header.set_property_or_panic("width" , &width);
header.set_property_or_panic("height", format!("{}px", 16.0));
header.set_property_or_panic("border-bottom", "1px solid black");
header.set_property_or_panic("position", "relative");
header.set_style_or_panic("width" , &width);
header.set_style_or_panic("height", format!("{}px", 16.0));
header.set_style_or_panic("border-bottom", "1px solid black");
header.set_style_or_panic("position", "relative");
div.append_or_panic(&header);
let container = create_element("div").expect("div");
let container : HtmlElement = dyn_into(container).expect("HtmlElement");
container.set_property_or_panic("width" , width);
container.set_property_or_panic("height", format!("{}px", height));
container.set_style_or_panic("width" , width);
container.set_style_or_panic("height", format!("{}px", height));
container.set_attribute_or_panic("id", name);
container.set_property_or_panic("position", "relative");
container.set_style_or_panic("position", "relative");
div.append_or_panic(&container);

View File

@ -31,10 +31,10 @@ impl Group {
let div = dyn_into::<_, HtmlElement>(div).expect("HtmlElement");
div.set_attribute_or_panic("id" , name);
div.set_property_or_panic ("display" , "flex");
div.set_property_or_panic ("flex-wrap" , "wrap");
div.set_property_or_panic ("border" , "1px solid black");
div.set_property_or_panic ("margin-bottom", "10px");
div.set_style_or_panic ("display" , "flex");
div.set_style_or_panic ("flex-wrap" , "wrap");
div.set_style_or_panic ("border" , "1px solid black");
div.set_style_or_panic ("margin-bottom", "10px");
let header = create_element("center");
let header = header.expect("TestGroup failed to create header");
@ -43,8 +43,8 @@ impl Group {
let border = "1px solid black";
header.set_inner_html(name);
header.set_property_or_panic("border-bottom", border);
header.set_property_or_panic("width" , "100%");
header.set_style_or_panic("border-bottom", border);
header.set_style_or_panic("width" , "100%");
div.append_or_panic(&header);
let document = document().expect("Document is not present");