Skip to content

Commit

Permalink
implemented instancing
Browse files Browse the repository at this point in the history
- resolve #57
  • Loading branch information
mrDIMAS committed Nov 14, 2020
1 parent 25bdd0e commit 30fabc6
Show file tree
Hide file tree
Showing 20 changed files with 1,295 additions and 473 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ lazy_static = "1.4.0"
futures = {version = "0.3.6", features = ["thread-pool"] }
ddsfile = "0.4.0"
rapier3d = "0.3.0"
arrayvec = "0.5.2"

[dev-dependencies]
imageproc = "0.21.0"
Expand Down
301 changes: 301 additions & 0 deletions examples/instancing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
//! Example X. Instancing.
//!
//! Difficulty: Easy.
//!
//! This example shows how to create simple scene with lots of animated models without
//! any performance impact.

extern crate rg3d;

pub mod shared;

use crate::shared::create_camera;
use rg3d::rand::Rng;
use rg3d::{
animation::Animation,
core::{
algebra::{Matrix4, UnitQuaternion, Vector3},
color::Color,
pool::Handle,
},
engine::resource_manager::ResourceManager,
event::{ElementState, Event, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
gui::{
message::{MessageDirection, TextMessage},
node::StubNode,
text::TextBuilder,
widget::WidgetBuilder,
},
renderer::surface::{SurfaceBuilder, SurfaceSharedData},
scene::{base::BaseBuilder, mesh::MeshBuilder, node::Node, transform::TransformBuilder, Scene},
utils::translate_event,
};
use std::{
sync::{Arc, Mutex},
time::Instant,
};

// Create our own engine type aliases. These specializations are needed
// because engine provides a way to extend UI with custom nodes and messages.
type GameEngine = rg3d::engine::Engine<(), StubNode>;
type UiNode = rg3d::gui::node::UINode<(), StubNode>;
type BuildContext<'a> = rg3d::gui::BuildContext<'a, (), StubNode>;

fn create_ui(ctx: &mut BuildContext) -> Handle<UiNode> {
TextBuilder::new(WidgetBuilder::new()).build(ctx)
}

struct GameScene {
scene: Scene,
camera: Handle<Node>,
animations: Vec<Handle<Animation>>,
}

async fn create_scene(resource_manager: ResourceManager) -> GameScene {
let mut scene = Scene::new();

// Camera is our eyes in the world - you won't see anything without it.
let camera = create_camera(resource_manager.clone(), Vector3::new(0.0, 32.0, -164.0)).await;

let camera = scene.graph.add_node(Node::Camera(camera));

// Load model and animation resource in parallel. Is does *not* adds anything to
// our scene - it just loads a resource then can be used later on to instantiate
// models from it on scene. Why loading of resource is separated from instantiation?
// Because it is too inefficient to load a resource every time you trying to
// create instance of it - much more efficient is to load it once and then make copies
// of it. In case of models it is very efficient because single vertex and index buffer
// can be used for all models instances, so memory footprint on GPU will be lower.
let (model_resource, walk_animation_resource) = rg3d::futures::join!(
resource_manager.request_model("examples/data/mutant.FBX"),
resource_manager.request_model("examples/data/walk.fbx")
);

let mut animations = Vec::new();

for z in -10..10 {
for x in -10..10 {
// Instantiate model on scene - but only geometry, without any animations.
// Instantiation is a process of embedding model resource data in desired scene.
let model_handle = model_resource
.clone()
.unwrap()
.instantiate_geometry(&mut scene);

// Now we have whole sub-graph instantiated, we can start modifying model instance.
scene.graph[model_handle]
.local_transform_mut()
// Our model is too big, fix it by scale.
.set_scale(Vector3::new(0.05, 0.05, 0.05))
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::y_axis(),
180.0f32.to_radians(),
))
.set_position(Vector3::new((x as f32) * 7.0, 0.0, (z as f32) * 7.0));

// Add simple animation for our model. Animations are loaded from model resources -
// this is because animation is a set of skeleton bones with their own transforms.
// Once animation resource is loaded it must be re-targeted to our model instance.
// Why? Because animation in *resource* uses information about *resource* bones,
// not model instance bones, retarget_animations maps animations of each bone on
// model instance so animation will know about nodes it should operate on.
let walk_animation = *walk_animation_resource
.clone()
.unwrap()
.retarget_animations(model_handle, &mut scene)
.get(0)
.unwrap();

scene
.animations
.get_mut(walk_animation)
.set_speed(rg3d::rand::thread_rng().gen_range(0.8, 1.2));

animations.push(walk_animation);
}
}

GameScene {
scene,
camera,
animations,
}
}

struct InputController {
rotate_left: bool,
rotate_right: bool,
}

