60 steps per second Physics Simulator (Kinematics, Spring, Air Dragging) (https://github.com/enso-org/ide/pull/91)

* 60 steps per second Physics Simulator (Kinematics, Spring, Air Dragging)

* Style fix

* Review refactoring

* Moved mass to Kinematics

* Linter fixes

* Vertical alignemnt update

* Removing std::mem::forget from physics test

* Physically correct interpolated simulator

* PhysicsSimulator implementation only with one Animator

* Added source commcents

* Safer Physics mod setters

* Removed use of performance.now() in set_time


Original commit: 0596cc5097
This commit is contained in:
Danilo Guanabara 2019-12-26 13:53:25 -03:00 committed by GitHub
parent df8321d9da
commit cd95b281e8
19 changed files with 750 additions and 64 deletions

View File

@ -0,0 +1,47 @@
use super::ContinuousTimeAnimator;
use super::FnAnimation;
// ====================
// === AnimatorData ===
// ====================
struct AnimatorData {
closure : Box<dyn FnMut(f32)>,
previous_ms : Option<f32>
}
impl AnimatorData {
pub fn new<F:FnAnimation>(f:F) -> Self {
let closure = Box::new(f);
let previous_ms = None;
Self { closure, previous_ms }
}
}
// ================
// === Animator ===
// ================
/// This structure runs an animation every frame with the time difference from the last frame as
/// its input.
pub struct Animator {
_continuous_animator: ContinuousTimeAnimator
}
impl Animator {
pub fn new<F:FnAnimation>(f:F) -> Self {
let mut data = AnimatorData::new(f);
let _continuous_animator = ContinuousTimeAnimator::new(move |current_ms| {
if let Some(previous_ms) = data.previous_ms {
let delta_ms = current_ms - previous_ms;
(data.closure)(delta_ms);
}
data.previous_ms = Some(current_ms);
});
Self { _continuous_animator }
}
}

View File

@ -0,0 +1,64 @@
use crate::system::web::get_performance;
use crate::system::web::animation_frame_loop::AnimationFrameLoop;
use super::FnAnimation;
use std::rc::Rc;
use std::cell::RefCell;
// ==================================
// === ContinuousTimeAnimatorData ===
// ==================================
struct ContinuousTimeAnimatorData {
closure : Box<dyn FnMut(f32)>,
start_time : f32,
current_time : f32
}
impl ContinuousTimeAnimatorData {
pub fn new<F:FnAnimation>(f:F) -> Self {
let closure = Box::new(f);
let start_time = get_performance().expect("Couldn't get performance timer").now() as f32;
let current_time = start_time;
Self { closure,start_time,current_time }
}
pub fn set_time(&mut self, time:f32) {
self.start_time = self.current_time + time;
}
}
// ==============================
// === ContinuousTimeAnimator ===
// ==============================
/// This structure runs an animation with continuous time as its input.
pub struct ContinuousTimeAnimator {
_animation_loop : AnimationFrameLoop,
data : Rc<RefCell<ContinuousTimeAnimatorData>>
}
impl ContinuousTimeAnimator {
pub fn new<F:FnAnimation>(f:F) -> Self {
let data = Rc::new(RefCell::new(ContinuousTimeAnimatorData::new(f)));
let data_clone = data.clone();
let _animation_loop = AnimationFrameLoop::new(move |current_time| {
let mut data : &mut ContinuousTimeAnimatorData = &mut data_clone.borrow_mut();
(data.closure)(current_time - data.start_time);
data.current_time = current_time;
});
Self { _animation_loop, data }
}
}
// === Setters ===
impl ContinuousTimeAnimator {
pub fn set_time(&mut self, time:f32) {
self.data.borrow_mut().set_time(time);
}
}

View File

@ -0,0 +1,79 @@
use super::Animator;
use super::FnAnimation;
use nalgebra::zero;
// =======================
// === IntervalCounter ===
// =======================
/// This struct counts the intervals in a time period.
pub struct IntervalCounter {
pub interval_duration : f32,
pub accumulated_time : f32
}
impl IntervalCounter {
pub fn new(interval_duration:f32) -> Self {
let accumulated_time = zero();
Self { interval_duration, accumulated_time }
}
/// Adds time to the counter and returns the number of intervals it reached.
pub fn add_time(&mut self, time:f32) -> u32 {
self.accumulated_time += time;
let count = (self.accumulated_time / self.interval_duration) as u32;
self.accumulated_time -= count as f32 * self.interval_duration;
count
}
}
// =============================
// === FixedStepAnimatorData ===
// =============================
struct FixedStepAnimatorData {
closure : Box<dyn FnMut(f32)>,
counter : IntervalCounter
}
impl FixedStepAnimatorData {
pub fn new<F:FnAnimation>(steps_per_second:f32, f:F) -> Self {
let closure = Box::new(f);
let step_duration = 1000.0 / steps_per_second;
let counter = IntervalCounter::new(step_duration);
Self { closure,counter }
}
}
// ================
// === Animator ===
// ================
/// This structure attempts to run a closure at a fixed time rate.
///
/// # Internals
/// If, for instance, we want to run FnAnimation once per second, it's delta_time
/// (FnAnimation(delta_time)) will be 1 second. But keep in mind that if the actual frame takes
/// longer, say 2 seconds, FnAnimation will be called twice in the same moment, but its delta_time
/// parameter will always be fixed to 1 second.
pub struct FixedStepAnimator {
_animator: Animator
}
impl FixedStepAnimator {
pub fn new<F:FnAnimation>(steps_per_second:f32, f:F) -> Self {
let mut data = FixedStepAnimatorData::new(steps_per_second, f);
let _animator = Animator::new(move |delta_ms| {
let intervals = data.counter.add_time(delta_ms);
for _ in 0..intervals {
(data.closure)(data.counter.interval_duration);
}
});
Self { _animator }
}
}

View File

@ -0,0 +1,35 @@
// FIXME: Animators structs should get EventLoop as parameter. The whole application should have
// only one RequestAnimationFrame loop going on to avoid its overhead.
pub mod physics;
mod continuous_time_animator;
mod animator;
mod fixed_step_animator;
pub use continuous_time_animator::ContinuousTimeAnimator;
pub use animator::Animator;
pub use fixed_step_animator::FixedStepAnimator;
pub use fixed_step_animator::IntervalCounter;
// ===================
// === FnAnimation ===
// ===================
pub trait FnAnimation = FnMut(f32) + 'static;
// FIXME: The objects in this section needs a better place.
// =============
// === Utils ===
// =============
use nalgebra::clamp;
use std::ops::Mul;
use std::ops::Add;
pub fn linear_interpolation<T>(a:T, b:T, t:f32) -> T
where T : Mul<f32, Output = T> + Add<T, Output = T> {
let t = clamp(t, 0.0, 1.0);
a * (1.0 - t) + b * t
}

View File

@ -0,0 +1,320 @@
use crate::prelude::*;
use crate::animation::{Animator, IntervalCounter, linear_interpolation};
use crate::traits::HasPosition;
use nalgebra::Vector3;
use nalgebra::zero;
// ====================
// === PhysicsForce ===
// ====================
pub trait PhysicsForce {
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32>;
}
// ======================
// === DragProperties ===
// ======================
/// This structure contains air dragging properties.
#[derive(Default, Clone, Copy)]
pub struct DragProperties {
coefficient: f32
}
impl DragProperties {
pub fn new(amount:f32) -> Self {
Self { coefficient: amount }
}
}
impl PhysicsForce for DragProperties {
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32> {
let velocity = kinematics.velocity;
let speed = velocity.norm();
if speed > 0.0 {
velocity.normalize() * speed * speed * -0.5 * self.coefficient
} else {
zero()
}
}
}
// === Getters ===
impl DragProperties {
pub fn coefficient(self) -> f32 {
self.coefficient
}
}
// === Setters ===
impl DragProperties {
pub fn set_coefficient(&mut self, coefficient:f32) {
self.coefficient = coefficient
}
}
// ========================
// === SpringProperties ===
// ========================
/// This structure contains spring physics properties.
#[derive(Debug, Clone, Copy)]
pub struct SpringProperties {
coefficient : f32,
fixed_point : Vector3<f32>
}
impl Default for SpringProperties {
fn default() -> Self {
Self::new(zero(),zero())
}
}
impl SpringProperties {
pub fn new(coefficient:f32, fixed_point:Vector3<f32>) -> Self {
Self { coefficient,fixed_point }
}
}
// === Getters ===
impl SpringProperties {
pub fn coefficient(&self) -> f32 { self.coefficient }
pub fn fixed_point(&self) -> Vector3<f32> { self.fixed_point }
}
impl PhysicsForce for SpringProperties {
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32> {
let delta = self.fixed_point() - kinematics.position();
let delta_len = delta.magnitude();
if delta_len > 0.0 {
let force_val = delta_len * self.coefficient();
delta.normalize() * force_val
} else {
zero()
}
}
}
// === Setters ===
impl SpringProperties {
pub fn set_coefficient(&mut self, coefficient:f32) { self.coefficient = coefficient }
pub fn set_fixed_point(&mut self, fixed_point:Vector3<f32>) { self.fixed_point = fixed_point }
}
// ============================
// === KinematicProperties ===
// ============================
/// This structure contains kinematics properties.
#[derive(Debug, Clone, Copy)]
pub struct KinematicsProperties {
position : Vector3<f32>,
velocity : Vector3<f32>,
acceleration : Vector3<f32>,
mass : f32
}
impl Default for KinematicsProperties {
fn default() -> Self {
Self::new(zero(),zero(),zero(),zero())
}
}
impl KinematicsProperties {
pub fn new
(position:Vector3<f32>, velocity:Vector3<f32>, acceleration:Vector3<f32>, mass:f32) -> Self {
Self { position,velocity,acceleration,mass }
}
}
// === Getters ===
impl KinematicsProperties {
pub fn velocity (&self) -> Vector3<f32> { self.velocity }
pub fn acceleration(&self) -> Vector3<f32> { self.acceleration }
pub fn mass (&self) -> f32 { self.mass }
}
// === Setters ===
impl KinematicsProperties {
pub fn set_velocity(&mut self, velocity:Vector3<f32>) {
self.velocity = velocity
}
pub fn set_acceleration(&mut self, acceleration:Vector3<f32>) {
self.acceleration = acceleration
}
pub fn set_mass(&mut self, mass:f32) {
self.mass = mass
}
}
impl HasPosition for KinematicsProperties {
fn position (&self) -> Vector3<f32> { self.position }
fn set_position(&mut self, position:Vector3<f32>) { self.position = position }
}
// =============================
// === PhysicsPropertiesData ===
// =============================
struct PhysicsPropertiesData {
kinematics : KinematicsProperties,
spring : SpringProperties,
drag : DragProperties
}
impl PhysicsPropertiesData {
pub fn new
(kinematics: KinematicsProperties, spring:SpringProperties, drag:DragProperties) -> Self {
Self { kinematics,spring,drag }
}
}
// =========================
// === PhysicsProperties ===
// =========================
/// A structure including kinematics, drag and spring properties.
#[derive(Clone)]
pub struct PhysicsProperties {
data : Rc<RefCell<PhysicsPropertiesData>>
}
impl PhysicsProperties {
pub fn new
(kinematics: KinematicsProperties, spring:SpringProperties, drag:DragProperties) -> Self {
let data = Rc::new(RefCell::new(PhysicsPropertiesData::new(kinematics, spring, drag)));
Self { data }
}
}
// === Getters ===
impl PhysicsProperties {
pub fn kinematics(&self) -> KinematicsProperties { self.data.borrow().kinematics }
pub fn spring (&self) -> SpringProperties { self.data.borrow().spring }
pub fn drag (&self) -> DragProperties { self.data.borrow().drag }
}
// === Setters ===
impl PhysicsProperties {
pub fn mod_kinematics<F:FnOnce(&mut KinematicsProperties)>(&mut self, f:F) {
let mut kinematics = self.kinematics();
f(&mut kinematics);
self.set_kinematics(kinematics);
}
pub fn set_kinematics(&mut self, kinematics:KinematicsProperties) {
self.data.borrow_mut().kinematics = kinematics;
}
pub fn mod_spring<F:FnOnce(&mut SpringProperties)>(&mut self, f:F) {
let mut spring = self.spring();
f(&mut spring);
self.set_spring(spring);
}
pub fn set_spring(&mut self, spring:SpringProperties) {
self.data.borrow_mut().spring = spring;
}
pub fn mod_drag<F:FnOnce(&mut DragProperties)>(&mut self, f:F) {
let mut drag = self.drag();
f(&mut drag);
self.set_drag(drag);
}
pub fn set_drag(&mut self, drag:DragProperties) {
self.data.borrow_mut().drag = drag;
}
}
// ========================
// === SimulationObject ===
// ========================
pub trait SimulationObject = HasPosition + 'static;
// ========================
// === PhysicsSimulator ===
// ========================
/// A 60 steps per second physics simulator used to simulate `Properties`.
pub struct PhysicsSimulator {
_animator : Animator
}
impl PhysicsSimulator {
/// Simulates `Properties` on `object`.
pub fn new<T:SimulationObject>(mut object:T, mut properties:PhysicsProperties) -> Self {
properties.mod_kinematics(|kinematics| { kinematics.set_position(object.position()); });
let steps_per_second = 60.0;
let step_ms = 1000.0 / steps_per_second;
let mut current_position = object.position();
let mut next_position = simulate(&mut properties, step_ms);
let mut interval_counter = IntervalCounter::new(step_ms);
let _animator = Animator::new(move |delta_ms| {
let intervals = interval_counter.add_time(delta_ms);
for _ in 0..intervals {
current_position = next_position;
next_position = simulate(&mut properties, step_ms);
}
let transition = interval_counter.accumulated_time / interval_counter.interval_duration;
let position = linear_interpolation(current_position, next_position, transition);
object.set_position(position);
});
Self { _animator }
}
}
/// Simulate the `KinematicProperties`.
fn simulate_kinematics(kinematics:&mut KinematicsProperties, force:&Vector3<f32>, dt:f32) {
kinematics.set_acceleration(force / kinematics.mass);
kinematics.set_velocity(kinematics.velocity() + kinematics.acceleration() * dt);
kinematics.set_position(kinematics.position() + kinematics.velocity() * dt);
}
/// Runs a simulation step.
fn simulate(properties:&mut PhysicsProperties, delta_ms:f32) -> Vector3<f32> {
let spring = properties.spring();
let drag = properties.drag();
let mut net_force = zero();
properties.mod_kinematics(|mut kinematics| {
net_force += spring.force(&kinematics);
net_force += drag.force(&kinematics);
let delta_seconds = delta_ms / 1000.0;
simulate_kinematics(&mut kinematics, &net_force, delta_seconds);
});
properties.kinematics().position()
}

View File

@ -35,7 +35,7 @@ impl EventLoop {
/// Init the event loop.
fn init(self) -> Self {
let data = Rc::downgrade(&self.rc);
let main = move || { data.upgrade().map(|t| t.borrow_mut().run()); };
let main = move |_| { data.upgrade().map(|t| t.borrow_mut().run()); };
with(self.rc.borrow_mut(), |mut data| {
data.main = Some(Closure::new(main));
data.run();
@ -60,7 +60,7 @@ impl EventLoop {
#[derive(Derivative)]
#[derivative(Debug, Default)]
pub struct EventLoopData {
main : Option<Closure<dyn FnMut()>>,
main : Option<Closure<dyn FnMut(f32)>>,
callbacks : CallbackRegistry,
main_id : i32,
}

View File

@ -6,16 +6,19 @@ use crate::system::web::dyn_into;
use crate::system::web::Result;
use crate::system::web::Error;
use crate::system::web::StyleSetter;
use crate::traits::HasPosition;
use nalgebra::Vector2;
use nalgebra::{Vector2, Vector3};
use web_sys::HtmlElement;
// ==================
// === HTMLObject ===
// ==================
/// A structure for representing a 3D HTMLElement in a `HTMLScene`.
#[derive(Shrinkwrap, Debug)]
#[derive(Shrinkwrap, Debug, Clone)]
#[shrinkwrap(mutable)]
pub struct HTMLObject {
#[shrinkwrap(main_field)]
@ -24,6 +27,16 @@ pub struct HTMLObject {
dimensions : Vector2<f32>,
}
impl HasPosition for HTMLObject {
fn position(&self) -> Vector3<f32> {
self.object.position()
}
fn set_position(&mut self, position:Vector3<f32>) {
self.object.set_position(position)
}
}
impl HTMLObject {
/// Creates a HTMLObject from element name.
pub fn new(dom_name: &str) -> Result<Self> {

View File

@ -18,6 +18,12 @@ use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use web_sys::HtmlElement;
// ===================
// === Js Bindings ===
// ===================
mod js {
use super::*;
#[wasm_bindgen(module = "/src/display/render/css3d/html/snippets.js")]
@ -74,6 +80,8 @@ fn setup_camera_transform
}
}
// ========================
// === HTMLRendererData ===
// ========================
@ -145,7 +153,7 @@ impl HTMLRenderer {
/// Renders the `Scene` from `Camera`'s point of view.
pub fn render(&self, camera: &mut Camera, scene: &Scene<HTMLObject>) {
let trans_cam = camera.transform.to_homogeneous().try_inverse();
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);
@ -166,7 +174,7 @@ impl HTMLRenderer {
let scene : &Scene<HTMLObject> = &scene;
for object in &mut scene.into_iter() {
let mut transform = object.transform.to_homogeneous();
let mut transform = object.transform().to_homogeneous();
transform.iter_mut().for_each(|a| *a = eps(*a));
let parent_node = object.dom.parent_node();

View File

@ -5,67 +5,77 @@ use crate::display::render::css3d::Transform;
use nalgebra::UnitQuaternion;
use nalgebra::Vector3;
use std::rc::Rc;
use std::cell::RefCell;
use crate::traits::HasPosition;
// ==============
// === Object ===
// ==============
/// Base structure for representing a 3D object in a `Scene`.
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone)]
pub struct Object {
pub transform : Transform
transform : Rc<RefCell<Transform>>
}
impl Object {
/// Creates a Default Object.
pub fn new() -> Object { default() }
/// Sets the object's position.
pub fn set_position(&mut self, x:f32, y:f32, z:f32) {
self.transform.set_translation(x, y, z)
}
/// Gets the object's position.
pub fn position(&self) -> &Vector3<f32> {
self.transform.translation()
}
/// Gets object's transform.
pub fn transform(&self) -> Transform { *self.transform.borrow() }
/// Sets the object's rotation in YXZ (yaw -> roll -> pitch) order.
pub fn set_rotation(&mut self, roll:f32, pitch:f32, yaw:f32) {
self.transform.set_rotation(roll, pitch, yaw)
self.transform.borrow_mut().set_rotation(roll, pitch, yaw)
}
/// Gets the object's rotation UnitQuaternion.
pub fn rotation(&self) -> &UnitQuaternion<f32> {
self.transform.rotation()
pub fn rotation(&self) -> UnitQuaternion<f32> {
*self.transform.borrow().rotation()
}
/// Sets the object's scale.
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
self.transform.set_scale(x, y, z);
self.transform.borrow_mut().set_scale(x, y, z);
}
/// Gets the object's scale.
pub fn scale(&self) -> &Vector3<f32> {
self.transform.scale()
pub fn scale(&self) -> Vector3<f32> {
*self.transform.borrow().scale()
}
}
impl HasPosition for Object {
fn position(&self) -> Vector3<f32> {
*self.transform.borrow().translation()
}
fn set_position(&mut self, position:Vector3<f32>) {
self.transform.borrow_mut().set_translation(position)
}
}
#[cfg(test)]
mod test {
use super::Object;
use crate::traits::HasPosition;
use nalgebra::Vector3;
use nalgebra::Quaternion;
use std::f32::consts::PI;
#[test]
fn set_transform() {
use super::Object;
use nalgebra::Vector3;
use nalgebra::Quaternion;
use std::f32::consts::PI;
let mut object = Object::new();
object.set_position(1.0, 2.0, 3.0);
object.set_position(Vector3::new(1.0, 2.0, 3.0));
object.set_scale(3.0, 2.0, 1.0);
object.set_rotation(PI * 2.0, PI, PI / 2.0);
assert_eq!(*object.position(), Vector3::new(1.0, 2.0, 3.0));
assert_eq!(*object.scale(), Vector3::new(3.0, 2.0, 1.0));
assert_eq!(object.position(), Vector3::new(1.0, 2.0, 3.0));
assert_eq!(object.scale(), Vector3::new(3.0, 2.0, 1.0));
let expected = Quaternion::new
( 0.00000009272586

View File

@ -3,6 +3,8 @@ use crate::data::opt_vec::*;
type Index = usize;
// =============
// === Scene ===
// =============
@ -38,20 +40,24 @@ impl<T> Scene<T> {
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
/// Gets mutable iterator.
pub fn iter_mut(&mut self) -> IterMut<'_, T> { self.objects.iter_mut() }
/// Gets iterator.
pub fn iter(&self) -> Iter<'_, T> { self.objects.iter() }
}
impl<'a, T> IntoIterator for &'a Scene<T> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.objects.into_iter()
self.iter()
}
}
impl<'a, T> IntoIterator for &'a mut Scene<T> {
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
(&mut self.objects).into_iter()
}
type IntoIter = IterMut<'a, T> ;
fn into_iter(self) -> Self::IntoIter { self.iter_mut() }
}

View File

@ -5,6 +5,8 @@ use nalgebra::Quaternion;
use nalgebra::UnitQuaternion;
use nalgebra::Vector3;
// =============
// === Utils ===
// =============
@ -22,12 +24,14 @@ fn from_euler_angles_pry(roll:f32, pitch:f32, yaw:f32) -> UnitQuaternion<f32> {
))
}
// =================
// === Transform ===
// =================
/// A structure representing 3D Position, Rotation and Scale.
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct Transform {
pub translation : Vector3<f32>,
pub rotation : UnitQuaternion<f32>,
@ -48,8 +52,8 @@ impl Transform {
pub fn identity() -> Self { default() }
/// Sets Transform's translation.
pub fn set_translation(&mut self, x:f32, y:f32, z:f32) {
self.translation = Vector3::new(x, y, z);
pub fn set_translation(&mut self, translation:Vector3<f32>) {
self.translation = translation;
}
/// Gets Transform's translation.
@ -121,6 +125,8 @@ impl Transform {
}
}
// =============
// === Tests ===
// =============
@ -147,7 +153,7 @@ mod test {
use std::f32::consts::PI;
let mut transform = Transform::identity();
transform.set_translation(1.0, 2.0, 3.0);
transform.set_translation(Vector3::new(1.0, 2.0, 3.0));
transform.set_scale(3.0, 2.0, 1.0);
transform.set_rotation(PI * 2.0, PI, PI / 2.0);

View File

@ -26,6 +26,7 @@ pub mod control;
pub mod data;
pub mod debug;
pub mod display;
pub mod traits;
pub use basegl_prelude as prelude;
pub mod system {

View File

@ -0,0 +1,8 @@
use nalgebra::Vector3;
pub trait HasPosition {
/// Gets self's position.
fn position(&self) -> Vector3<f32>;
/// Sets self's position.
fn set_position(&mut self, position:Vector3<f32>);
}

View File

@ -24,8 +24,10 @@ mod tests {
use basegl::display::render::css3d::html::HTMLRenderer;
use basegl::system::web::StyleSetter;
use basegl::system::web::get_performance;
use basegl::traits::HasPosition;
use web_test::*;
use web_sys::Performance;
use nalgebra::Vector3;
#[web_test(no_container)]
fn invalid_container() {
@ -44,7 +46,7 @@ mod tests {
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let mut object = HTMLObject::new("div").unwrap();
object.set_position(0.0, 0.0, 0.0);
object.set_position(Vector3::new(0.0, 0.0, 0.0));
object.dom.set_property_or_panic("background-color", "black");
object.set_dimensions(100.0, 100.0);
scene.add(object);
@ -52,7 +54,7 @@ mod tests {
let aspect_ratio = view_dim.x / view_dim.y;
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
// We move the Camera behind the object so we don't see it.
camera.set_position(0.0, 0.0, -100.0);
camera.set_position(Vector3::new(0.0, 0.0, -100.0));
renderer.render(&mut camera, &scene);
}
@ -76,7 +78,7 @@ mod tests {
let x = (i * axis.0) as f32;
let y = (i * axis.1) as f32;
let z = (i * axis.2) as f32;
object.set_position(x, y, z);
object.set_position(Vector3::new(x, y, z));
// Creates a gradient color based on the axis.
let r = (x * 25.5) as u8;
@ -105,7 +107,7 @@ mod tests {
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
// We move the Camera 29 units away from the center.
camera.set_position(0.0, 0.0, 29.0);
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
renderer.render(&mut camera, &scene);
}
@ -125,7 +127,7 @@ mod tests {
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
// We move the Camera -29 units away from the center.
camera.set_position(0.0, 0.0, -29.0);
camera.set_position(Vector3::new(0.0, 0.0, -29.0));
// We rotate it 180 degrees so we can see the center of the scene
// from behind.
camera.set_rotation(0.0, PI, 0.0);
@ -150,7 +152,7 @@ mod tests {
b.iter(move || {
let t = (performance.now() / 1000.0) as f32;
// We move the Camera 29 units away from the center.
camera.set_position(t.sin() * 5.0, t.cos() * 5.0, 29.0);
camera.set_position(Vector3::new(t.sin() * 5.0, t.cos() * 5.0, 29.0));
renderer.render(&mut camera, &scene);
})
@ -174,7 +176,7 @@ mod tests {
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;
object.set_position(x * 5.0, y * 5.0, z * 5.0);
object.set_position(Vector3::new(x * 5.0, y * 5.0, z * 5.0));
let faster_t = t * 100.0;
let r = (i + 0.0 + faster_t) as u8 % 255;
@ -208,7 +210,7 @@ mod tests {
.expect("Couldn't get performance obj");
// We move the Camera 29 units away from the center.
camera.set_position(0.0, 0.0, 29.0);
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
make_sphere(&mut scene, &performance);
@ -241,7 +243,7 @@ mod tests {
.expect("Couldn't get performance obj");
// We move the Camera 29 units away from the center.
camera.set_position(0.0, 0.0, 29.0);
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
b.iter(move || {
make_sphere(&mut scene, &performance);

View File

@ -0,0 +1,75 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
use web_test::web_configure;
web_configure!(run_in_browser);
#[cfg(test)]
mod tests {
use basegl::system::web::StyleSetter;
use basegl::animation::physics::DragProperties;
use basegl::animation::physics::SpringProperties;
use basegl::animation::physics::KinematicsProperties;
use basegl::animation::FixedStepAnimator;
use basegl::animation::physics::PhysicsSimulator;
use basegl::animation::physics::PhysicsProperties;
use basegl::display::render::css3d::html::HTMLRenderer;
use basegl::display::render::css3d::html::HTMLObject;
use basegl::display::render::css3d::Scene;
use basegl::display::render::css3d::Camera;
use basegl::traits::HasPosition;
use web_test::*;
use nalgebra::{zero, Vector3};
use js_sys::Math::random;
#[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");
let mut scene : Scene<HTMLObject> = Scene::new();
let mut target = HTMLObject::new("div").unwrap();
target.set_dimensions(1.0, 1.0);
target.dom.set_property_or_panic("background-color", "green");
scene.add(target.clone());
let mut object = HTMLObject::new("div").unwrap();
object.set_dimensions(1.0, 1.0);
object.dom.set_property_or_panic("background-color", "red");
scene.add(object.clone());
let view_dim = renderer.dimensions();
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
let aspect_ratio = view_dim.x / view_dim.y;
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
let mass = 2.0;
let kinematics = KinematicsProperties::new(zero(), zero(), zero(), mass);
let coefficient = 10.0;
let fixed_point = zero();
let spring = SpringProperties::new(coefficient, fixed_point);
let drag = DragProperties::new(0.8);
let mut properties = PhysicsProperties::new(kinematics, spring, drag);
let simulator = PhysicsSimulator::new(object, properties.clone());
// Updates spring's fixed point every two seconds.
let every = 2.0;
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
let x = 32.0 * (random() - 0.5) as f32;
let y = 24.0 * (random() - 0.5) as f32;
let z = 0.0;
let position = Vector3::new(x, y, z);
properties.mod_spring(|spring| spring.set_fixed_point(position));
target.set_position(position);
});
b.iter(move || {
let _keep_alive = &simulator;
let _keep_alive = &animator;
renderer.render(&mut camera, &scene);
});
}
}

View File

@ -5,6 +5,14 @@ use std::cell::RefCell;
use wasm_bindgen::prelude::Closure;
// =======================
// === FnAnimationLoop ===
// =======================
pub trait FnAnimationLoop = FnMut(f32) + 'static;
// ==========================
// === AnimationFrameData ===
// ==========================
@ -19,13 +27,14 @@ pub struct AnimationFrameLoop {
}
// ==========================
// === AnimationFrameLoop ===
// ==========================
impl AnimationFrameLoop {
pub fn new(mut func:Box<dyn FnMut()>) -> Self {
let nop_func = Box::new(|| ()) as Box<dyn FnMut()>;
pub fn new<F:FnAnimationLoop>(mut f:F) -> Self {
let nop_func = Box::new(|_| ()) as Box<dyn FnMut(f32)>;
let nop_closure = Closure::once(nop_func);
let callback = Rc::new(RefCell::new(nop_closure));
let run = true;
@ -33,13 +42,13 @@ impl AnimationFrameLoop {
let callback_clone = callback.clone();
let data_clone = data.clone();
*callback.borrow_mut() = Closure::wrap(Box::new(move || {
*callback.borrow_mut() = Closure::wrap(Box::new(move |delta_time| {
if data_clone.borrow().run {
func();
f(delta_time);
let clb = &callback_clone.borrow();
request_animation_frame(&clb).expect("Request Animation Frame");
}
}) as Box<dyn FnMut()>);
}) as Box<dyn FnMut(f32)>);
request_animation_frame(&callback.borrow()).unwrap();
let forget = false;

View File

@ -1,8 +1,7 @@
#![feature(trait_alias)]
pub mod resize_observer;
mod animationframeloop;
pub use animationframeloop::AnimationFrameLoop;
pub mod animation_frame_loop;
use basegl_prelude::*;
@ -113,24 +112,24 @@ impl Logger {
#[cfg(target_arch = "wasm32")]
impl Logger {
pub fn trace<M: LogMsg>(&self, msg: M) {
pub fn trace<M: LogMsg>(&self, _msg: M) {
// console::debug_1(&self.format(msg));
}
pub fn info<M: LogMsg>(&self, msg: M) {
pub fn info<M: LogMsg>(&self, _msg: M) {
// console::group_1(&self.format(msg));
// console::group_end();
}
pub fn warning<M: LogMsg>(&self, msg: M) {
pub fn warning<M: LogMsg>(&self, _msg: M) {
// console::warn_1(&self.format(msg));
}
pub fn error<M: LogMsg>(&self, msg: M) {
pub fn error<M: LogMsg>(&self, _msg: M) {
// console::error_1(&self.format(msg));
}
pub fn group_begin<M: LogMsg>(&self, msg: M) {
pub fn group_begin<M: LogMsg>(&self, _msg: M) {
// console::group_1(&self.format(msg));
}
@ -232,7 +231,7 @@ pub fn get_webgl2_context
context.dyn_into().map_err(|_| no_webgl())
}
pub fn request_animation_frame(f:&Closure<dyn FnMut()>) -> Result<i32> {
pub fn request_animation_frame(f:&Closure<dyn FnMut(f32)>) -> Result<i32> {
let req = window()?.request_animation_frame(f.as_ref().unchecked_ref());
req.map_err(|_| Error::missing("requestAnimationFrame"))
}

View File

@ -2,7 +2,7 @@ use crate::prelude::*;
use super::BenchContainer;
pub use crate::system::web::get_performance;
pub use crate::system::web::AnimationFrameLoop;
pub use crate::system::web::animation_frame_loop::AnimationFrameLoop;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
@ -10,6 +10,7 @@ use std::rc::Rc;
use std::cell::RefCell;
// ===================
// === BencherCell ===
// ===================
@ -46,6 +47,7 @@ impl BencherCell {
}
// ===================
// === BencherData ===
// ===================
@ -66,7 +68,7 @@ impl BencherData {
let data_clone = self.clone();
let performance = get_performance().expect("Performance object");
let mut t0 = performance.now();
let anim_loop = AnimationFrameLoop::new(Box::new(move || {
let anim_loop = AnimationFrameLoop::new(Box::new(move |_| {
let mut data = data_clone.borrow_mut();
(&mut data.func)();
@ -91,6 +93,8 @@ impl BencherData {
}
}
// ===============
// === Bencher ===
// ===============