fn main() {
let event_loop = EventLoop::new();

let window_builder = rg3d::window::WindowBuilder::new()
.with_title("Example - Instancing")
.with_resizable(true);

let mut engine = GameEngine::new(window_builder, &event_loop).unwrap();

// Prepare resource manager - it must be notified where to search textures. When engine
// loads model resource it automatically tries to load textures it uses. But since most
// model formats store absolute paths, we can't use them as direct path to load texture
// instead we telling engine to search textures in given folder.
engine
.resource_manager
.state()
.set_textures_path("examples/data");

// Create simple user interface that will show some useful info.
let debug_text = create_ui(&mut engine.user_interface.build_ctx());

// Create test scene.
let GameScene {
scene,
camera,
animations,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));

// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
// actions you need.
let scene_handle = engine.scenes.add(scene);

// Set ambient light.
engine
.renderer
.set_ambient_color(Color::opaque(200, 200, 200));

let clock = Instant::now();
let fixed_timestep = 1.0 / 60.0;
let mut elapsed_time = 0.0;

// We will rotate model using keyboard input.
let mut camera_angle = 0.0f32.to_radians();

// Create input controller - it will hold information about needed actions.
let mut input_controller = InputController {
rotate_left: false,
rotate_right: false,
};

// Finally run our event loop which will respond to OS and window events and update
// engine state accordingly. Engine lets you to decide which event should be handled,
// this is minimal working example if how it should be.
event_loop.run(move |event, _, control_flow| {
match event {
Event::MainEventsCleared => {
// This main game loop - it has fixed time step which means that game
// code will run at fixed speed even if renderer can't give you desired
// 60 fps.
let mut dt = clock.elapsed().as_secs_f32() - elapsed_time;
while dt >= fixed_timestep {
dt -= fixed_timestep;
elapsed_time += fixed_timestep;

// ************************
// Put your game logic here.
// ************************

// Use stored scene handle to borrow a mutable reference of scene in
// engine.
let scene = &mut engine.scenes[scene_handle];

// Our animation must be applied to scene explicitly, otherwise
// it will have no effect.
for &animation in animations.iter() {
scene
.animations
.get_mut(animation)
.get_pose()
.apply(&mut scene.graph);
}

// Rotate model according to input controller state.
if input_controller.rotate_left {
camera_angle -= 5.0f32.to_radians();
} else if input_controller.rotate_right {
camera_angle += 5.0f32.to_radians();
}

scene.graph[camera].local_transform_mut().set_rotation(
UnitQuaternion::from_axis_angle(&Vector3::y_axis(), camera_angle),
);

let statistics = engine.renderer.get_statistics();
let text = format!(
"Example - Instancing\nUse [A][D] keys to rotate camera.\nFPS: {}\nFrame time: {} ms\nDraw Calls: {}\nTriangles Rendered: {}",
statistics.frames_per_second,
statistics.pure_frame_time,
statistics.geometry.draw_calls,
statistics.geometry.triangles_rendered
);
engine.user_interface.send_message(TextMessage::text(
debug_text,
MessageDirection::ToWidget,
text,
));

engine.update(fixed_timestep);
}

// It is very important to "pump" messages from UI. Even if don't need to
// respond to such message, you should call this method, otherwise UI
// might behave very weird.
while let Some(_ui_event) = engine.user_interface.poll_message() {
// ************************
// Put your data model synchronization code here. It should
// take message and update data in your game according to
// changes in UI.
// ************************
}

// Rendering must be explicitly requested and handled after RedrawRequested event is received.
engine.get_window().request_redraw();
}
Event::RedrawRequested(_) => {
// Run renderer at max speed - it is not tied to game code.
engine.render(fixed_timestep).unwrap();
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(size) => {
// It is very important to handle Resized event from window, because
// renderer knows nothing about window size - it must be notified
// directly when window size has changed.
engine.renderer.set_frame_size(size.into());
}
WindowEvent::KeyboardInput { input, .. } => {
// Handle key input events via `WindowEvent`, not via `DeviceEvent` (#32)
if let Some(key_code) = input.virtual_keycode {
match key_code {
VirtualKeyCode::A => {
input_controller.rotate_left =
input.state == ElementState::Pressed
}
VirtualKeyCode::D => {
input_controller.rotate_right =
input.state == ElementState::Pressed
}
_ => (),
}
}
}
_ => (),
}

// It is very important to "feed" user interface (UI) with events coming
// from main window, otherwise UI won't respond to mouse, keyboard, or any
// other event.
if let Some(os_event) = translate_event(&event) {
engine.user_interface.process_os_event(&os_event);
}
}
Event::DeviceEvent { .. } => {
// Handle key input events via `WindowEvent`, not via `DeviceEvent` (#32)
}
_ => *control_flow = ControlFlow::Poll,
}
});
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate inflate;
extern crate lexical;
#[macro_use]
extern crate lazy_static;
extern crate arrayvec;
extern crate ddsfile;

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/blur.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl Blur {
state,
viewport,
&self.shader.program,
DrawParameters {
&DrawParameters {
cull_face: CullFace::Back,
culling: false,
color_write: Default::default(),
Expand Down
Loading

0 comments on commit 30fabc6

Please sign in to comment.