Przeglądaj źródła

Initial world rewrite

Daniele Bartolini 10 lat temu
rodzic
commit
e1f4aed008

+ 5 - 7
src/lua/lua_environment.cpp

@@ -5,13 +5,13 @@
 
 
 #include "config.h"
+#include "device.h"
 #include "error.h"
+#include "log.h"
 #include "lua_environment.h"
-#include "lua_stack.h"
 #include "lua_resource.h"
-#include "device.h"
+#include "lua_stack.h"
 #include "resource_manager.h"
-#include "log.h"
 #include <stdarg.h>
 
 namespace crown
@@ -33,6 +33,7 @@ extern void load_window(LuaEnvironment& env);
 extern void load_world(LuaEnvironment& env);
 extern void load_material(LuaEnvironment& env);
 extern void load_input(LuaEnvironment& env);
+extern void load_render_world(LuaEnvironment& env);
 
 // When an error occurs, logs the error message and pauses the engine.
 static int error_handler(lua_State* L)
@@ -103,14 +104,11 @@ void LuaEnvironment::load_libs()
 	load_physics_world(*this);
 	load_resource_package(*this);
 	load_sound_world(*this);
-	load_raycast(*this);
-	load_resource_package(*this);
-	load_sound_world(*this);
-	load_sprite(*this);
 	load_window(*this);
 	load_world(*this);
 	load_material(*this);
 	load_input(*this);
+	load_render_world(*this);
 
 	// Register custom loader
 	lua_getfield(L, LUA_GLOBALSINDEX, "package");

+ 356 - 46
src/lua/lua_physics_world.cpp

@@ -8,66 +8,357 @@
 #include "physics_world.h"
 #include "quaternion.h"
 #include "memory.h"
+#include "temp_allocator.h"
 
 namespace crown
 {
 
-static int physics_world_gravity(lua_State* L)
+struct RaycastInfo
+{
+	const char* name;
+	RaycastMode::Enum mode;
+};
+
+static RaycastInfo s_raycast[] =
+{
+	{ "closest", RaycastMode::CLOSEST },
+	{ "all",     RaycastMode::ALL     }
+};
+CE_STATIC_ASSERT(CE_COUNTOF(s_raycast) == RaycastMode::COUNT);
+
+static RaycastMode::Enum name_to_raycast_mode(const char* name)
+{
+	for (uint32_t i = 0; i < CE_COUNTOF(s_raycast); ++i)
+	{
+		if (strcmp(s_raycast[i].name, name) == 0)
+			return s_raycast[i].mode;
+	}
+
+	return RaycastMode::COUNT;
+}
+
+static int physics_world_actor_instances(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.push_vector3(stack.get_physics_world(1)->gravity());
+	ActorInstance inst = stack.get_physics_world(1)->actor(stack.get_unit(2));
+
+	if (inst.i == UINT32_MAX)
+		stack.push_nil();
+	else
+		stack.push_actor(inst);
+
 	return 1;
 }
 
-static int physics_world_set_gravity(lua_State* L)
+static int physics_world_actor_world_position(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_physics_world(1)->set_gravity(stack.get_vector3(2));
+	stack.push_vector3(stack.get_physics_world(1)->actor_world_position(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_actor_world_rotation(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_quaternion(stack.get_physics_world(1)->actor_world_rotation(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_actor_world_pose(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_matrix4x4(stack.get_physics_world(1)->actor_world_pose(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_teleport_actor_world_position(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->teleport_actor_world_position(stack.get_actor(2), stack.get_vector3(3));
 	return 0;
 }
 
-static int physics_world_make_raycast(lua_State* L)
+static int physics_world_teleport_actor_world_rotation(lua_State* L)
 {
 	LuaStack stack(L);
+	stack.get_physics_world(1)->teleport_actor_world_rotation(stack.get_actor(2), stack.get_quaternion(3));
+	return 0;
+}
 
-	PhysicsWorld* world = stack.get_physics_world(1);
-	/* const char* callback = */ stack.get_string(2);
-	int mode = stack.get_int(3);
-	int filter = stack.get_int(4);
+static int physics_world_teleport_actor_world_pose(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->teleport_actor_world_pose(stack.get_actor(2), stack.get_matrix4x4(3));
+	return 0;
+}
+
+static int physics_world_actor_center_of_mass(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_physics_world(1)->actor_center_of_mass(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_enable_actor_gravity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->enable_actor_gravity(stack.get_actor(2));
+	return 0;
+}
+
+static int physics_world_disable_actor_gravity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->disable_actor_gravity(stack.get_actor(2));
+	return 0;
+}
+
+static int physics_world_enable_actor_collision(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->enable_actor_collision(stack.get_actor(2));
+	return 0;
+}
+
+static int physics_world_disable_actor_collision(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->disable_actor_collision(stack.get_actor(2));
+	return 0;
+}
+
+static int physics_world_set_actor_collision_filter(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_collision_filter(stack.get_actor(2), stack.get_string_id(3));
+	return 0;
+}
 
-	RaycastId raycast = world->create_raycast((CollisionMode::Enum) mode, (CollisionType::Enum) filter);
+static int physics_world_set_actor_kinematic(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_kinematic(stack.get_actor(2), stack.get_bool(3));
+	return 0;
+}
+
+static int physics_world_move_actor(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->move_actor(stack.get_actor(2), stack.get_vector3(3));
+	return 0;
+}
 
-	stack.push_raycast(world->get_raycast(raycast));
+static int physics_world_is_static(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_bool(stack.get_physics_world(1)->is_static(stack.get_actor(2)));
 	return 1;
 }
 
-static int physics_world_overlap_test(lua_State* L)
+static int physics_world_is_dynamic(lua_State* L)
 {
 	LuaStack stack(L);
+	stack.push_bool(stack.get_physics_world(1)->is_dynamic(stack.get_actor(2)));
+	return 1;
+}
 
-	PhysicsWorld* world = stack.get_physics_world(1);
-	CollisionType::Enum filter = (CollisionType::Enum) stack.get_int(2);
-	ShapeType::Enum shape_type = (ShapeType::Enum) stack.get_int(3);
-	Vector3 pos = stack.get_vector3(4);
-	Quaternion rot = stack.get_quaternion(5);
-	Vector3 size = stack.get_vector3(6);
+static int physics_world_is_kinematic(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_bool(stack.get_physics_world(1)->is_kinematic(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_is_nonkinematic(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_bool(stack.get_physics_world(1)->is_nonkinematic(stack.get_actor(2)));
+	return 1;
+}
+
+
+static int physics_world_actor_linear_damping(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_physics_world(1)->actor_linear_damping(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_set_actor_linear_damping(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_linear_damping(stack.get_actor(2), stack.get_float(3));
+	return 0;
+}
 
+static int physics_world_actor_angular_damping(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_physics_world(1)->actor_angular_damping(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_set_actor_angular_damping(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_angular_damping(stack.get_actor(2), stack.get_float(3));
+	return 0;
+}
+
+static int physics_world_actor_linear_velocity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_physics_world(1)->actor_linear_velocity(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_set_actor_linear_velocity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_linear_velocity(stack.get_actor(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int physics_world_actor_angular_velocity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_physics_world(1)->actor_angular_velocity(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_set_actor_angular_velocity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_actor_angular_velocity(stack.get_actor(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int physics_world_add_actor_impulse(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->add_actor_impulse(stack.get_actor(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int physics_world_add_actor_impulse_at(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->add_actor_impulse_at(stack.get_actor(2), stack.get_vector3(3), stack.get_vector3(4));
+	return 0;
+}
+
+static int physics_world_add_actor_torque_impulse(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->add_actor_torque_impulse(stack.get_actor(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int physics_world_push_actor(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->push_actor(stack.get_actor(2), stack.get_vector3(3), stack.get_float(4));
+	return 0;
+}
+
+static int physics_world_push_actor_at(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->push_actor_at(stack.get_actor(2), stack.get_vector3(3), stack.get_float(4), stack.get_vector3(5));
+	return 0;
+}
+
+static int physics_world_is_sleeping(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_bool(stack.get_physics_world(1)->is_sleeping(stack.get_actor(2)));
+	return 1;
+}
+
+static int physics_world_wake_up(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->wake_up(stack.get_actor(2));
+	return 0;
+}
 
-	Array<Actor*> actors(default_allocator());
+static int physics_world_controller_instances(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_controller(stack.get_physics_world(1)->controller(stack.get_unit(2)));
+	return 1;
+}
 
-	world->overlap_test(filter, shape_type, pos, rot, size, actors);
+static int physics_world_move_controller(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->move_controller(stack.get_controller(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int physics_world_create_joint(lua_State* L)
+{
+	LuaStack stack(L);
+	JointDesc jd;
+	jd.type = JointType::SPRING;
+	jd.anchor_0 = vector3(0, -2, 0);
+	jd.anchor_1 = vector3(0, 2, 0);
+	jd.break_force = 999999.0f;
+	jd.hinge.axis = vector3(1, 0, 0);
+	jd.hinge.lower_limit = -3.14f / 4.0f;
+	jd.hinge.upper_limit = 3.14f / 4.0f;
+	jd.hinge.bounciness = 12.0f;
+	stack.get_physics_world(1)->create_joint(stack.get_actor(2), stack.get_actor(3), jd);
+	return 0;
+}
+
+static int physics_world_gravity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_physics_world(1)->gravity());
+	return 1;
+}
+
+static int physics_world_set_gravity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->set_gravity(stack.get_vector3(2));
+	return 0;
+}
+
+static int physics_world_raycast(lua_State* L)
+{
+	LuaStack stack(L);
+	PhysicsWorld* world = stack.get_physics_world(1);
+
+	TempAllocator1024 ta;
+	Array<RaycastHit> hits(ta);
+
+	world->raycast(stack.get_vector3(2)
+		, stack.get_vector3(3)
+		, stack.get_float(4)
+		, name_to_raycast_mode(stack.get_string(5))
+		, hits
+		);
 
 	stack.push_table();
-	for (uint32_t i = 0; i < array::size(actors); i++)
+	for (uint32_t i = 0; i < array::size(hits); ++i)
 	{
 		stack.push_key_begin(i+1);
-		stack.push_actor(actors[i]);
+		stack.push_actor(hits[i].actor);
 		stack.push_key_end();
 	}
 
 	return 1;
 }
 
+static int physics_world_enable_debug_drawing(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_physics_world(1)->enable_debug_drawing(stack.get_bool(2));
+	return 0;
+}
+
 static int physics_world_tostring(lua_State* L)
 {
 	LuaStack stack(L);
@@ -78,30 +369,49 @@ static int physics_world_tostring(lua_State* L)
 
 void load_physics_world(LuaEnvironment& env)
 {
-	env.load_module_function("PhysicsWorld", "gravity",      physics_world_gravity);
-	env.load_module_function("PhysicsWorld", "set_gravity",  physics_world_set_gravity);
-	env.load_module_function("PhysicsWorld", "make_raycast", physics_world_make_raycast);
-	env.load_module_function("PhysicsWorld", "overlap_test", physics_world_overlap_test);
-	env.load_module_function("PhysicsWorld", "__index",      "PhysicsWorld");
-	env.load_module_function("PhysicsWorld", "__tostring",   physics_world_tostring);
-
-	env.load_module_enum("ActorType", "STATIC",            ActorType::STATIC);
-	env.load_module_enum("ActorType", "DYNAMIC_PHYSICAL",  ActorType::DYNAMIC_PHYSICAL);
-	env.load_module_enum("ActorType", "DYNAMIC_KINEMATIC", ActorType::DYNAMIC_KINEMATIC);
-
-	env.load_module_enum("ShapeType", "SPHERE",      ShapeType::SPHERE);
-	env.load_module_enum("ShapeType", "CAPSULE",     ShapeType::CAPSULE);
-	env.load_module_enum("ShapeType", "BOX",         ShapeType::BOX);
-	env.load_module_enum("ShapeType", "PLANE",       ShapeType::PLANE);
-	env.load_module_enum("ShapeType", "CONVEX_MESH", ShapeType::CONVEX_MESH);
-
-	env.load_module_enum("CollisionMode", "CLOSEST", CollisionMode::CLOSEST);
-	env.load_module_enum("CollisionMode", "ANY",     CollisionMode::ANY);
-	env.load_module_enum("CollisionMode", "ALL",     CollisionMode::ALL);
-
-	env.load_module_enum("CollisionType", "STATIC",  CollisionType::STATIC);
-	env.load_module_enum("CollisionType", "DYNAMIC", CollisionType::DYNAMIC);
-	env.load_module_enum("CollisionType", "BOTH",    CollisionType::BOTH);
+	env.load_module_function("PhysicsWorld", "actor_instances",               physics_world_actor_instances);
+	env.load_module_function("PhysicsWorld", "actor_world_position",          physics_world_actor_world_position);
+	env.load_module_function("PhysicsWorld", "actor_world_rotation",          physics_world_actor_world_rotation);
+	env.load_module_function("PhysicsWorld", "actor_world_pose",              physics_world_actor_world_pose);
+	env.load_module_function("PhysicsWorld", "teleport_actor_world_position", physics_world_teleport_actor_world_position);
+	env.load_module_function("PhysicsWorld", "teleport_actor_world_rotation", physics_world_teleport_actor_world_rotation);
+	env.load_module_function("PhysicsWorld", "teleport_actor_world_pose",     physics_world_teleport_actor_world_pose);
+	env.load_module_function("PhysicsWorld", "actor_center_of_mass",          physics_world_actor_center_of_mass);
+	env.load_module_function("PhysicsWorld", "enable_actor_gravity",          physics_world_enable_actor_gravity);
+	env.load_module_function("PhysicsWorld", "disable_actor_gravity",         physics_world_disable_actor_gravity);
+	env.load_module_function("PhysicsWorld", "enable_actor_collision",        physics_world_enable_actor_collision);
+	env.load_module_function("PhysicsWorld", "disable_actor_collision",       physics_world_disable_actor_collision);
+	env.load_module_function("PhysicsWorld", "set_actor_collision_filter",    physics_world_set_actor_collision_filter);
+	env.load_module_function("PhysicsWorld", "set_actor_kinematic",           physics_world_set_actor_kinematic);
+	env.load_module_function("PhysicsWorld", "move_actor",                    physics_world_move_actor);
+	env.load_module_function("PhysicsWorld", "is_static",                     physics_world_is_static);
+	env.load_module_function("PhysicsWorld", "is_dynamic",                    physics_world_is_dynamic);
+	env.load_module_function("PhysicsWorld", "is_kinematic",                  physics_world_is_kinematic);
+	env.load_module_function("PhysicsWorld", "is_nonkinematic",               physics_world_is_nonkinematic);
+	env.load_module_function("PhysicsWorld", "actor_linear_damping",          physics_world_actor_linear_damping);
+	env.load_module_function("PhysicsWorld", "set_actor_linear_damping",      physics_world_set_actor_linear_damping);
+	env.load_module_function("PhysicsWorld", "actor_angular_damping",         physics_world_actor_angular_damping);
+	env.load_module_function("PhysicsWorld", "set_actor_angular_damping",     physics_world_set_actor_angular_damping);
+	env.load_module_function("PhysicsWorld", "actor_linear_velocity",         physics_world_actor_linear_velocity);
+	env.load_module_function("PhysicsWorld", "set_actor_linear_velocity",     physics_world_set_actor_linear_velocity);
+	env.load_module_function("PhysicsWorld", "actor_angular_velocity",        physics_world_actor_angular_velocity);
+	env.load_module_function("PhysicsWorld", "set_actor_angular_velocity",    physics_world_set_actor_angular_velocity);
+	env.load_module_function("PhysicsWorld", "add_actor_impulse",             physics_world_add_actor_impulse);
+	env.load_module_function("PhysicsWorld", "add_actor_impulse_at",          physics_world_add_actor_impulse_at);
+	env.load_module_function("PhysicsWorld", "add_actor_torque_impulse",      physics_world_add_actor_torque_impulse);
+	env.load_module_function("PhysicsWorld", "push_actor",                    physics_world_push_actor);
+	env.load_module_function("PhysicsWorld", "push_actor_at",                 physics_world_push_actor_at);
+	env.load_module_function("PhysicsWorld", "is_sleeping",                   physics_world_is_sleeping);
+	env.load_module_function("PhysicsWorld", "wake_up",                       physics_world_wake_up);
+	env.load_module_function("PhysicsWorld", "controller_instances",          physics_world_controller_instances);
+	env.load_module_function("PhysicsWorld", "move_controller",               physics_world_move_controller);
+	env.load_module_function("PhysicsWorld", "create_joint",                  physics_world_create_joint);
+	env.load_module_function("PhysicsWorld", "gravity",                       physics_world_gravity);
+	env.load_module_function("PhysicsWorld", "set_gravity",                   physics_world_set_gravity);
+	env.load_module_function("PhysicsWorld", "raycast",                       physics_world_raycast);
+	env.load_module_function("PhysicsWorld", "enable_debug_drawing",          physics_world_enable_debug_drawing);
+	env.load_module_function("PhysicsWorld", "__index",                       "PhysicsWorld");
+	env.load_module_function("PhysicsWorld", "__tostring",                    physics_world_tostring);
 }
 
 } // namespace crown

+ 284 - 0
src/lua/lua_render_world.cpp

@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#include "quaternion.h"
+#include "render_world.h"
+#include "lua_stack.h"
+#include "lua_environment.h"
+
+namespace crown
+{
+
+struct LightInfo
+{
+	const char* name;
+	LightType::Enum type;
+};
+
+static LightInfo s_light[] =
+{
+	{ "directional", LightType::DIRECTIONAL },
+	{ "omni",        LightType::OMNI        },
+	{ "spot",        LightType::SPOT        }
+};
+CE_STATIC_ASSERT(CE_COUNTOF(s_light) == LightType::COUNT);
+
+static LightType::Enum name_to_light_type(LuaStack& stack, const char* name)
+{
+	for (uint32_t i = 0; i < CE_COUNTOF(s_light); ++i)
+	{
+		if (strcmp(s_light[i].name, name) == 0)
+			return s_light[i].type;
+	}
+
+	LUA_ASSERT(false, stack, "Unknown light type: %s", name);
+	return LightType::COUNT;
+}
+
+static int render_world_create_mesh(lua_State* L)
+{
+	LuaStack stack(L);
+	RenderWorld* rw = stack.get_render_world(1);
+	UnitId unit = stack.get_unit(2);
+
+	MeshRendererDesc desc;
+	desc.mesh_resource = stack.get_resource_id(3);
+	desc.mesh_name = stack.get_string_id(4);
+	desc.material_resource = stack.get_resource_id(5);
+	desc.visible = stack.get_bool(6);
+
+	stack.push_mesh_instance(rw->create_mesh(unit, desc, MATRIX4X4_IDENTITY));
+	return 1;
+}
+
+static int render_world_destroy_mesh(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->destroy_mesh(stack.get_mesh_instance(2));
+	return 0;
+}
+
+static int render_world_mesh_instances(lua_State* L)
+{
+	LuaStack stack(L);
+	RenderWorld* rw = stack.get_render_world(1);
+	UnitId unit = stack.get_unit(2);
+	MeshInstance inst = rw->first_mesh(unit);
+
+	stack.push_table();
+	for (uint32_t i = 0; rw->is_valid(inst); ++i, inst = rw->next_mesh(inst))
+	{
+		stack.push_key_begin(i+1);
+		stack.push_mesh_instance(inst);
+		stack.push_key_end();
+	}
+
+	return 1;
+}
+
+static int render_world_mesh_obb(lua_State* L)
+{
+	LuaStack stack(L);
+	OBB obb = stack.get_render_world(1)->mesh_obb(stack.get_mesh_instance(2));
+	stack.push_matrix4x4(obb.tm);
+	stack.push_vector3(obb.half_extents);
+	return 2;
+}
+
+static int render_world_set_mesh_visible(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_mesh_visible(stack.get_mesh_instance(2), stack.get_bool(3));
+	return 0;
+}
+
+static int render_world_create_sprite(lua_State* L)
+{
+	LuaStack stack(L);
+	RenderWorld* rw = stack.get_render_world(1);
+	UnitId unit = stack.get_unit(2);
+
+	SpriteRendererDesc desc;
+	StringId64 sprite_resource = stack.get_resource_id(3);
+	StringId64 material_resource = stack.get_resource_id(4);
+	bool visible = stack.get_bool(5);
+
+	stack.push_sprite_instance(rw->create_sprite(unit, desc, MATRIX4X4_IDENTITY));
+	return 1;
+}
+
+static int render_world_destroy_sprite(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->destroy_sprite(stack.get_sprite_instance(2));
+	return 0;
+}
+
+static int render_world_sprite_instances(lua_State* L)
+{
+	LuaStack stack(L);
+	RenderWorld* rw = stack.get_render_world(1);
+	UnitId unit = stack.get_unit(2);
+	SpriteInstance inst = rw->first_sprite(unit);
+
+	stack.push_table();
+	for (uint32_t i = 0; rw->is_valid(inst); ++i, inst = rw->next_sprite(inst))
+	{
+		stack.push_key_begin(i+1);
+		stack.push_sprite_instance(inst);
+		stack.push_key_end();
+	}
+}
+
+static int render_world_set_sprite_visible(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_sprite_visible(stack.get_sprite_instance(2), stack.get_bool(3));
+	return 0;
+}
+
+static int render_world_set_sprite_frame(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_sprite_frame(stack.get_sprite_instance(2), stack.get_int(3));
+	return 0;
+}
+
+static int render_world_create_light(lua_State* L)
+{
+	LuaStack stack(L);
+	LightDesc ld;
+	ld.type = LightType::DIRECTIONAL;
+	ld.range = 1.0f;
+	ld.intensity = 1.0f;
+	ld.spot_angle = 20.0f;
+	ld.color = vector3(1, 1, 1);
+	stack.push_light_instance(stack.get_render_world(1)->create_light(stack.get_unit(2), ld, MATRIX4X4_IDENTITY));
+	return 1;
+}
+
+static int render_world_destroy_light(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->destroy_light(stack.get_light_instance(2));
+	return 0;
+}
+
+static int render_world_light_instances(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_light_instance(stack.get_render_world(1)->light(stack.get_unit(2)));
+	return 1;
+}
+
+static int render_world_light_type(lua_State* L)
+{
+	LuaStack stack(L);
+	LightType::Enum type = stack.get_render_world(1)->light_type(stack.get_light_instance(2));
+	stack.push_string(s_light[type].name);
+	return 1;
+}
+
+static int render_world_light_color(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_color4(stack.get_render_world(1)->light_color(stack.get_light_instance(2)));
+	return 1;
+}
+
+static int render_world_light_range(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_render_world(1)->light_range(stack.get_light_instance(2)));
+	return 1;
+}
+
+static int render_world_light_intensity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_render_world(1)->light_intensity(stack.get_light_instance(2)));
+	return 1;
+}
+
+static int render_world_light_spot_angle(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_render_world(1)->light_spot_angle(stack.get_light_instance(2)));
+	return 1;
+}
+
+static int render_world_set_light_type(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_light_type(stack.get_light_instance(2)
+		, name_to_light_type(stack, stack.get_string(3))
+		);
+	return 0;
+}
+
+static int render_world_set_light_color(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_light_color(stack.get_light_instance(2), stack.get_color4(3));
+	return 0;
+}
+
+static int render_world_set_light_range(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_light_range(stack.get_light_instance(2), stack.get_float(3));
+	return 0;
+}
+
+static int render_world_set_light_intensity(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_light_intensity(stack.get_light_instance(2), stack.get_float(3));
+	return 0;
+}
+
+static int render_world_set_light_spot_angle(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->set_light_spot_angle(stack.get_light_instance(2), stack.get_float(3));
+	return 0;
+}
+
+static int render_world_enable_debug_drawing(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_render_world(1)->enable_debug_drawing(stack.get_bool(2));
+	return 0;
+}
+
+void load_render_world(LuaEnvironment& env)
+{
+	env.load_module_function("RenderWorld", "create_mesh",          render_world_create_mesh);
+	env.load_module_function("RenderWorld", "destroy_mesh",         render_world_destroy_mesh);
+	env.load_module_function("RenderWorld", "mesh_instances",       render_world_mesh_instances);
+	env.load_module_function("RenderWorld", "mesh_obb",             render_world_mesh_obb);
+	env.load_module_function("RenderWorld", "set_mesh_visible",     render_world_set_mesh_visible);
+	env.load_module_function("RenderWorld", "create_sprite",        render_world_create_sprite);
+	env.load_module_function("RenderWorld", "destroy_sprite",       render_world_destroy_sprite);
+	env.load_module_function("RenderWorld", "sprite_instances",     render_world_sprite_instances);
+	env.load_module_function("RenderWorld", "set_sprite_frame",     render_world_set_sprite_frame);
+	env.load_module_function("RenderWorld", "set_sprite_visible",   render_world_set_sprite_visible);
+	env.load_module_function("RenderWorld", "create_light",         render_world_create_light);
+	env.load_module_function("RenderWorld", "destroy_light",        render_world_destroy_light);
+	env.load_module_function("RenderWorld", "light_instances",      render_world_light_instances);
+	env.load_module_function("RenderWorld", "light_type",           render_world_light_type);
+	env.load_module_function("RenderWorld", "light_color",          render_world_light_color);
+	env.load_module_function("RenderWorld", "light_range",          render_world_light_range);
+	env.load_module_function("RenderWorld", "light_intensity",      render_world_light_intensity);
+	env.load_module_function("RenderWorld", "light_spot_angle",     render_world_light_spot_angle);
+	env.load_module_function("RenderWorld", "set_light_type",       render_world_set_light_type);
+	env.load_module_function("RenderWorld", "set_light_color",      render_world_set_light_color);
+	env.load_module_function("RenderWorld", "set_light_range",      render_world_set_light_range);
+	env.load_module_function("RenderWorld", "set_light_intensity",  render_world_set_light_intensity);
+	env.load_module_function("RenderWorld", "set_light_spot_angle", render_world_set_light_spot_angle);
+	env.load_module_function("RenderWorld", "enable_debug_drawing", render_world_enable_debug_drawing);
+}
+
+} // namespace crown

+ 60 - 2
src/lua/lua_stack.cpp

@@ -3,9 +3,20 @@
  * License: https://github.com/taylor001/crown/blob/master/LICENSE
  */
 
-#include "lua_stack.h"
-#include "lua_environment.h"
+#include "debug_line.h"
 #include "device.h"
+#include "gui.h"
+#include "level.h"
+#include "lua_environment.h"
+#include "lua_stack.h"
+#include "physics_world.h"
+#include "render_world.h"
+#include "resource_package.h"
+#include "scene_graph.h"
+#include "sound_world.h"
+#include "vector2.h"
+#include "vector3.h"
+#include "world.h"
 
 namespace crown
 {
@@ -99,4 +110,51 @@ void LuaStack::push_matrix4x4(const Matrix4x4& m)
 	lua_pushlightuserdata(L, device()->lua_environment()->next_matrix4x4(m));
 }
 
+void LuaStack::push_color4(const Color4& c)
+{
+	// Color4 represented as Quaternion
+	Quaternion q;
+	q.x = c.x;
+	q.y = c.y;
+	q.z = c.z;
+	q.w = c.w;
+	push_quaternion(q);
+}
+
+void LuaStack::check_type(int i, DebugLine* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != DebugLine::MARKER)
+		luaL_typerror(L, i, "DebugLine");
+}
+
+void LuaStack::check_type(int i, ResourcePackage* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != ResourcePackage::MARKER)
+		luaL_typerror(L, i, "ResourcePackage");
+}
+
+void LuaStack::check_type(int i, World* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != World::MARKER)
+		luaL_typerror(L, i, "World");
+}
+
+void LuaStack::check_type(int i, SceneGraph* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != SceneGraph::MARKER)
+		luaL_typerror(L, i, "SceneGraph");
+}
+
+void LuaStack::check_type(int i, RenderWorld* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != RenderWorld::MARKER)
+		luaL_typerror(L, i, "RenderWorld");
+}
+
+void LuaStack::check_type(int i, Level* p)
+{
+	if (!is_pointer(i) || *(uint32_t*)p != Level::MARKER)
+		luaL_typerror(L, i, "Level");
+}
+
 } // namespace crown

+ 176 - 153
src/lua/lua_stack.h

@@ -5,80 +5,34 @@
 
 #pragma once
 
-#include "types.h"
-#include "vector3.h"
-#include "vector2.h"
-#include "quaternion.h"
-#include "matrix4x4.h"
-#include "string_utils.h"
-#include "color4.h"
+#include "audio_types.h"
+#include "graphics_types.h"
+#include "physics_types.h"
+#include "resource_types.h"
 #include "string_id.h"
+#include "string_utils.h"
+#include "types.h"
+#include "world_types.h"
 #include <lua.hpp>
 
 #if CROWN_DEBUG
-	static void* checkudata(lua_State* L, int i, const char* expected)
-	{
-		luaL_checktype(L, i, LUA_TUSERDATA);
-		return luaL_checkudata(L, i, expected);
-	}
-
-	typedef bool (*checkfn)(int);
-	static void* checklightdata(lua_State* L, int i, checkfn cf, const char* expected)
-	{
-		luaL_checktype(L, i, LUA_TLIGHTUSERDATA);
-		if (!cf(i)) luaL_typerror(L, i, expected);
-		return lua_touserdata(L, i);
-	}
-
-	static bool always_true(int i)
-	{
-		return i == i;
-	}
-
-	#define CHECKUDATA(stack, i, expected) checkudata(stack, i, expected)
-	#define CHECKLIGHTDATA(stack, i, cf, expected) checklightdata(stack, i, cf, expected)
-	#define CHECKBOOLEAN(stack, i) lua_toboolean(stack, i)
-	#define CHECKINTEGER(stack, i) luaL_checkinteger(stack, i)
-	#define CHECKNUMBER(stack, i) luaL_checknumber(stack, i)
-	#define CHECKSTRING(stack, i) luaL_checkstring(stack, i)
-
 	#define LUA_ASSERT(condition, stack, msg, ...) do { if (!(condition)) {\
 		stack.push_fstring("\nLua assertion failed: %s\n\t" msg "\n", #condition, ##__VA_ARGS__);\
 		lua_error(stack.state()); }} while (0);
 #else
-	#define CHECKUDATA(stack, i, expected) lua_touserdata(stack, i)
-	#define CHECKLIGHTDATA(stack, i, cf, expected) lua_touserdata(stack, i)
-	#define CHECKBOOLEAN(stack, i) lua_toboolean(stack, i)
-	#define CHECKINTEGER(stack, i) lua_tointeger(stack, i)
-	#define CHECKNUMBER(stack, i) lua_tonumber(stack, i)
-	#define CHECKSTRING(stack, i) lua_tostring(stack, i)
-
 	#define LUA_ASSERT(...) ((void)0)
 #endif // CROWN_DEBUG
 
+#define LIGHTDATA_TYPE_BITS  2
+#define LIGHTDATA_TYPE_MASK  0x3
+#define LIGHTDATA_TYPE_SHIFT 0
+
+#define POINTER_MARKER       0x0
+#define UNIT_MARKER          0x1
+
 namespace crown
 {
 
-class PhysicsWorld;
-class SoundWorld;
-class World;
-struct Actor;
-struct Camera;
-struct Controller;
-struct Gui;
-struct Mesh;
-struct ResourcePackage;
-struct Sprite;
-struct Unit;
-struct DebugLine;
-struct Raycast;
-struct Material;
-
-typedef Id SoundInstanceId;
-typedef Id GuiId;
-
-typedef int (*MetamethodFunction)(lua_State*);
-
 struct LuaStack
 {
 	LuaStack(lua_State* L)
@@ -133,6 +87,11 @@ struct LuaStack
 		return lua_islightuserdata(L, i) == 1 && ((uintptr_t)lua_touserdata(L, i) & 0x3) == 0;
 	}
 
+	bool is_function(int i)
+	{
+		return lua_isfunction(L, i) == 1;
+	}
+
 	bool is_vector3(int i);
 	bool is_quaternion(int i);
 	bool is_matrix4x4(int i);
@@ -186,29 +145,30 @@ struct LuaStack
 		va_end(vl);
 	}
 
-	void push_literal(const char* s, uint32_t len)
+	void push_lstring(const char* s, uint32_t len)
 	{
 		lua_pushlstring(L, s, len);
 	}
 
 	void push_pointer(void* p)
 	{
+		CE_ASSERT_NOT_NULL(p);
 		lua_pushlightuserdata(L, p);
 	}
 
 	bool get_bool(int i)
 	{
-		return CHECKBOOLEAN(L, i) == 1;
+		return lua_toboolean(L, i) == 1;
 	}
 
 	int get_int(int i)
 	{
-		return (int)CHECKINTEGER(L, i);
+		return (int)lua_tonumber(L, i);
 	}
 
 	uint32_t get_id(int i)
 	{
-		return (uint32_t)CHECKINTEGER(L, i);
+		return (uint32_t)lua_tonumber(L, i);
 	}
 
 	StringId32 get_string_id(int i)
@@ -218,17 +178,22 @@ struct LuaStack
 
 	float get_float(int i)
 	{
-		return (float) CHECKNUMBER(L, i);
+		return (float)lua_tonumber(L, i);
 	}
 
 	const char* get_string(int i)
 	{
-		return CHECKSTRING(L, i);
+		return lua_tostring(L, i);
 	}
 
 	void* get_pointer(int i)
 	{
-		return lua_touserdata(L, i);
+		if (!lua_isuserdata(L, i))
+			luaL_typerror(L, i, "lightuserdata");
+
+		void* p = lua_touserdata(L, i);
+		CE_ASSERT_NOT_NULL(p);
+		return p;
 	}
 
 	/// Pushes an empty table onto the stack.
@@ -240,9 +205,9 @@ struct LuaStack
 	/// stack.push_key_begin("foo"); stack.push_foo(); stack.push_key_end()
 	/// stack.push_key_begin("bar"); stack.push_bar(); stack.push_key_end()
 	/// return 1;
-	void push_table()
+	void push_table(int narr = 0, int nrec = 0)
 	{
-		lua_newtable(L);
+		lua_createtable(L, narr, nrec);
 	}
 
 	/// See Stack::push_table()
@@ -270,180 +235,230 @@ struct LuaStack
 
 	StringId64 get_resource_id(int i)
 	{
-		return StringId64(CHECKSTRING(L, i));
+		return StringId64(get_string(i));
+	}
+
+	void push_debug_line(DebugLine* line)
+	{
+		push_pointer(line);
+	}
+
+	DebugLine* get_debug_line(int i)
+	{
+		DebugLine* p = (DebugLine*)get_pointer(i);
+		check_type(i, p);
+		return p;
 	}
 
 	void push_resource_package(ResourcePackage* package)
 	{
-		ResourcePackage** p = (ResourcePackage**) lua_newuserdata(L, sizeof(ResourcePackage*));
-		*p = package;
-		luaL_getmetatable(L, "ResourcePackage");
-		lua_setmetatable(L, -2);
+		push_pointer(package);
 	}
 
 	ResourcePackage* get_resource_package(int i)
 	{
-		ResourcePackage* pkg = *(ResourcePackage**) CHECKUDATA(L, i, "ResourcePackage");
-		return pkg;
+		ResourcePackage* p = (ResourcePackage*)get_pointer(i);
+		check_type(i, p);
+		return p;
 	}
 
 	void push_world(World* world)
 	{
-		World** w = (World**) lua_newuserdata(L, sizeof(World*));
-		*w = world;
-		luaL_getmetatable(L, "World");
-		lua_setmetatable(L, -2);
+		push_pointer(world);
 	};
 
 	World* get_world(int i)
 	{
-		World* w = *(World**) CHECKUDATA(L, i, "World");
-		return w;
+		World* p = (World*)get_pointer(i);
+		check_type(i, p);
+		return p;
 	};
 
+	void push_scene_graph(SceneGraph* sg)
+	{
+		push_pointer(sg);
+	}
+
+	SceneGraph* get_scene_graph(int i)
+	{
+		SceneGraph* p = (SceneGraph*)get_pointer(i);
+		check_type(i, p);
+		return p;
+	}
+
+	void push_level(Level* level)
+	{
+		push_pointer(level);
+	}
+
+	Level* get_level(int i)
+	{
+		Level* p = (Level*)get_pointer(i);
+		check_type(i, p);
+		return p;
+	}
+
+	void push_render_world(RenderWorld* world)
+	{
+		push_pointer(world);
+	}
+
+	RenderWorld* get_render_world(int i)
+	{
+		RenderWorld* p = (RenderWorld*)get_pointer(i);
+		check_type(i, p);
+		return p;
+	}
+
 	void push_physics_world(PhysicsWorld* world)
 	{
-		PhysicsWorld** w = (PhysicsWorld**) lua_newuserdata(L, sizeof(PhysicsWorld*));
-		luaL_getmetatable(L, "PhysicsWorld");
-		lua_setmetatable(L, -2);
-		*w = world;
+		push_pointer(world);
 	}
 
 	PhysicsWorld* get_physics_world(int i)
 	{
-		PhysicsWorld* w = *(PhysicsWorld**) CHECKUDATA(L, i, "PhysicsWorld");
-		return w;
+		PhysicsWorld* p = (PhysicsWorld*)get_pointer(i);
+//		if (*(uint32_t*)p != PhysicsWorld::MARKER)
+//			luaL_typerror(L, i, "PhysicsWorld");
+		return p;
 	}
 
 	void push_sound_world(SoundWorld* world)
 	{
-		SoundWorld** w = (SoundWorld**) lua_newuserdata(L, sizeof(SoundWorld*));
-		*w = world;
-		luaL_getmetatable(L, "SoundWorld");
-		lua_setmetatable(L, -2);
+		push_pointer(world);
 	}
 
 	SoundWorld* get_sound_world(int i)
 	{
-		SoundWorld* w = *(SoundWorld**) CHECKUDATA(L, i, "SoundWorld");
-		return w;
+		SoundWorld* p = (SoundWorld*)get_pointer(i);
+//		if (*(uint32_t*)p != SoundWorld::MARKER)
+//			luaL_typerror(L, i, "SoundWorld");
+		return p;
 	}
 
-	void push_unit(Unit* unit)
+	void push_unit(UnitId id)
 	{
-		lua_pushlightuserdata(L, unit);
+		uint32_t encoded = (id.encode() << 2) | UNIT_MARKER;
+		push_pointer((void*)(uintptr_t)encoded);
 	}
 
-	Unit* get_unit(int i)
+	UnitId get_unit(int i)
 	{
-		return (Unit*) CHECKLIGHTDATA(L, i, always_true, "Unit");
+		uint32_t enc = (uintptr_t)get_pointer(i);
+
+		if ((enc & LIGHTDATA_TYPE_MASK) != UNIT_MARKER)
+			luaL_typerror(L, i, "UnitId");
+
+		UnitId id;
+		id.decode(enc >> 2);
+		return id;
 	}
 
-	void push_camera(Camera* camera)
+	void push_camera(CameraInstance i)
 	{
-		lua_pushlightuserdata(L, camera);
+		push_id(i.i);
 	}
 
-	Camera* get_camera(int i)
+	CameraInstance get_camera(int i)
 	{
-		return (Camera*) CHECKLIGHTDATA(L, i, always_true, "Camera");
+		CameraInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_mesh(Mesh* mesh)
+	void push_transform(TransformInstance i)
 	{
-		lua_pushlightuserdata(L, mesh);
+		push_id(i.i);
 	}
 
-	Mesh* get_mesh(int i)
+	TransformInstance get_transform(int i)
 	{
-		return (Mesh*) CHECKLIGHTDATA(L, i, always_true, "Mesh");
+		TransformInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_sprite(Sprite* sprite)
+	void push_mesh_instance(MeshInstance i)
 	{
-		lua_pushlightuserdata(L, sprite);
+		push_id(i.i);
 	}
 
-	Sprite* get_sprite(int i)
+	MeshInstance get_mesh_instance(int i)
 	{
-		return (Sprite*) CHECKLIGHTDATA(L, i, always_true, "Sprite");
+		MeshInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_material(Material* material)
+	void push_sprite_instance(SpriteInstance i)
 	{
-		lua_pushlightuserdata(L, material);
+		push_id(i.i);
 	}
 
-	Material* get_material(int i)
+	SpriteInstance get_sprite_instance(int i)
 	{
-		return (Material*) CHECKLIGHTDATA(L, i, always_true, "Material");
+		SpriteInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_actor(Actor* actor)
+	void push_light_instance(LightInstance i)
 	{
-		lua_pushlightuserdata(L, actor);
+		push_id(i.i);
 	}
 
-	Actor* get_actor(int i)
+	LightInstance get_light_instance(int i)
 	{
-		return (Actor*) CHECKLIGHTDATA(L, i, always_true, "Actor");
+		LightInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_controller(Controller* controller)
+	void push_material(Material* material)
 	{
-		lua_pushlightuserdata(L, controller);
+		push_pointer(material);
 	}
 
-	Controller* get_controller(int i)
+	Material* get_material(int i)
 	{
-		return (Controller*) CHECKLIGHTDATA(L, i, always_true, "Controller");
+		return (Material*)get_pointer(i);
 	}
 
-	void push_raycast(Raycast* raycast)
+	void push_actor(ActorInstance i)
 	{
-		lua_pushlightuserdata(L, raycast);
+		push_id(i.i);
 	}
 
-	Raycast* get_raycast(int i)
+	ActorInstance get_actor(int i)
 	{
-		return (Raycast*) CHECKLIGHTDATA(L, i, always_true, "Raycast");
+		ActorInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_sound_instance_id(const SoundInstanceId id)
+	void push_controller(ControllerInstance i)
 	{
-		push_id(id.encode());
+		push_id(i.i);
 	}
 
-	SoundInstanceId get_sound_instance_id(int i)
+	ControllerInstance get_controller(int i)
 	{
-		uint32_t enc = get_id(i);
-		SoundInstanceId id;
-		id.decode(enc);
-		return id;
+		ControllerInstance inst = { get_id(i) };
+		return inst;
 	}
 
-	void push_gui(Gui* gui)
+	void push_sound_instance_id(const SoundInstanceId id)
 	{
-		lua_pushlightuserdata(L, gui);
+		push_id(id);
 	}
 
-	Gui* get_gui(int i)
+	SoundInstanceId get_sound_instance_id(int i)
 	{
-		return (Gui*) CHECKLIGHTDATA(L, i, always_true, "Gui");
+		return get_id(i);
 	}
 
-	void push_debug_line(DebugLine* line)
+	void push_gui(Gui* gui)
 	{
-		DebugLine** l = (DebugLine**) lua_newuserdata(L, sizeof(DebugLine*));
-		*l = line;
-		luaL_getmetatable(L, "DebugLine");
-		lua_setmetatable(L, -2);
+		push_pointer(gui);
 	}
 
-	DebugLine* get_debug_line(int i)
+	Gui* get_gui(int i)
 	{
-		DebugLine* l = *(DebugLine**) CHECKUDATA(L, i, "DebugLine");
-		return l;
+		return (Gui*)get_pointer(i);
 	}
 
 	Vector2 get_vector2(int i);
@@ -455,10 +470,11 @@ struct LuaStack
 	void push_vector3(const Vector3& v);
 	void push_matrix4x4(const Matrix4x4& m);
 	void push_quaternion(const Quaternion& q);
+	void push_color4(const Color4& c);
 
 	void push_vector2box(const Vector2& v)
 	{
-		Vector2* vec = (Vector2*) lua_newuserdata(L, sizeof(Vector2));
+		Vector2* vec = (Vector2*)lua_newuserdata(L, sizeof(Vector2));
 		luaL_getmetatable(L, "Vector2Box");
 		lua_setmetatable(L, -2);
 		*vec = v;
@@ -466,13 +482,13 @@ struct LuaStack
 
 	Vector2& get_vector2box(int i)
 	{
-		Vector2* v = (Vector2*) CHECKUDATA(L, i, "Vector2Box");
+		Vector2* v = (Vector2*)luaL_checkudata(L, i, "Vector2Box");
 		return *v;
 	}
 
 	void push_vector3box(const Vector3& v)
 	{
-		Vector3* vec = (Vector3*) lua_newuserdata(L, sizeof(Vector3));
+		Vector3* vec = (Vector3*)lua_newuserdata(L, sizeof(Vector3));
 		luaL_getmetatable(L, "Vector3Box");
 		lua_setmetatable(L, -2);
 		*vec = v;
@@ -480,13 +496,13 @@ struct LuaStack
 
 	Vector3& get_vector3box(int i)
 	{
-		Vector3* v = (Vector3*) CHECKUDATA(L, i, "Vector3Box");
+		Vector3* v = (Vector3*)luaL_checkudata(L, i, "Vector3Box");
 		return *v;
 	}
 
 	void push_quaternionbox(const Quaternion& q)
 	{
-		Quaternion* quat = (Quaternion*) lua_newuserdata(L, sizeof(Quaternion));
+		Quaternion* quat = (Quaternion*)lua_newuserdata(L, sizeof(Quaternion));
 		luaL_getmetatable(L, "QuaternionBox");
 		lua_setmetatable(L, -2);
 		*quat = q;
@@ -494,13 +510,13 @@ struct LuaStack
 
 	Quaternion& get_quaternionbox(int i)
 	{
-		Quaternion* q = (Quaternion*) CHECKUDATA(L, i, "QuaternionBox");
+		Quaternion* q = (Quaternion*)luaL_checkudata(L, i, "QuaternionBox");
 		return *q;
 	}
 
 	void push_matrix4x4box(const Matrix4x4& m)
 	{
-		Matrix4x4* mat = (Matrix4x4*) lua_newuserdata(L, sizeof(Matrix4x4));
+		Matrix4x4* mat = (Matrix4x4*)lua_newuserdata(L, sizeof(Matrix4x4));
 		luaL_getmetatable(L, "Matrix4x4Box");
 		lua_setmetatable(L, -2);
 		*mat = m;
@@ -508,7 +524,7 @@ struct LuaStack
 
 	Matrix4x4& get_matrix4x4box(int i)
 	{
-		Matrix4x4* m = (Matrix4x4*) CHECKUDATA(L, i, "Matrix4x4Box");
+		Matrix4x4* m = (Matrix4x4*)luaL_checkudata(L, i, "Matrix4x4Box");
 		return *m;
 	}
 
@@ -516,6 +532,13 @@ struct LuaStack
 	void check_temporary(int i, Quaternion* p);
 	void check_temporary(int i, Matrix4x4* p);
 
+	void check_type(int i, DebugLine* p);
+	void check_type(int i, ResourcePackage* p);
+	void check_type(int i, World* p);
+	void check_type(int i, SceneGraph* p);
+	void check_type(int i, RenderWorld* p);
+	void check_type(int i, Level* p);
+
 private:
 
 	lua_State* L;

+ 347 - 58
src/lua/lua_world.cpp

@@ -3,18 +3,44 @@
  * License: https://github.com/taylor001/crown/blob/master/LICENSE
  */
 
-#include "lua_stack.h"
-#include "lua_environment.h"
 #include "array.h"
-#include "gui.h"
-#include "temp_allocator.h"
-#include "world.h"
 #include "device.h"
+#include "lua_environment.h"
+#include "lua_stack.h"
 #include "resource_manager.h"
+#include "scene_graph.h"
+#include "sound_world.h"
+#include "temp_allocator.h"
+#include "world.h"
 
 namespace crown
 {
 
+struct ProjectionInfo
+{
+	const char* name;
+	ProjectionType::Enum type;
+};
+
+static ProjectionInfo s_projection[] =
+{
+	{ "orthographic", ProjectionType::ORTHOGRAPHIC },
+	{ "perspective",  ProjectionType::PERSPECTIVE  }
+};
+CE_STATIC_ASSERT(CE_COUNTOF(s_projection) == ProjectionType::COUNT);
+
+static ProjectionType::Enum name_to_projection_type(LuaStack& stack, const char* name)
+{
+	for (uint32_t i = 0; i < CE_COUNTOF(s_projection); ++i)
+	{
+		if (strcmp(s_projection[i].name, name) == 0)
+			return s_projection[i].type;
+	}
+
+	LUA_ASSERT(false, stack, "Unknown projection: %s", name);
+	return ProjectionType::COUNT;
+}
+
 static int world_spawn_unit(lua_State* L)
 {
 	LuaStack stack(L);
@@ -26,14 +52,14 @@ static int world_spawn_unit(lua_State* L)
 	LUA_ASSERT(device()->resource_manager()->can_get(UNIT_TYPE, name), stack, "Unit not found");
 
 	UnitId unit = world->spawn_unit(name, pos, rot);
-	stack.push_unit(world->get_unit(unit));
+	stack.push_unit(unit);
 	return 1;
 }
 
 static int world_destroy_unit(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_world(1)->destroy_unit(stack.get_unit(2)->id());
+	stack.get_world(1)->destroy_unit(stack.get_unit(2));
 	return 0;
 }
 
@@ -48,36 +74,133 @@ static int world_units(lua_State* L)
 {
 	LuaStack stack(L);
 
-	World* world = stack.get_world(1);
 	TempAllocator1024 alloc;
-	Array<UnitId> all_units(alloc);
-	world->units(all_units);
+	Array<UnitId> units(alloc);
+	stack.get_world(1)->units(units);
 
-	stack.push_table();
-	for (uint32_t i = 0; i < array::size(all_units); i++)
+	const uint32_t num = array::size(units);
+
+	stack.push_table(num);
+	for (uint32_t i = 0; i < num; ++i)
 	{
 		stack.push_key_begin((int32_t) i + 1);
-		stack.push_unit(world->get_unit(all_units[i]));
+		stack.push_unit(units[i]);
 		stack.push_key_end();
 	}
 
 	return 1;
 }
 
-static int world_link_unit(lua_State* L)
+static int world_camera(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_camera(stack.get_world(1)->camera(stack.get_unit(2)));
+	return 1;
+}
+
+static int camera_set_projection_type(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_projection_type(stack.get_camera(2)
+		, name_to_projection_type(stack, stack.get_string(3))
+		);
+	return 0;
+}
+
+static int camera_projection_type(lua_State* L)
+{
+	LuaStack stack(L);
+	ProjectionType::Enum type = stack.get_world(1)->camera_projection_type(stack.get_camera(2));
+	stack.push_string(s_projection[type].name);
+	return 1;
+}
+
+static int camera_fov(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_world(1)->link_unit(stack.get_unit(2)->id(), stack.get_unit(3)->id());
+	stack.push_float(stack.get_world(1)->camera_fov(stack.get_camera(2)));
+	return 1;
+}
+
+static int camera_set_fov(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_fov(stack.get_camera(2), stack.get_float(3));
 	return 0;
 }
 
-static int world_unlink_unit(lua_State* L)
+static int camera_aspect(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_world(1)->camera_aspect(stack.get_camera(2)));
+	return 1;
+}
+
+static int camera_set_aspect(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_world(1)->unlink_unit(stack.get_unit(2)->id());
+	stack.get_world(1)->set_camera_aspect(stack.get_camera(2), stack.get_float(3));
 	return 0;
 }
 
+static int camera_near_clip_distance(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_world(1)->camera_near_clip_distance(stack.get_camera(2)));
+	return 1;
+}
+
+static int camera_set_near_clip_distance(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_near_clip_distance(stack.get_camera(2), stack.get_float(3));
+	return 0;
+}
+
+static int camera_far_clip_distance(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_float(stack.get_world(1)->camera_far_clip_distance(stack.get_camera(2)));
+	return 1;
+}
+
+static int camera_set_far_clip_distance(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_far_clip_distance(stack.get_camera(2), stack.get_float(3));
+	return 0;
+}
+
+static int camera_set_orthographic_metrics(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_orthographic_metrics(stack.get_camera(2), stack.get_float(3), stack.get_float(4),
+		stack.get_float(5), stack.get_float(6));
+	return 0;
+}
+
+static int camera_set_viewport_metrics(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_world(1)->set_camera_viewport_metrics(stack.get_camera(2), stack.get_int(3), stack.get_int(4),
+		stack.get_int(5), stack.get_int(6));
+	return 0;
+}
+
+static int camera_screen_to_world(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_world(1)->camera_screen_to_world(stack.get_camera(2), stack.get_vector3(3)));
+	return 1;
+}
+
+static int camera_world_to_screen(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_world(1)->camera_world_to_screen(stack.get_camera(2), stack.get_vector3(3)));
+	return 1;
+}
+
 static int world_update_animations(lua_State* L)
 {
 	LuaStack stack(L);
@@ -163,43 +286,43 @@ static int world_set_sound_volume(lua_State* L)
 	return 0;
 }
 
-static int world_create_window_gui(lua_State* L)
+static int world_create_debug_line(lua_State* L)
 {
 	LuaStack stack(L);
-	World* world = stack.get_world(1);
-	GuiId id = world->create_window_gui(stack.get_int(2), stack.get_int(3), stack.get_string(4));
-	stack.push_gui(world->get_gui(id));
+	stack.push_debug_line(stack.get_world(1)->create_debug_line(stack.get_bool(2)));
 	return 1;
 }
 
-static int world_destroy_gui(lua_State* L)
+static int world_destroy_debug_line(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_world(1)->destroy_gui(stack.get_gui(2)->id());
+	stack.get_world(1)->destroy_debug_line(*stack.get_debug_line(2));
 	return 0;
 }
 
-static int world_create_debug_line(lua_State* L)
+static int world_load_level(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.push_debug_line(stack.get_world(1)->create_debug_line(stack.get_bool(2)));
+	const StringId64 name = stack.get_resource_id(2);
+	const Vector3& pos = stack.num_args() > 2 ? stack.get_vector3(3) : VECTOR3_ZERO;
+	const Quaternion& rot = stack.num_args() > 3 ? stack.get_quaternion(4) : QUATERNION_IDENTITY;
+	LUA_ASSERT(device()->resource_manager()->can_get(LEVEL_TYPE, name), stack, "Level not found");
+	stack.push_level(stack.get_world(1)->load_level(name, pos, rot));
 	return 1;
 }
 
-static int world_destroy_debug_line(lua_State* L)
+static int world_scene_graph(lua_State* L)
 {
 	LuaStack stack(L);
-	stack.get_world(1)->destroy_debug_line(*stack.get_debug_line(2));
-	return 0;
+	stack.push_scene_graph(stack.get_world(1)->scene_graph());
+	return 1;
 }
 
-static int world_load_level(lua_State* L)
+static int world_render_world(lua_State* L)
 {
 	LuaStack stack(L);
-	StringId64 name = stack.get_resource_id(2);
-	LUA_ASSERT(device()->resource_manager()->can_get(LEVEL_TYPE, name), stack, "Level not found");
-	stack.get_world(1)->load_level(name);
-	return 0;
+	stack.push_render_world(stack.get_world(1)->render_world());
+	return 1;
 }
 
 static int world_physics_world(lua_State* L)
@@ -224,33 +347,199 @@ static int world_tostring(lua_State* L)
 	return 1;
 }
 
+static int scene_graph_create(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_transform(stack.get_scene_graph(1)->create(stack.get_unit(2), MATRIX4X4_IDENTITY));
+	return 1;
+}
+
+static int scene_graph_destroy(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->destroy(stack.get_transform(2));
+	return 0;
+}
+
+static int scene_graph_transform_instances(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_transform(stack.get_scene_graph(1)->get(stack.get_unit(2)));
+	return 1;
+}
+
+static int scene_graph_local_position(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_scene_graph(1)->local_position(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_local_rotation(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_quaternion(stack.get_scene_graph(1)->local_rotation(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_local_scale(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_scene_graph(1)->local_scale(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_local_pose(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_matrix4x4(stack.get_scene_graph(1)->local_pose(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_world_position(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_vector3(stack.get_scene_graph(1)->world_position(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_world_rotation(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_quaternion(stack.get_scene_graph(1)->world_rotation(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_world_pose(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_matrix4x4(stack.get_scene_graph(1)->world_pose(stack.get_transform(2)));
+	return 1;
+}
+
+static int scene_graph_set_local_position(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->set_local_position(stack.get_transform(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int scene_graph_set_local_rotation(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->set_local_rotation(stack.get_transform(2), stack.get_quaternion(3));
+	return 0;
+}
+
+static int scene_graph_set_local_scale(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->set_local_scale(stack.get_transform(2), stack.get_vector3(3));
+	return 0;
+}
+
+static int scene_graph_set_local_pose(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->set_local_pose(stack.get_transform(2), stack.get_matrix4x4(3));
+	return 0;
+}
+
+static int scene_graph_link(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->link(stack.get_transform(2), stack.get_transform(3));
+	return 0;
+}
+
+static int scene_graph_unlink(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.get_scene_graph(1)->unlink(stack.get_transform(2));
+	return 0;
+}
+
+static int unit_manager_create(lua_State* L)
+{
+	LuaStack stack(L);
+
+	if (stack.num_args() == 1)
+		stack.push_unit(device()->unit_manager()->create(*stack.get_world(1)));
+	else
+		stack.push_unit(device()->unit_manager()->create());
+
+	return 1;
+}
+
+static int unit_manager_alive(lua_State* L)
+{
+	LuaStack stack(L);
+	stack.push_bool(device()->unit_manager()->alive(stack.get_unit(1)));
+	return 1;
+}
+
 void load_world(LuaEnvironment& env)
 {
-	env.load_module_function("World", "spawn_unit",         world_spawn_unit);
-	env.load_module_function("World", "destroy_unit",       world_destroy_unit);
-	env.load_module_function("World", "num_units",          world_num_units);
-	env.load_module_function("World", "units",              world_units);
-	env.load_module_function("World", "link_unit",          world_link_unit);
-	env.load_module_function("World", "unlink_unit",        world_unlink_unit);
-	env.load_module_function("World", "update_animations",  world_update_animations);
-	env.load_module_function("World", "update_scene",       world_update_scene);
-	env.load_module_function("World", "update",             world_update);
-	env.load_module_function("World", "play_sound",         world_play_sound);
-	env.load_module_function("World", "stop_sound",         world_stop_sound);
-	env.load_module_function("World", "link_sound",         world_link_sound);
-	env.load_module_function("World", "set_listener_pose",  world_set_listener_pose);
-	env.load_module_function("World", "set_sound_position", world_set_sound_position);
-	env.load_module_function("World", "set_sound_range",    world_set_sound_range);
-	env.load_module_function("World", "set_sound_volume",   world_set_sound_volume);
-	env.load_module_function("World", "create_window_gui",  world_create_window_gui);
-	env.load_module_function("World", "destroy_gui",        world_destroy_gui);
-	env.load_module_function("World", "create_debug_line",  world_create_debug_line);
-	env.load_module_function("World", "destroy_debug_line", world_destroy_debug_line);
-	env.load_module_function("World", "load_level",         world_load_level);
-	env.load_module_function("World", "physics_world",      world_physics_world);
-	env.load_module_function("World", "sound_world",        world_sound_world);
-	env.load_module_function("World", "__index",            "World");
-	env.load_module_function("World", "__tostring",         world_tostring);
+	env.load_module_function("World", "spawn_unit",                      world_spawn_unit);
+	env.load_module_function("World", "destroy_unit",                    world_destroy_unit);
+	env.load_module_function("World", "num_units",                       world_num_units);
+	env.load_module_function("World", "units",                           world_units);
+
+	env.load_module_function("World", "camera",                          world_camera);
+	env.load_module_function("World", "set_camera_projection_type",      camera_set_projection_type);
+	env.load_module_function("World", "camera_projection_type",          camera_projection_type);
+	env.load_module_function("World", "camera_fov",                      camera_fov);
+	env.load_module_function("World", "set_camera_fov",                  camera_set_fov);
+	env.load_module_function("World", "camera_aspect",                   camera_aspect);
+	env.load_module_function("World", "set_camera_aspect",               camera_set_aspect);
+	env.load_module_function("World", "camera_near_clip_distance",       camera_near_clip_distance);
+	env.load_module_function("World", "set_camera_near_clip_distance",   camera_set_near_clip_distance);
+	env.load_module_function("World", "camera_far_clip_distance",        camera_far_clip_distance);
+	env.load_module_function("World", "set_camera_far_clip_distance",    camera_set_far_clip_distance);
+	env.load_module_function("World", "set_camera_orthographic_metrics", camera_set_orthographic_metrics);
+	env.load_module_function("World", "set_camera_viewport_metrics",     camera_set_viewport_metrics);
+	env.load_module_function("World", "camera_screen_to_world",          camera_screen_to_world);
+	env.load_module_function("World", "camera_world_to_screen",          camera_world_to_screen);
+
+	env.load_module_function("World", "update_animations",               world_update_animations);
+	env.load_module_function("World", "update_scene",                    world_update_scene);
+	env.load_module_function("World", "update",                          world_update);
+	env.load_module_function("World", "play_sound",                      world_play_sound);
+	env.load_module_function("World", "stop_sound",                      world_stop_sound);
+	env.load_module_function("World", "link_sound",                      world_link_sound);
+	env.load_module_function("World", "set_listener_pose",               world_set_listener_pose);
+	env.load_module_function("World", "set_sound_position",              world_set_sound_position);
+	env.load_module_function("World", "set_sound_range",                 world_set_sound_range);
+	env.load_module_function("World", "set_sound_volume",                world_set_sound_volume);
+	env.load_module_function("World", "create_debug_line",               world_create_debug_line);
+	env.load_module_function("World", "destroy_debug_line",              world_destroy_debug_line);
+	env.load_module_function("World", "load_level",                      world_load_level);
+	env.load_module_function("World", "scene_graph",                     world_scene_graph);
+	env.load_module_function("World", "render_world",                    world_render_world);
+	env.load_module_function("World", "physics_world",                   world_physics_world);
+	env.load_module_function("World", "sound_world",                     world_sound_world);
+	env.load_module_function("World", "__index",                         "World");
+	env.load_module_function("World", "__tostring",                      world_tostring);
+
+	env.load_module_function("SceneGraph", "create",              scene_graph_create);
+	env.load_module_function("SceneGraph", "destroy",             scene_graph_destroy);
+	env.load_module_function("SceneGraph", "transform_instances", scene_graph_transform_instances);
+	env.load_module_function("SceneGraph", "local_position",      scene_graph_local_position);
+	env.load_module_function("SceneGraph", "local_rotation",      scene_graph_local_rotation);
+	env.load_module_function("SceneGraph", "local_scale",         scene_graph_local_scale);
+	env.load_module_function("SceneGraph", "local_pose",          scene_graph_local_pose);
+	env.load_module_function("SceneGraph", "world_position",      scene_graph_world_position);
+	env.load_module_function("SceneGraph", "world_rotation",      scene_graph_world_rotation);
+	env.load_module_function("SceneGraph", "world_pose",          scene_graph_world_pose);
+	env.load_module_function("SceneGraph", "set_local_position",  scene_graph_set_local_position);
+	env.load_module_function("SceneGraph", "set_local_rotation",  scene_graph_set_local_rotation);
+	env.load_module_function("SceneGraph", "set_local_scale",     scene_graph_set_local_scale);
+	env.load_module_function("SceneGraph", "set_local_pose",      scene_graph_set_local_pose);
+	env.load_module_function("SceneGraph", "link",                scene_graph_link);
+	env.load_module_function("SceneGraph", "unlink",              scene_graph_unlink);
+
+	env.load_module_function("UnitManager", "create", unit_manager_create);
+	env.load_module_function("UnitManager", "alive",  unit_manager_alive);
 }
 
 } // namespace crown

+ 162 - 70
src/physics/physics_types.h

@@ -5,20 +5,32 @@
 
 #pragma once
 
-#include "vector3.h"
+#include "math_types.h"
+#include "string_id.h"
 
 namespace crown
 {
 
-typedef Id ActorId;
-typedef Id ControllerId;
-typedef Id JointId;
-typedef Id RaycastId;
+struct ColliderInstance
+{
+	uint32_t i;
+};
+
+struct ActorInstance
+{
+	uint32_t i;
+};
+
+struct ControllerInstance
+{
+	uint32_t i;
+};
+
+struct JointInstance
+{
+	uint32_t i;
+};
 
-struct Actor;
-struct Controller;
-struct Joint;
-struct Raycast;
 class PhysicsWorld;
 
 struct ActorType
@@ -38,8 +50,9 @@ struct ShapeType
 		SPHERE,
 		CAPSULE,
 		BOX,
-		PLANE,
-		CONVEX_MESH,
+		CONVEX_HULL,
+		MESH,
+		HEIGHTFIELD,
 
 		COUNT
 	};
@@ -50,100 +63,179 @@ struct JointType
 	enum Enum
 	{
 		FIXED,
-		SPHERICAL,
-		REVOLUTE,
-		PRISMATIC,
-		DISTANCE,
+		HINGE,
+		SPRING,
 
 		COUNT
 	};
 };
 
-struct CollisionGroup
+struct ControllerDesc
+{
+	float height;                // Height of the capsule
+	float radius;                // Radius of the capsule
+	float slope_limit;           // The maximum slope which the character can walk up in radians.
+	float step_offset;           // Maximum height of an obstacle which the character can climb.
+	float contact_offset;        // Skin around the object within which contacts will be generated. Use it to avoid numerical precision issues.
+	StringId32 collision_filter; // Collision filter from global.physics_config
+};
+
+struct ActorFlags
 {
 	enum Enum
 	{
-		GROUP_0		= (1<<0), // Collisions disabled
-		GROUP_1		= (1<<1),
-		GROUP_2		= (1<<2),
-		GROUP_3		= (1<<3),
-		GROUP_4		= (1<<4),
-		GROUP_5		= (1<<5),
-		GROUP_6		= (1<<6),
-		GROUP_7		= (1<<7),
-		GROUP_8		= (1<<8),
-		GROUP_9		= (1<<9),
-		GROUP_10	= (1<<10),
-		GROUP_11	= (1<<11),
-		GROUP_12	= (1<<12),
-		GROUP_13	= (1<<13),
-		GROUP_14	= (1<<14),
-		GROUP_15	= (1<<15),
-		GROUP_16	= (1<<16),
-		GROUP_17	= (1<<17),
-		GROUP_18	= (1<<18),
-		GROUP_19	= (1<<19),
-		GROUP_20	= (1<<20),
-		GROUP_21	= (1<<21),
-		GROUP_22	= (1<<22),
-		GROUP_23	= (1<<23),
-		GROUP_24	= (1<<24),
-		GROUP_25	= (1<<25),
-		GROUP_26	= (1<<26),
-		GROUP_27	= (1<<27),
-		GROUP_28	= (1<<28),
-		GROUP_29	= (1<<29),
-		GROUP_30	= (1<<30),
-		GROUP_31	= (1<<31)
+		LOCK_TRANSLATION_X = (1 << 0),
+		LOCK_TRANSLATION_Y = (1 << 1),
+		LOCK_TRANSLATION_Z = (1 << 2),
+		LOCK_ROTATION_X    = (1 << 3),
+		LOCK_ROTATION_Y    = (1 << 4),
+		LOCK_ROTATION_Z    = (1 << 5)
 	};
 };
 
-struct CollisionType
+struct ActorResource
+{
+	StringId32 actor_class;      // Actor from global.physics
+	float mass;                  // Mass of the actor
+	uint32_t flags;              // ActorFlags
+	StringId32 collision_filter; // Collision filter from global.physics_config
+};
+
+struct SphereShape
+{
+	float radius;
+};
+
+struct CapsuleShape
+{
+	float radius;
+	float height;
+};
+
+struct BoxShape
+{
+	Vector3 half_size;
+};
+
+struct HeightfieldShape
+{
+	uint32_t width;
+	uint32_t length;
+	float height_scale;
+	float min_height;
+	float max_height;
+};
+
+struct ShapeDesc
+{
+	StringId32 shape_class;       // Shape class from global.physics_config
+	uint32_t type;                // ShapeType
+	StringId32 material;          // Material from global.physics_config
+	Matrix4x4 local_tm;           // In actor-space
+	SphereShape sphere;
+	CapsuleShape capsule;
+	BoxShape box;
+	HeightfieldShape heightfield;
+	// dynamic data               // Mesh, Heightfield
+};
+
+struct HingeJoint
+{
+	Vector3 axis;
+
+	bool use_motor;
+	float target_velocity;
+	float max_motor_impulse;
+
+	bool use_limits;
+	float lower_limit;
+	float upper_limit;
+	float bounciness;
+};
+
+struct JointDesc
+{
+	uint32_t type;    // JointType::Enum
+	Vector3 anchor_0;
+	Vector3 anchor_1;
+
+	bool breakable;
+	char _pad[3];
+	float break_force;
+
+	HingeJoint hinge;
+};
+
+struct CollisionGroup
 {
 	enum Enum
 	{
-		STATIC,
-		DYNAMIC,
-		BOTH
+		GROUP_0  = (1<<0), // Reserved
+		GROUP_1  = (1<<1),
+		GROUP_2  = (1<<2),
+		GROUP_3  = (1<<3),
+		GROUP_4  = (1<<4),
+		GROUP_5  = (1<<5),
+		GROUP_6  = (1<<6),
+		GROUP_7  = (1<<7),
+		GROUP_8  = (1<<8),
+		GROUP_9  = (1<<9),
+		GROUP_10 = (1<<10),
+		GROUP_11 = (1<<11),
+		GROUP_12 = (1<<12),
+		GROUP_13 = (1<<13),
+		GROUP_14 = (1<<14),
+		GROUP_15 = (1<<15),
+		GROUP_16 = (1<<16),
+		GROUP_17 = (1<<17),
+		GROUP_18 = (1<<18),
+		GROUP_19 = (1<<19),
+		GROUP_20 = (1<<20),
+		GROUP_21 = (1<<21),
+		GROUP_22 = (1<<22),
+		GROUP_23 = (1<<23),
+		GROUP_24 = (1<<24),
+		GROUP_25 = (1<<25),
+		GROUP_26 = (1<<26),
+		GROUP_27 = (1<<27),
+		GROUP_28 = (1<<28),
+		GROUP_29 = (1<<29),
+		GROUP_30 = (1<<30),
+		GROUP_31 = (1<<31)
 	};
 };
 
-struct CollisionMode
+struct RaycastMode
 {
 	enum Enum
 	{
 		CLOSEST,
-		ANY,
-		ALL
+		ALL,
+
+		COUNT
 	};
 };
 
-namespace physics_world
-{
-struct EventType
+struct RaycastHit
 {
-	enum Enum
-	{
-		COLLISION,
-		TRIGGER
-	};
+	ActorInstance actor;
+	Vector3 position;    // In world-space
+	Vector3 normal;      // In world-space
 };
 
-struct CollisionEvent
+struct PhysicsCollisionEvent
 {
 	enum Type { BEGIN_TOUCH, END_TOUCH } type;
-	Actor* actors[2];
+	ActorInstance actors[2];
 	Vector3 where;
 	Vector3 normal;
 };
 
-struct TriggerEvent
+struct PhysicsTriggerEvent
 {
 	enum Type { BEGIN_TOUCH, END_TOUCH } type;
-	Actor* trigger;
-	Actor* other;
+	ActorInstance trigger;
+	ActorInstance other;
 };
 
-} // namespace physics_world
-
 } // namespace crown

+ 151 - 77
src/physics/physics_world.h

@@ -5,40 +5,16 @@
 
 #pragma once
 
-#include "config.h"
-#include "id_array.h"
-#include "pool_allocator.h"
-#include "physics_types.h"
-#include "physics_callback.h"
 #include "event_stream.h"
-#include "physics_types.h"
+#include "graphics_types.h"
 #include "math_types.h"
-#include "world_types.h"
+#include "physics_types.h"
 #include "resource_types.h"
-#include "PxScene.h"
-#include "PxCooking.h"
-#include "PxDefaultCpuDispatcher.h"
-#include "PxControllerManager.h"
-
-using physx::PxControllerManager;
-using physx::PxScene;
-using physx::PxDefaultCpuDispatcher;
-using physx::PxActor;
-using physx::PxOverlapHit;
-using physx::PxOverlapBuffer;
-using physx::PxMaterial;
-using physx::PxShape;
-using physx::PxPhysics;
-using physx::PxCooking;
+#include "world_types.h"
 
 namespace crown
 {
 
-struct SceneGraph;
-class World;
-struct Unit;
-struct DebugLine;
-
 /// Manages physics objects in a World.
 ///
 /// @ingroup Physics
@@ -46,76 +22,174 @@ class PhysicsWorld
 {
 public:
 
-	PhysicsWorld(World& world);
-	~PhysicsWorld();
+	PhysicsWorld() {}
+	virtual ~PhysicsWorld() {}
 
-	ActorId create_actor(const ActorResource* ar, SceneGraph& sg, UnitId unit_id);
-	void destroy_actor(ActorId id);
+	virtual ColliderInstance create_collider(UnitId id, const ShapeDesc* sd) = 0;
+	virtual ColliderInstance first_collider(UnitId id) = 0;
+	virtual ColliderInstance next_collider(ColliderInstance i) = 0;
 
-	ControllerId create_controller(const ControllerResource* cr, SceneGraph& sg, UnitId id);
-	void destroy_controller(ControllerId id);
+	virtual ActorInstance create_actor(UnitId id, const ActorResource* ar, const Matrix4x4& tm) = 0;
+	virtual void destroy_actor(ActorInstance i) = 0;
+	virtual ActorInstance actor(UnitId id) = 0;
 
-	JointId create_joint(const JointResource* jr, const Actor& actor_0, const Actor& actor_1);
-	void destroy_joint(JointId id);
+	/// Returns the world position of the actor.
+	virtual Vector3 actor_world_position(ActorInstance i) const = 0;
 
-	RaycastId create_raycast(CollisionMode::Enum mode, CollisionType::Enum filter);
-	void destroy_raycast(RaycastId id);
+	/// Returns the world rotation of the actor.
+	virtual Quaternion actor_world_rotation(ActorInstance i) const = 0;
 
-	/// Returns the gravity.
-	Vector3 gravity() const;
+	/// Returns the world pose of the actor.
+	virtual Matrix4x4 actor_world_pose(ActorInstance i) const = 0;
 
-	/// Sets the gravity.
-	void set_gravity(const Vector3& g);
+	/// Teleports the actor to the given world position.
+	virtual void teleport_actor_world_position(ActorInstance i, const Vector3& p) = 0;
 
-	/// Finds all actors in the physics world that are in a particular shape (supported: spheres, capsules and boxes)
-	void overlap_test(CollisionType::Enum filter, ShapeType::Enum type,
-						const Vector3& pos, const Quaternion& rot, const Vector3& size, Array<Actor*>& actors);
+	/// Teleports the actor to the given world rotation.
+	virtual void teleport_actor_world_rotation(ActorInstance i, const Quaternion& r) = 0;
 
-	/// Updates the physics simulation.
-	void update(float dt);
+	/// Teleports the actor to the given world pose.
+	virtual void teleport_actor_world_pose(ActorInstance i, const Matrix4x4& m) = 0;
 
-	/// Draws debug lines.
-	void draw_debug(DebugLine& lines);
+	/// Returns the center of mass of the actor.
+	virtual Vector3 actor_center_of_mass(ActorInstance i) const = 0;
 
-	Actor* get_actor(ActorId id);
-	Controller* get_controller(ControllerId id);
-	Raycast* get_raycast(RaycastId id);
+	/// Enables gravity for the actor.
+	virtual void enable_actor_gravity(ActorInstance i) = 0;
 
-	World& world() { return m_world; }
-	EventStream& events() { return m_events; }
+	/// Disables gravity for the actor.
+	virtual void disable_actor_gravity(ActorInstance i) = 0;
 
-public:
+	/// Enables collision detection for the actor.
+	virtual void enable_actor_collision(ActorInstance i) = 0;
+
+	/// Disables collision detection for the actor.
+	virtual void disable_actor_collision(ActorInstance i) = 0;
+
+	/// Sets the collision filter of the actor.
+	virtual void set_actor_collision_filter(ActorInstance i, StringId32 filter) = 0;
+
+	/// Sets whether the actor is kinematic or not.
+	/// @note This call has no effect on static actors.
+	virtual void set_actor_kinematic(ActorInstance i, bool kinematic) = 0;
+
+	/// Moves the actor to @a pos
+	/// @note This call only affects nonkinematic actors.
+	virtual void move_actor(ActorInstance i, const Vector3& pos) = 0;
+
+	/// Returns whether the actor is static.
+	virtual bool is_static(ActorInstance i) const = 0;
+
+	/// Returns whether the actor is dynamic.
+	virtual bool is_dynamic(ActorInstance i) const = 0;
+
+	/// Returns whether the actor is kinematic (keyframed).
+	virtual bool is_kinematic(ActorInstance i) const = 0;
+
+	/// Returns whether the actor is nonkinematic (i.e. dynamic and not kinematic).
+	virtual bool is_nonkinematic(ActorInstance i) const = 0;
+
+	/// Returns the linear damping of the actor.
+	virtual float actor_linear_damping(ActorInstance i) const = 0;
+
+	/// Sets the linear damping of the actor.
+	virtual void set_actor_linear_damping(ActorInstance i, float rate) = 0;
+
+	/// Returns the angular damping of the actor.
+	virtual float actor_angular_damping(ActorInstance i) const = 0;
+
+	/// Sets the angular damping of the actor.
+	virtual void set_actor_angular_damping(ActorInstance i, float rate) = 0;
+
+	/// Returns the linear velocity of the actor.
+	virtual Vector3 actor_linear_velocity(ActorInstance i) const = 0;
 
-	PxPhysics* physx_physics();
-	PxCooking* physx_cooking();
-	PxScene* physx_scene();
-	const PhysicsConfigResource* resource() { return m_resource; }
+	/// Sets the linear velocity of the actor.
+	/// @note This call only affects nonkinematic actors.
+	virtual void set_actor_linear_velocity(ActorInstance i, const Vector3& vel) = 0;
 
-private:
+	/// Returns the angular velocity of the actor.
+	virtual Vector3 actor_angular_velocity(ActorInstance i) const = 0;
 
-	World& m_world;
-	PxControllerManager* m_controller_manager;
-	PxScene* m_scene;
-	PxDefaultCpuDispatcher* m_cpu_dispatcher;
+	/// Sets the angular velocity of the actor.
+	/// @note This call only affects nonkinematic actors.
+	virtual void set_actor_angular_velocity(ActorInstance i, const Vector3& vel) = 0;
 
-	PxOverlapHit m_hits[64]; // hardcoded
-	PxOverlapBuffer m_buffer;
+	/// Adds a linear impulse (acting along the center of mass) to the actor.
+	/// @note This call only affects nonkinematic actors.
+	virtual void add_actor_impulse(ActorInstance i, const Vector3& impulse) = 0;
 
-	PoolAllocator m_actors_pool;
-	PoolAllocator m_controllers_pool;
-	PoolAllocator m_joints_pool;
-	PoolAllocator m_raycasts_pool;
+	/// Adds a linear impulse (acting along the world position @a pos) to the actor.
+	/// @note This call only affects nonkinematic actors.
+	virtual void add_actor_impulse_at(ActorInstance i, const Vector3& impulse, const Vector3& pos) = 0;
 
-	IdArray<CE_MAX_ACTORS, Actor*>	m_actors;
-	IdArray<CE_MAX_CONTROLLERS, Controller*> m_controllers;
-	IdArray<CE_MAX_JOINTS, Joint*> m_joints;
-	IdArray<CE_MAX_RAYCASTS, Raycast*> m_raycasts;
+	/// Adds a torque impulse to the actor.
+	virtual void add_actor_torque_impulse(ActorInstance i, const Vector3& imp) = 0;
+
+	/// Pushes the actor as if it was hit by a point object with the given @a mass
+	/// travelling at the given @a velocity.
+	/// @note This call only affects nonkinematic actors.
+	virtual void push_actor(ActorInstance i, const Vector3& vel, float mass) = 0;
+
+	/// Like push() but applies the force at the world position @a pos.
+	/// @note This call only affects nonkinematic actors.
+	virtual void push_actor_at(ActorInstance i, const Vector3& vel, float mass, const Vector3& pos) = 0;
+
+	/// Returns whether the actor is sleeping.
+	virtual bool is_sleeping(ActorInstance i) = 0;
+
+	/// Wakes the actor up.
+	virtual void wake_up(ActorInstance i) = 0;
+
+	virtual ControllerInstance create_controller(UnitId id, const ControllerDesc& cd, const Matrix4x4& tm) = 0;
+	virtual void destroy_controller(ControllerInstance id) = 0;
+	virtual ControllerInstance controller(UnitId id) = 0;
+
+	/// Returns the position of the controller.
+	virtual Vector3 position(ControllerInstance i) const = 0;
+
+	/// Moves the controller to @a pos.
+	virtual void move_controller(ControllerInstance i, const Vector3& pos) = 0;
+
+	/// Sets the contoller height.
+	virtual void set_height(ControllerInstance i, float height) = 0;
+
+	/// Returns whether the contoller collides upwards.
+	virtual bool collides_up(ControllerInstance i) const = 0;
+
+	/// Returns whether the controller collides downwards.
+	virtual bool collides_down(ControllerInstance i) const = 0;
+
+	/// Returns whether the controller collides sidewards.
+	virtual bool collides_sides(ControllerInstance i) const = 0;
+
+	/// Creates joint
+	virtual JointInstance create_joint(ActorInstance a0, ActorInstance a1, const JointDesc& jd) = 0;
+	virtual void destroy_joint(JointInstance i) = 0;
+
+	/// Performs a raycast.
+	virtual void raycast(const Vector3& from, const Vector3& dir, float len, RaycastMode::Enum mode, Array<RaycastHit>& hits) = 0;
+
+	/// Returns the gravity.
+	virtual Vector3 gravity() const = 0;
+
+	/// Sets the gravity.
+	virtual void set_gravity(const Vector3& g) = 0;
+
+	virtual void update_actor_world_poses(const UnitId* begin, const UnitId* end, const Matrix4x4* begin_world) = 0;
+
+	/// Updates the physics simulation.
+	virtual void update(float dt) = 0;
+
+	virtual EventStream& events() = 0;
+
+	/// Draws debug lines.
+	virtual void draw_debug() = 0;
 
-	// Events management
-	EventStream m_events;
-	PhysicsSimulationCallback m_callback;
+	virtual void enable_debug_drawing(bool enable) = 0;
 
-	const PhysicsConfigResource* m_resource;
+	static PhysicsWorld* create(Allocator& a, ResourceManager& rm, UnitManager& um, DebugLine& dl);
+	static void destroy(Allocator& a, PhysicsWorld* pw);
 };
 
 } // namespace crown

+ 988 - 0
src/physics/physics_world_bullet.cpp

@@ -0,0 +1,988 @@
+/*
+ * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#include "config.h"
+
+#if CROWN_PHYSICS_BULLET
+
+#include "array.h"
+#include "color4.h"
+#include "debug_line.h"
+#include "hash.h"
+#include "log.h"
+#include "matrix4x4.h"
+#include "physics.h"
+#include "physics_resource.h"
+#include "physics_world.h"
+#include "proxy_allocator.h"
+#include "quaternion.h"
+#include "resource_manager.h"
+#include "unit_manager.h"
+#include "vector3.h"
+#include <btBoxShape.h>
+#include <btCapsuleShape.h>
+#include <btCollisionObject.h>
+#include <btCompoundShape.h>
+#include <btConvexHullShape.h>
+#include <btConvexTriangleMeshShape.h>
+#include <btDbvtBroadphase.h>
+#include <btDefaultCollisionConfiguration.h>
+#include <btDefaultMotionState.h>
+#include <btDiscreteDynamicsWorld.h>
+#include <btFixedConstraint.h>
+#include <btGhostObject.h>
+#include <btHeightfieldTerrainShape.h>
+#include <btHingeConstraint.h>
+#include <btIDebugDraw.h>
+#include <btKinematicCharacterController.h>
+#include <btPoint2PointConstraint.h>
+#include <btRigidBody.h>
+#include <btSequentialImpulseConstraintSolver.h>
+#include <btSliderConstraint.h>
+#include <btSphereShape.h>
+#include <btStaticPlaneShape.h>
+#include <btTriangleMesh.h>
+
+namespace crown
+{
+namespace physics_globals
+{
+	static btDefaultCollisionConfiguration* _bt_configuration;
+	static btCollisionDispatcher* _bt_dispatcher;
+	static btBroadphaseInterface* _bt_interface;
+	static btSequentialImpulseConstraintSolver* _bt_solver;
+
+	void init()
+	{
+		_bt_configuration = CE_NEW(default_allocator(), btDefaultCollisionConfiguration);
+		_bt_dispatcher = CE_NEW(default_allocator(), btCollisionDispatcher)(_bt_configuration);
+		_bt_interface = CE_NEW(default_allocator(), btDbvtBroadphase);
+		_bt_solver = CE_NEW(default_allocator(), btSequentialImpulseConstraintSolver);
+	}
+
+	void shutdown()
+	{
+		CE_DELETE(default_allocator(), _bt_solver);
+		CE_DELETE(default_allocator(), _bt_interface);
+		CE_DELETE(default_allocator(), _bt_dispatcher);
+		CE_DELETE(default_allocator(), _bt_configuration);
+	}
+} // namespace physics_globals
+
+static btVector3 to_btVector3(const Vector3& v)
+{
+	return btVector3(v.x, v.y, v.z);
+}
+
+static btQuaternion to_btQuaternion(const Quaternion& q)
+{
+	return btQuaternion(q.x, q.y, q.z, q.w);
+}
+
+static Vector3 to_vector3(const btVector3& v)
+{
+	return vector3(v.x(), v.y(), v.z());
+}
+
+static Quaternion to_quaternion(const btQuaternion& q)
+{
+	return quaternion(q.x(), q.y(), q.z(), q.w());
+}
+
+class MyDebugDrawer : public btIDebugDraw
+{
+public:
+
+	MyDebugDrawer(DebugLine& dl)
+		: _lines(&dl)
+	{
+	}
+
+	void drawLine(const btVector3& from, const btVector3& to, const btVector3& /*color*/)
+	{
+		const Vector3 start = to_vector3(from);
+		const Vector3 end = to_vector3(to);
+		_lines->add_line(start, end, COLOR4_DARKORANGE);
+	}
+
+	void drawContactPoint(const btVector3& pointOnB, const btVector3& /*normalOnB*/, btScalar /*distance*/, int /*lifeTime*/, const btVector3& /*color*/)
+	{
+		const Vector3 from = to_vector3(pointOnB);
+		_lines->add_sphere(from, 0.1f, COLOR4_WHITE);
+	}
+
+	void reportErrorWarning(const char* warningString)
+	{
+		CE_LOGW(warningString);
+	}
+
+	void draw3dText(const btVector3& /*location*/, const char* /*textString*/)
+	{
+	}
+
+	void setDebugMode(int /*debugMode*/)
+	{
+	}
+
+	int getDebugMode() const
+	{
+		return DBG_DrawWireframe
+			| DBG_DrawConstraints
+			| DBG_DrawConstraintLimits
+			| DBG_FastWireframe
+			;
+	}
+
+public:
+
+	DebugLine* _lines;
+};
+
+class MyFilterCallback : public btOverlapFilterCallback
+{
+	bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1) const
+	{
+		bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
+		collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
+
+		return collides;
+	}
+};
+
+class BulletWorld : public PhysicsWorld
+{
+public:
+
+	BulletWorld(Allocator& a, ResourceManager& rm, UnitManager& um, DebugLine& dl)
+		: _allocator(&a)
+		, _collider_map(a)
+		, _actor_map(a)
+		, _controller_map(a)
+		, _collider(a)
+		, _actor(a)
+		, _controller(a)
+		, _joints(a)
+		, _scene(NULL)
+		, _debug_drawer(dl)
+		, _events(a)
+		, _debug_drawing(false)
+	{
+		_scene = CE_NEW(*_allocator, btDiscreteDynamicsWorld)(physics_globals::_bt_dispatcher
+			, physics_globals::_bt_interface
+			, physics_globals::_bt_solver
+			, physics_globals::_bt_configuration
+			);
+
+		_scene->getCollisionWorld()->setDebugDrawer(&_debug_drawer);
+		_scene->setInternalTickCallback(tick_cb, this);
+		_scene->getPairCache()->setOverlapFilterCallback(&_filter_cb);
+
+		_config_resource = (const PhysicsConfigResource*)rm.get(PHYSICS_CONFIG_TYPE, StringId64("global"));
+
+		um.register_destroy_function(BulletWorld::unit_destroyed_callback, this);
+	}
+
+	~BulletWorld()
+	{
+		for (uint32_t i = 0; i < array::size(_actor); ++i)
+		{
+			btRigidBody* rb = _actor[i].actor;
+
+			_scene->removeRigidBody(rb);
+			rb->getMotionState()->~btMotionState();
+			rb->getCollisionShape()->~btCollisionShape();
+			CE_DELETE(*_allocator, rb);
+		}
+
+		for (uint32_t i = 0; i < array::size(_collider); ++i)
+		{
+			CE_DELETE(*_allocator, _collider[i].shape);
+		}
+
+		CE_DELETE(*_allocator, _scene);
+	}
+
+	ColliderInstance create_collider(UnitId id, const ShapeDesc* sd)
+	{
+		btCollisionShape* child_shape = NULL;
+
+		switch(sd->type)
+		{
+			case ShapeType::SPHERE:
+				child_shape = CE_NEW(*_allocator, btSphereShape)(sd->sphere.radius);
+				break;
+			case ShapeType::CAPSULE:
+				child_shape = CE_NEW(*_allocator, btCapsuleShape)(sd->capsule.radius, sd->capsule.height);
+				break;
+			case ShapeType::BOX:
+				child_shape = CE_NEW(*_allocator, btBoxShape)(to_btVector3(sd->box.half_size));
+				break;
+			case ShapeType::CONVEX_HULL:
+			{
+				const uint32_t num = *(const uint32_t*)&sd[1];
+				const Vector3* points = (const Vector3*)((const char*)(&sd[1]) + sizeof(num));
+
+				btConvexHullShape* convex = CE_NEW(*_allocator, btConvexHullShape);
+				for (uint32_t i = 0; i < num; ++i)
+				{
+					Vector3 vec = points[i];
+					btVector3 vec_bt(vec.x, vec.y, vec.z);
+					convex->addPoint(vec_bt);
+				}
+				child_shape = convex;
+				break;
+			}
+			case ShapeType::MESH:
+			case ShapeType::HEIGHTFIELD:
+			{
+				CE_FATAL("Not implemented yet");
+				break;
+			}
+			default:
+			{
+				CE_FATAL("Bad shape");
+				break;
+			}
+		}
+
+		const uint32_t last = array::size(_collider);
+
+		ColliderInstanceData cid;
+		cid.unit   = id;
+		cid.shape  = child_shape;
+		cid.next.i = UINT32_MAX;
+
+		ColliderInstance ci = first_collider(id);
+		while (is_valid(ci) && is_valid(next_collider(ci)))
+			ci = next_collider(ci);
+
+		if (is_valid(ci))
+			_collider[ci.i].next.i = last;
+		else
+			hash::set(_collider_map, id.encode(), last);
+
+		array::push_back(_collider, cid);
+		return make_collider_instance(last);
+	}
+
+	ColliderInstance first_collider(UnitId id)
+	{
+		return make_collider_instance(hash::get(_collider_map, id.encode(), UINT32_MAX));
+	}
+
+	ColliderInstance next_collider(ColliderInstance i)
+	{
+		return _collider[i.i].next;
+	}
+
+	ActorInstance create_actor(UnitId id, const ActorResource* ar, const Matrix4x4& tm)
+	{
+		const PhysicsConfigActor* actor_class = physics_config_resource::actor(_config_resource, ar->actor_class);
+
+		const uint32_t blob_size = sizeof(btRigidBody)
+			+ sizeof(btDefaultMotionState)
+			+ sizeof(btCompoundShape
+			);
+
+		void* data = _allocator->allocate(blob_size);
+		btRigidBody* actor = (btRigidBody*)data;
+		btDefaultMotionState* ms = (btDefaultMotionState*)(actor + 1);
+		btCompoundShape* shape = (btCompoundShape*)(ms + 1);
+
+		// Create motion state
+		const Quaternion r = rotation(tm);
+		const Vector3 p = translation(tm);
+		const btQuaternion bt_r = to_btQuaternion(r);
+		const btVector3 bt_p = to_btVector3(p);
+		ms = new (ms) btDefaultMotionState(btTransform(bt_r, bt_p));
+
+		// Create compound shape
+		shape = new (shape) btCompoundShape(true);
+
+		ColliderInstance ci = first_collider(id);
+		while (is_valid(ci))
+		{
+			shape->addChildShape(btTransform::getIdentity(), _collider[ci.i].shape);
+			ci = next_collider(ci);
+		}
+
+		const float mass = (actor_class->flags & PhysicsConfigActor::DYNAMIC) ? ar->mass : 0.0f;
+
+		// If dynamic, calculate inertia
+		btVector3 inertia;
+		if (mass != 0.0f) // Actor is dynamic iff mass != 0
+			shape->calculateLocalInertia(mass, inertia);
+
+		btRigidBody::btRigidBodyConstructionInfo rbinfo(mass, ms, shape, inertia);
+		rbinfo.m_linearDamping = actor_class->linear_damping;
+		rbinfo.m_angularDamping = actor_class->angular_damping;
+		rbinfo.m_restitution = 0.81f; // FIXME
+		rbinfo.m_friction = 0.8f; // FIXME
+		rbinfo.m_linearSleepingThreshold = 0.01f; // FIXME
+		rbinfo.m_angularSleepingThreshold = 0.01f; // FIXME
+
+		// Create rigid body
+		const bool is_kinematic = actor_class->flags & PhysicsConfigActor::KINEMATIC;
+		const bool is_dynamic   = actor_class->flags & PhysicsConfigActor::DYNAMIC;
+		const bool is_static    = !is_kinematic && !is_dynamic;
+
+		actor = new (actor) btRigidBody(rbinfo);
+
+		int cflags = actor->getCollisionFlags();
+		cflags |= is_kinematic ? btCollisionObject::CF_KINEMATIC_OBJECT : 0;
+		cflags |= is_static ? btCollisionObject::CF_STATIC_OBJECT : 0;
+		actor->setCollisionFlags(cflags);
+
+		actor->setLinearFactor(btVector3(
+			!(ar->flags & ActorFlags::LOCK_TRANSLATION_X) ? 1.0f : 0.0f,
+			!(ar->flags & ActorFlags::LOCK_TRANSLATION_Y) ? 1.0f : 0.0f,
+			!(ar->flags & ActorFlags::LOCK_TRANSLATION_Z) ? 1.0f : 0.0f)
+		);
+		actor->setAngularFactor(btVector3(
+			!(ar->flags & ActorFlags::LOCK_ROTATION_X) ? 1.0f : 0.0f,
+			!(ar->flags & ActorFlags::LOCK_ROTATION_Y) ? 1.0f : 0.0f,
+			!(ar->flags & ActorFlags::LOCK_ROTATION_Z) ? 1.0f : 0.0f)
+		);
+
+		const uint32_t last = array::size(_actor);
+
+		actor->setUserPointer((void*)(uintptr_t)last);
+
+		// Set collision filters
+		const uint32_t me = physics_config_resource::filter(_config_resource, ar->collision_filter)->me;
+		const uint32_t mask = physics_config_resource::filter(_config_resource, ar->collision_filter)->mask;
+
+		_scene->addRigidBody(actor, me, mask);
+
+		ActorInstanceData aid;
+		aid.unit  = id;
+		aid.actor = actor;
+
+		array::push_back(_actor, aid);
+		hash::set(_actor_map, id.encode(), last);
+
+		return make_actor_instance(last);
+	}
+
+	void destroy_actor(ActorInstance i)
+	{
+		const uint32_t last = array::size(_actor) - 1;
+		const UnitId u      = _actor[i.i].unit;
+		const UnitId last_u = _actor[last].unit;
+
+		_scene->removeRigidBody(_actor[i.i].actor);
+
+		_actor[i.i] = _actor[last];
+
+		array::pop_back(_actor);
+
+		hash::set(_actor_map, last_u.encode(), i.i);
+		hash::remove(_actor_map, u.encode());
+	}
+
+	ActorInstance actor(UnitId id)
+	{
+		return make_actor_instance(hash::get(_actor_map, id.encode(), UINT32_MAX));
+	}
+
+	Vector3 actor_world_position(ActorInstance i) const
+	{
+		btTransform pose;
+		_actor[i.i].actor->getMotionState()->getWorldTransform(pose);
+		const btVector3 p = pose.getOrigin();
+		return to_vector3(p);
+	}
+
+	Quaternion actor_world_rotation(ActorInstance i) const
+	{
+		btTransform pose;
+		_actor[i.i].actor->getMotionState()->getWorldTransform(pose);
+		return to_quaternion(pose.getRotation());
+	}
+
+	Matrix4x4 actor_world_pose(ActorInstance i) const
+	{
+		btTransform pose;
+		_actor[i.i].actor->getMotionState()->getWorldTransform(pose);
+		const btQuaternion r = pose.getRotation();
+		const btVector3 p = pose.getOrigin();
+		return matrix4x4(to_quaternion(r), to_vector3(p));
+	}
+
+	void teleport_actor_world_position(ActorInstance i, const Vector3& p)
+	{
+		btTransform pose = _actor[i.i].actor->getCenterOfMassTransform();
+		pose.setOrigin(to_btVector3(p));
+		_actor[i.i].actor->setCenterOfMassTransform(pose);
+	}
+
+	void teleport_actor_world_rotation(ActorInstance i, const Quaternion& r)
+	{
+		btTransform pose = _actor[i.i].actor->getCenterOfMassTransform();
+		pose.setRotation(to_btQuaternion(r));
+		_actor[i.i].actor->setCenterOfMassTransform(pose);
+	}
+
+	void teleport_actor_world_pose(ActorInstance i, const Matrix4x4& m)
+	{
+		const Quaternion rot = rotation(m);
+		const Vector3 pos = translation(m);
+
+		btTransform pose = _actor[i.i].actor->getCenterOfMassTransform();
+		pose.setRotation(to_btQuaternion(rot));
+		pose.setOrigin(to_btVector3(pos));
+		_actor[i.i].actor->setCenterOfMassTransform(pose);
+	}
+
+	Vector3 actor_center_of_mass(ActorInstance i) const
+	{
+		if (is_static(i))
+			return VECTOR3_ZERO;
+
+		const btVector3 c = _actor[i.i].actor->getCenterOfMassTransform().getOrigin();
+		return to_vector3(c);
+	}
+
+	void enable_actor_gravity(ActorInstance i)
+	{
+		_actor[i.i].actor->setGravity(_scene->getGravity());
+	}
+
+	void disable_actor_gravity(ActorInstance i)
+	{
+		_actor[i.i].actor->setGravity(btVector3(0.0f, 0.0f, 0.0f));
+	}
+
+	void enable_actor_collision(ActorInstance /*i*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	void disable_actor_collision(ActorInstance /*i*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	void set_actor_collision_filter(ActorInstance /*i*/, StringId32 /*filter*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	void set_actor_kinematic(ActorInstance i, bool kinematic)
+	{
+		if (kinematic)
+			_actor[i.i].actor->setCollisionFlags(_actor[i.i].actor->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
+	}
+
+	void move_actor(ActorInstance i, const Vector3& pos)
+	{
+		if (!is_kinematic(i))
+			return;
+
+		_actor[i.i].actor->setLinearVelocity(to_btVector3(pos));
+	}
+
+	bool is_static(ActorInstance i) const
+	{
+		return _actor[i.i].actor->getCollisionFlags() & btCollisionObject::CF_STATIC_OBJECT;
+	}
+
+	bool is_dynamic(ActorInstance i) const
+	{
+		const int flags = _actor[i.i].actor->getCollisionFlags();
+		return !(flags & btCollisionObject::CF_STATIC_OBJECT)
+			&& !(flags & btCollisionObject::CF_KINEMATIC_OBJECT)
+			;
+	}
+
+	bool is_kinematic(ActorInstance i) const
+	{
+		return _actor[i.i].actor->getCollisionFlags() & btCollisionObject::CF_KINEMATIC_OBJECT;
+	}
+
+	bool is_nonkinematic(ActorInstance i) const
+	{
+		return is_dynamic(i) && !is_kinematic(i);
+	}
+
+	float actor_linear_damping(ActorInstance i) const
+	{
+		return _actor[i.i].actor->getLinearDamping();
+	}
+
+	void set_actor_linear_damping(ActorInstance i, float rate)
+	{
+		_actor[i.i].actor->setDamping(rate, _actor[i.i].actor->getAngularDamping());
+	}
+
+	float actor_angular_damping(ActorInstance i) const
+	{
+		return _actor[i.i].actor->getAngularDamping();
+	}
+
+	void set_actor_angular_damping(ActorInstance i, float rate)
+	{
+		_actor[i.i].actor->setDamping(_actor[i.i].actor->getLinearDamping(), rate);
+	}
+
+	Vector3 actor_linear_velocity(ActorInstance i) const
+	{
+		btVector3 v = _actor[i.i].actor->getLinearVelocity();
+		return to_vector3(v);
+	}
+
+	void set_actor_linear_velocity(ActorInstance i, const Vector3& vel)
+	{
+		_actor[i.i].actor->setLinearVelocity(to_btVector3(vel));
+	}
+
+	Vector3 actor_angular_velocity(ActorInstance i) const
+	{
+		btVector3 v = _actor[i.i].actor->getAngularVelocity();
+		return to_vector3(v);
+	}
+
+	void set_actor_angular_velocity(ActorInstance i, const Vector3& vel)
+	{
+		_actor[i.i].actor->setAngularVelocity(to_btVector3(vel));
+	}
+
+	void add_actor_impulse(ActorInstance i, const Vector3& impulse)
+	{
+		_actor[i.i].actor->activate();
+		_actor[i.i].actor->applyCentralImpulse(to_btVector3(impulse));
+	}
+
+	void add_actor_impulse_at(ActorInstance i, const Vector3& impulse, const Vector3& pos)
+	{
+		_actor[i.i].actor->activate();
+		_actor[i.i].actor->applyImpulse(to_btVector3(impulse), to_btVector3(pos));
+	}
+
+	void add_actor_torque_impulse(ActorInstance i, const Vector3& imp)
+	{
+		_actor[i.i].actor->applyTorqueImpulse(to_btVector3(imp));
+	}
+
+	void push_actor(ActorInstance i, const Vector3& vel, float mass)
+	{
+		const Vector3 f = vel * mass;
+		_actor[i.i].actor->applyCentralForce(to_btVector3(f));
+	}
+
+	void push_actor_at(ActorInstance i, const Vector3& vel, float mass, const Vector3& pos)
+	{
+		const Vector3 f = vel * mass;
+		_actor[i.i].actor->applyForce(to_btVector3(f), to_btVector3(pos));
+	}
+
+	bool is_sleeping(ActorInstance i)
+	{
+		return !_actor[i.i].actor->isActive();
+	}
+
+	void wake_up(ActorInstance i)
+	{
+		_actor[i.i].actor->activate(true);
+	}
+
+	ControllerInstance create_controller(UnitId /*id*/,	const ControllerDesc& /*cd*/, const Matrix4x4& /*tm*/)
+	{
+		CE_FATAL("Not implemented yet");
+		return make_controller_instance(UINT32_MAX);
+	}
+
+	void destroy_controller(ControllerInstance /*i*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	ControllerInstance controller(UnitId id)
+	{
+		return make_controller_instance(hash::get(_controller_map, id.encode(), UINT32_MAX));
+	}
+
+	void move_controller(ControllerInstance i, const Vector3& dir)
+	{
+		_controller[i.i].contr->setWalkDirection(to_btVector3(dir));
+	}
+
+	void set_height(ControllerInstance /*i*/, float /*height*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	Vector3 position(ControllerInstance /*i*/) const
+	{
+		CE_FATAL("Not implemented yet");
+		return VECTOR3_ZERO;
+	}
+
+	bool collides_up(ControllerInstance /*i*/) const
+	{
+		CE_FATAL("Not implemented yet");
+		return false;
+	}
+
+	bool collides_down(ControllerInstance /*i*/) const
+	{
+		CE_FATAL("Not implemented yet");
+		return false;
+	}
+
+	bool collides_sides(ControllerInstance /*i*/) const
+	{
+		CE_FATAL("Not implemented yet");
+		return false;
+	}
+
+	JointInstance create_joint(ActorInstance a0, ActorInstance a1, const JointDesc& jd)
+	{
+		const btVector3 anchor_0 = to_btVector3(jd.anchor_0);
+		const btVector3 anchor_1 = to_btVector3(jd.anchor_1);
+		btRigidBody* actor_0 = _actor[a0.i].actor;
+		btRigidBody* actor_1 = is_valid(a1) ? _actor[a1.i].actor : NULL;
+
+		btTypedConstraint* joint = NULL;
+		switch(jd.type)
+		{
+			case JointType::FIXED:
+			{
+				const btTransform frame_0 = btTransform(btQuaternion::getIdentity(), anchor_0);
+				const btTransform frame_1 = btTransform(btQuaternion::getIdentity(), anchor_1);
+	 			joint = CE_NEW(*_allocator, btFixedConstraint)(*actor_0
+	 				, *actor_1
+	 				, frame_0
+	 				, frame_1
+	 				);
+				break;
+			}
+			case JointType::SPRING:
+			{
+				joint = CE_NEW(*_allocator, btPoint2PointConstraint)(*actor_0
+					, *actor_1
+					, anchor_0
+					, anchor_1
+					);
+				break;
+			}
+			case JointType::HINGE:
+			{
+				btHingeConstraint* hinge = CE_NEW(*_allocator, btHingeConstraint)(*actor_0
+					, *actor_1
+					, anchor_0
+					, anchor_1
+					, to_btVector3(jd.hinge.axis)
+					, to_btVector3(jd.hinge.axis)
+					);
+
+				hinge->enableAngularMotor(jd.hinge.use_motor
+					, jd.hinge.target_velocity
+					, jd.hinge.max_motor_impulse
+					);
+
+				hinge->setLimit(jd.hinge.lower_limit
+					, jd.hinge.upper_limit
+					, jd.hinge.bounciness
+					);
+
+				joint = hinge;
+				break;
+			}
+			default:
+			{
+				CE_FATAL("Bad joint type");
+				break;
+			}
+		}
+
+		joint->setBreakingImpulseThreshold(jd.break_force);
+		_scene->addConstraint(joint);
+
+		return make_joint_instance(UINT32_MAX);
+	}
+
+	void destroy_joint(JointInstance /*i*/)
+	{
+		CE_FATAL("Not implemented yet");
+	}
+
+	void raycast(const Vector3& from, const Vector3& dir, float len, RaycastMode::Enum mode, Array<RaycastHit>& hits)
+	{
+		const btVector3 start = to_btVector3(from);
+		const btVector3 end = to_btVector3(from + dir*len);
+
+		switch (mode)
+		{
+			case RaycastMode::CLOSEST:
+			{
+				btCollisionWorld::ClosestRayResultCallback cb(start, end);
+				_scene->rayTest(start, end, cb);
+
+				if (cb.hasHit())
+				{
+					RaycastHit hit;
+					hit.position = to_vector3(cb.m_hitPointWorld);
+					hit.normal = to_vector3(cb.m_hitNormalWorld);
+					hit.actor.i = (uint32_t)(uintptr_t)btRigidBody::upcast(cb.m_collisionObject)->getUserPointer();
+					array::push_back(hits, hit);
+				}
+
+				break;
+			}
+			case RaycastMode::ALL:
+			{
+				btCollisionWorld::AllHitsRayResultCallback cb(start, end);
+				_scene->rayTest(start, end, cb);
+
+				if (cb.hasHit())
+				{
+					const int num = cb.m_hitPointWorld.size();
+					array::resize(hits, num);
+
+					for (int i = 0; i < num; ++i)
+					{
+						RaycastHit hit;
+						hit.position = to_vector3(cb.m_hitPointWorld[i]);
+						hit.normal = to_vector3(cb.m_hitNormalWorld[i]);
+						hit.actor.i = (uint32_t)(uintptr_t)btRigidBody::upcast(cb.m_collisionObjects[i])->getUserPointer();
+						hits[i] = hit;
+					}
+				}
+
+				break;
+			}
+			default:
+			{
+				CE_FATAL("Bad raycast mode");
+				break;
+			}
+		}
+	}
+
+	Vector3 gravity() const
+	{
+		return to_vector3(_scene->getGravity());
+	}
+
+	void set_gravity(const Vector3& g)
+	{
+		_scene->setGravity(to_btVector3(g));
+	}
+
+	void update_actor_world_poses(const UnitId* begin, const UnitId* end, const Matrix4x4* begin_world)
+	{
+		for (; begin != end; ++begin, ++begin_world)
+		{
+			const uint32_t ai = hash::get(_actor_map, begin->encode(), UINT32_MAX);
+			const Quaternion rot = rotation(*begin_world);
+			const Vector3 pos = translation(*begin_world);
+
+			if (ai == UINT32_MAX)
+				continue;
+
+			_actor[ai].actor->setCenterOfMassTransform(btTransform(to_btQuaternion(rot), to_btVector3(pos)));
+		}
+	}
+
+	void update(float dt)
+	{
+		_scene->stepSimulation(dt);
+
+		const int num = _scene->getNumCollisionObjects();
+		const btCollisionObjectArray& collision_array = _scene->getCollisionObjectArray();
+	    // Update actors
+		for (int i = 0; i < num; ++i)
+		{
+			if ((uintptr_t)collision_array[i]->getUserPointer() == (uintptr_t)UINT32_MAX)
+				continue;
+
+			btRigidBody* body = btRigidBody::upcast(collision_array[i]);
+			if (body && body->getMotionState())
+			{
+				btTransform tr;
+				body->getMotionState()->getWorldTransform(tr);
+				const btQuaternion rot_bt = tr.getRotation();
+				const btVector3 pos_bt    = tr.getOrigin();
+				const Quaternion rot      = to_quaternion(rot_bt);
+				const Vector3 pos         = to_vector3(pos_bt);
+				const Matrix4x4 pose      = matrix4x4(rot, pos);
+
+				const uint32_t a_idx = (uint32_t)(uintptr_t)body->getUserPointer();
+				const UnitId unit_id = _actor[a_idx].unit;
+			}
+		}
+	}
+
+	EventStream& events()
+	{
+		return _events;
+	}
+
+	void draw_debug()
+	{
+		if (!_debug_drawing)
+			return;
+
+		_scene->debugDrawWorld();
+		_debug_drawer._lines->submit();
+		_debug_drawer._lines->reset();
+	}
+
+	void enable_debug_drawing(bool enable)
+	{
+		_debug_drawing = enable;
+	}
+
+	void tick_callback(btDynamicsWorld* world, btScalar /*dt*/)
+	{
+		// Limit bodies velocity
+		for (uint32_t i = 0; i < array::size(_actor); ++i)
+		{
+			CE_ASSERT_NOT_NULL(_actor[i].actor);
+			const btVector3 velocity = _actor[i].actor->getLinearVelocity();
+			const btScalar speed = velocity.length();
+
+			if (speed > 100.0f)
+				_actor[i].actor->setLinearVelocity(velocity * 100.0f / speed);
+		}
+
+		// Check collisions
+		int num_manifolds = world->getDispatcher()->getNumManifolds();
+		for (int i = 0; i < num_manifolds; ++i)
+		{
+			const btPersistentManifold* contact_manifold = world->getDispatcher()->getManifoldByIndexInternal(i);
+
+			const btCollisionObject* actor_a = contact_manifold->getBody0();
+			const btCollisionObject* actor_b = contact_manifold->getBody1();
+			const ActorInstance a0 = make_actor_instance((uint32_t)(uintptr_t)actor_a->getUserPointer());
+			const ActorInstance a1 = make_actor_instance((uint32_t)(uintptr_t)actor_b->getUserPointer());
+
+			int num_contacts = contact_manifold->getNumContacts();
+			for (int j = 0; j < num_contacts; ++j)
+			{
+				const btManifoldPoint& point = contact_manifold->getContactPoint(j);
+				if (point.getDistance() < 0.0f)
+				{
+					const btVector3& where_a = point.getPositionWorldOnA();
+					const btVector3& where_b = point.getPositionWorldOnB();
+					const btVector3& normal = point.m_normalWorldOnB;
+
+					post_collision_event(a0
+						, a1
+						, to_vector3(where_a)
+						, to_vector3(normal)
+						, point.getLifeTime() > 0
+							? PhysicsCollisionEvent::BEGIN_TOUCH
+							: PhysicsCollisionEvent::END_TOUCH   // FIXME
+						);
+				}
+			}
+		}
+	}
+
+	void unit_destroyed_callback(UnitId id)
+	{
+		ActorInstance first = actor(id);
+		if (is_valid(first))
+			destroy_actor(first);
+	}
+
+	static void tick_cb(btDynamicsWorld* world, btScalar dt)
+	{
+		BulletWorld* bw = static_cast<BulletWorld*>(world->getWorldUserInfo());
+		bw->tick_callback(world, dt);
+	}
+
+	static void unit_destroyed_callback(UnitId id, void* user_ptr)
+	{
+		((BulletWorld*)user_ptr)->unit_destroyed_callback(id);
+	}
+
+private:
+
+	bool is_valid(ColliderInstance i) { return i.i != UINT32_MAX; }
+	bool is_valid(ActorInstance i) { return i.i != UINT32_MAX; }
+	bool is_valid(ControllerInstance i) { return i.i != UINT32_MAX; }
+	bool is_valid(JointInstance i) { return i.i != UINT32_MAX; }
+
+	ColliderInstance make_collider_instance(uint32_t i) { ColliderInstance inst = { i }; return inst; }
+	ActorInstance make_actor_instance(uint32_t i) { ActorInstance inst = { i }; return inst; }
+	ControllerInstance make_controller_instance(uint32_t i) { ControllerInstance inst = { i }; return inst; }
+	JointInstance make_joint_instance(uint32_t i) { JointInstance inst = { i }; return inst; }
+
+	void post_collision_event(ActorInstance a0, ActorInstance a1, const Vector3& where, const Vector3& normal, PhysicsCollisionEvent::Type type)
+	{
+		PhysicsCollisionEvent ev;
+		ev.type = type;
+		ev.actors[0] = a0;
+		ev.actors[1] = a1;
+		ev.where = where;
+		ev.normal = normal;
+
+		event_stream::write(_events, EventType::PHYSICS_COLLISION, ev);
+	}
+
+	void post_trigger_event(ActorInstance trigger, ActorInstance other, PhysicsTriggerEvent::Type type)
+	{
+		PhysicsTriggerEvent ev;
+		ev.type = type;
+		ev.trigger = trigger;
+		ev.other = other;
+
+		event_stream::write(_events, EventType::PHYSICS_TRIGGER, ev);
+	}
+
+	struct ColliderInstanceData
+	{
+		UnitId unit;
+		btCollisionShape* shape;
+		ColliderInstance next;
+	};
+
+	struct ActorInstanceData
+	{
+		UnitId unit;
+		btRigidBody* actor;
+	};
+
+	struct ControllerInstanceData
+	{
+		UnitId unit;
+		btKinematicCharacterController* contr;
+	};
+
+	Allocator* _allocator;
+
+	Hash<uint32_t> _collider_map;
+	Hash<uint32_t> _actor_map;
+	Hash<uint32_t> _controller_map;
+	Array<ColliderInstanceData> _collider;
+	Array<ActorInstanceData> _actor;
+	Array<ControllerInstanceData> _controller;
+	Array<btTypedConstraint*> _joints;
+
+	MyFilterCallback _filter_cb;
+	btDiscreteDynamicsWorld* _scene;
+	MyDebugDrawer _debug_drawer;
+
+	EventStream _events;
+
+	const PhysicsConfigResource* _config_resource;
+	bool _debug_drawing;
+};
+
+PhysicsWorld* PhysicsWorld::create(Allocator& a, ResourceManager& rm, UnitManager& um, DebugLine& dl)
+{
+	return CE_NEW(a, BulletWorld)(a, rm, um, dl);
+}
+
+void PhysicsWorld::destroy(Allocator& a, PhysicsWorld* pw)
+{
+	CE_DELETE(a, pw);
+}
+} // namespace crown
+
+#endif // CROWN_PHYSICS_BULLET

+ 681 - 37
src/renderers/render_world.cpp

@@ -3,90 +3,734 @@
  * License: https://github.com/taylor001/crown/blob/master/LICENSE
  */
 
-#include "render_world.h"
-#include "memory.h"
-#include "camera.h"
-#include "log.h"
-#include "sprite_resource.h"
-#include "sprite.h"
+#include "aabb.h"
+#include "color4.h"
+#include "debug_line.h"
+#include "hash.h"
 #include "material.h"
-#include "config.h"
-#include "gui.h"
+#include "material_manager.h"
+#include "matrix4x4.h"
 #include "mesh_resource.h"
-#include "scene_graph.h"
+#include "render_world.h"
+#include "resource_manager.h"
+#include "sprite_resource.h"
+#include "unit_manager.h"
 #include <bgfx/bgfx.h>
 
 namespace crown
 {
 
-RenderWorld::RenderWorld()
-	: m_sprite_pool(default_allocator(), MAX_SPRITES, sizeof(Sprite), CE_ALIGNOF(Sprite))
-	, m_gui_pool(default_allocator(), MAX_GUIS, sizeof(Gui), CE_ALIGNOF(Gui))
+RenderWorld::RenderWorld(Allocator& a, ResourceManager& rm, MaterialManager& mm, UnitManager& um)
+	: _marker(MARKER)
+	, _allocator(&a)
+	, _resource_manager(&rm)
+	, _material_manager(&mm)
+	, _debug_drawing(false)
+	, _mesh_map(a)
+	, _sprite_map(a)
+	, _light_map(a)
 {
+	um.register_destroy_function(RenderWorld::unit_destroyed_callback, this);
+
+	_u_light_pos = bgfx::createUniform("u_light_pos", bgfx::UniformType::Vec4);
+	_u_light_dir = bgfx::createUniform("u_light_dir", bgfx::UniformType::Vec4);
+	_u_light_col = bgfx::createUniform("u_light_col", bgfx::UniformType::Vec4);
 }
 
 RenderWorld::~RenderWorld()
 {
+	bgfx::destroyUniform(_u_light_pos);
+	bgfx::destroyUniform(_u_light_dir);
+	bgfx::destroyUniform(_u_light_col);
+
+	_allocator->deallocate(_mesh_data.buffer);
+	_allocator->deallocate(_sprite_data.buffer);
+	_allocator->deallocate(_light_data.buffer);
+
+	_marker = 0;
+}
+
+MeshInstance RenderWorld::create_mesh(UnitId id, const MeshRendererDesc& mrd, const Matrix4x4& tr)
+{
+	if (_mesh_data.size == _mesh_data.capacity)
+		grow_mesh();
+
+	const MeshResource* mr = (const MeshResource*)_resource_manager->get(MESH_TYPE, mrd.mesh_resource);
+	_material_manager->create_material(mrd.material_resource);
+
+	const uint32_t last = _mesh_data.size;
+
+	_mesh_data.unit[last]          = id;
+	_mesh_data.mr[last]            = mr;
+	_mesh_data.mesh[last].vbh      = mr->geometry(mrd.mesh_name)->vertex_buffer;
+	_mesh_data.mesh[last].ibh      = mr->geometry(mrd.mesh_name)->index_buffer;
+	_mesh_data.material[last]      = mrd.material_resource;
+	_mesh_data.world[last]         = tr;
+	_mesh_data.obb[last]           = mr->geometry(mrd.mesh_name)->obb;
+	_mesh_data.next_instance[last] = make_mesh_instance(UINT32_MAX);
+
+	++_mesh_data.size;
+	++_mesh_data.first_hidden;
+
+	MeshInstance first = first_mesh(id);
+	if (!is_valid(first))
+	{
+		hash::set(_mesh_map, id.encode(), last);
+	}
+	else
+	{
+		add_mesh_node(first, make_mesh_instance(last));
+	}
+
+	return make_mesh_instance(last);
+}
+
+void RenderWorld::destroy_mesh(MeshInstance i)
+{
+	const uint32_t last       = _mesh_data.size - 1;
+	const UnitId u            = _mesh_data.unit[i.i];
+	const MeshInstance first  = first_mesh(u);
+	const MeshInstance last_i = make_mesh_instance(last);
+
+	swap_mesh_node(last_i, i);
+	remove_mesh_node(first, i);
+
+	_mesh_data.unit[i.i]          = _mesh_data.unit[last];
+	_mesh_data.mr[i.i]            = _mesh_data.mr[last];
+	_mesh_data.mesh[i.i].vbh      = _mesh_data.mesh[last].vbh;
+	_mesh_data.mesh[i.i].ibh      = _mesh_data.mesh[last].ibh;
+	_mesh_data.material[i.i]      = _mesh_data.material[last];
+	_mesh_data.world[i.i]         = _mesh_data.world[last];
+	_mesh_data.obb[i.i]           = _mesh_data.obb[last];
+	_mesh_data.next_instance[i.i] = _mesh_data.next_instance[last];
+
+	--_mesh_data.size;
+	--_mesh_data.first_hidden;
+}
+
+void RenderWorld::add_mesh_node(MeshInstance first, MeshInstance i)
+{
+	MeshInstance curr = first;
+	while (is_valid(next_mesh(curr)))
+		curr = next_mesh(curr);
+
+	_mesh_data.next_instance[curr.i] = i;
+}
+
+void RenderWorld::remove_mesh_node(MeshInstance first, MeshInstance i)
+{
+	const UnitId u = _mesh_data.unit[first.i];
+
+	if (i.i == first.i)
+	{
+		if (!is_valid(next_mesh(i)))
+			hash::set(_mesh_map, u.encode(), UINT32_MAX);
+		else
+			hash::set(_mesh_map, u.encode(), next_mesh(i).i);
+	}
+	else
+	{
+		MeshInstance prev = previous_mesh(i);
+		_mesh_data.next_instance[prev.i] = next_mesh(i);
+	}
+}
+
+void RenderWorld::swap_mesh_node(MeshInstance a, MeshInstance b)
+{
+	const UnitId u = _mesh_data.unit[a.i];
+	const MeshInstance first = first_mesh(u);
+
+	if (a.i == first.i)
+	{
+		hash::set(_mesh_map, u.encode(), b.i);
+	}
+	else
+	{
+		const MeshInstance prev_a = previous_mesh(a);
+		CE_ENSURE(prev_a.i != a.i);
+		_mesh_data.next_instance[prev_a.i] = b;
+	}
+}
+
+MeshInstance RenderWorld::first_mesh(UnitId id)
+{
+	return make_mesh_instance(hash::get(_mesh_map, id.encode(), UINT32_MAX));
+}
+
+MeshInstance RenderWorld::next_mesh(MeshInstance i)
+{
+	CE_ASSERT(i.i < _mesh_data.size, "Index out of bounds: i.i = %d", i.i);
+	return _mesh_data.next_instance[i.i];
+}
+
+MeshInstance RenderWorld::previous_mesh(MeshInstance i)
+{
+	const UnitId u = _mesh_data.unit[i.i];
+
+	MeshInstance first = first_mesh(u);
+	MeshInstance curr = first;
+	MeshInstance prev = { UINT32_MAX };
+
+	while (curr.i != i.i)
+	{
+		prev = curr;
+		curr = next_mesh(curr);
+	}
+
+	return prev;
+}
+
+void RenderWorld::set_mesh_material(MeshInstance i, StringId64 id)
+{
+	_mesh_data.material[i.i] = id;
+}
+
+void RenderWorld::set_mesh_visible(MeshInstance i, bool visible)
+{
+}
+
+OBB RenderWorld::mesh_obb(MeshInstance i)
+{
+	return _mesh_data.obb[i.i];
+}
+
+SpriteInstance RenderWorld::create_sprite(UnitId id, const SpriteRendererDesc& srd, const Matrix4x4& tr)
+{
+	if (_sprite_data.size == _sprite_data.capacity)
+		grow_sprite();
+
+	const SpriteResource* sr = (const SpriteResource*)_resource_manager->get(SPRITE_TYPE, srd.sprite_resource);
+	_material_manager->create_material(srd.material_resource);
+
+	const uint32_t last = _sprite_data.size;
+
+	_sprite_data.unit[last]          = id;
+	_sprite_data.sr[last]            = sr;
+	_sprite_data.sprite[last].vbh    = sr->vb;
+	_sprite_data.sprite[last].ibh    = sr->ib;
+	_sprite_data.material[last]      = srd.material_resource;
+	_sprite_data.frame[last]         = 0;
+	_sprite_data.world[last]         = tr;
+	_sprite_data.aabb[last]          = AABB();
+	_sprite_data.next_instance[last] = make_sprite_instance(UINT32_MAX);
+
+	++_sprite_data.size;
+	++_sprite_data.first_hidden;
+
+	SpriteInstance first = first_sprite(id);
+	if (!is_valid(first))
+	{
+		hash::set(_sprite_map, id.encode(), last);
+	}
+	else
+	{
+		add_sprite_node(first, make_sprite_instance(last));
+	}
+
+	return make_sprite_instance(last);
+}
+
+void RenderWorld::destroy_sprite(SpriteInstance i)
+{
+	const uint32_t last         = _sprite_data.size - 1;
+	const UnitId u              = _sprite_data.unit[i.i];
+	const SpriteInstance first  = first_sprite(u);
+	const SpriteInstance last_i = make_sprite_instance(last);
+
+	swap_sprite_node(last_i, i);
+	remove_sprite_node(first, i);
+
+	_sprite_data.unit[i.i]          = _sprite_data.unit[last];
+	_sprite_data.sr[i.i]            = _sprite_data.sr[last];
+	_sprite_data.sprite[i.i].vbh    = _sprite_data.sprite[last].vbh;
+	_sprite_data.sprite[i.i].ibh    = _sprite_data.sprite[last].ibh;
+	_sprite_data.material[i.i]      = _sprite_data.material[last];
+	_sprite_data.frame[i.i]         = _sprite_data.frame[last];
+	_sprite_data.world[i.i]         = _sprite_data.world[last];
+	_sprite_data.aabb[i.i]          = _sprite_data.aabb[last];
+	_sprite_data.next_instance[i.i] = _sprite_data.next_instance[last];
+
+	--_sprite_data.size;
+	--_sprite_data.first_hidden;
+}
+
+void RenderWorld::add_sprite_node(SpriteInstance first, SpriteInstance i)
+{
+	SpriteInstance curr = first;
+	while (is_valid(next_sprite(curr)))
+		curr = next_sprite(curr);
+
+	_sprite_data.next_instance[curr.i] = i;
+}
+
+void RenderWorld::remove_sprite_node(SpriteInstance first, SpriteInstance i)
+{
+	const UnitId u = _sprite_data.unit[first.i];
+
+	if (i.i == first.i)
+	{
+		if (!is_valid(next_sprite(i)))
+			hash::set(_sprite_map, u.encode(), UINT32_MAX);
+		else
+			hash::set(_sprite_map, u.encode(), next_sprite(i).i);
+	}
+	else
+	{
+		SpriteInstance prev = previous_sprite(i);
+		_sprite_data.next_instance[prev.i] = next_sprite(i);
+	}
+}
+
+void RenderWorld::swap_sprite_node(SpriteInstance a, SpriteInstance b)
+{
+	const UnitId u = _sprite_data.unit[a.i];
+	const SpriteInstance first = first_sprite(u);
+
+	if (a.i == first.i)
+	{
+		hash::set(_sprite_map, u.encode(), b.i);
+	}
+	else
+	{
+		const SpriteInstance prev_a = previous_sprite(a);
+		_sprite_data.next_instance[prev_a.i] = b;
+	}
+}
+
+SpriteInstance RenderWorld::first_sprite(UnitId id)
+{
+	return make_sprite_instance(hash::get(_sprite_map, id.encode(), UINT32_MAX));
+}
+
+SpriteInstance RenderWorld::next_sprite(SpriteInstance i)
+{
+	CE_ASSERT(i.i < _sprite_data.size, "Index out of bounds");
+	return _sprite_data.next_instance[i.i];
+}
+
+SpriteInstance RenderWorld::previous_sprite(SpriteInstance i)
+{
+	const UnitId u = _sprite_data.unit[i.i];
+
+	SpriteInstance first = first_sprite(u);
+	SpriteInstance curr = first;
+	SpriteInstance prev = { UINT32_MAX };
+
+	while (curr.i != i.i)
+	{
+		prev = curr;
+		curr = next_sprite(curr);
+	}
+
+	return prev;
+}
+
+void RenderWorld::set_sprite_material(SpriteInstance i, StringId64 id)
+{
+	_sprite_data.material[i.i] = id;
+}
+
+void RenderWorld::set_sprite_visible(SpriteInstance i, bool visible)
+{
+}
+
+void RenderWorld::set_sprite_frame(SpriteInstance i, uint32_t index)
+{
+	_sprite_data.frame[i.i] = index;
+}
+
+LightInstance RenderWorld::create_light(UnitId id, const LightDesc& ld, const Matrix4x4& tr)
+{
+	CE_ASSERT(!hash::has(_light_map, id.encode()), "Unit already has light");
+
+	if (_light_data.size == _light_data.capacity)
+		grow_light();
+
+	const uint32_t last = _light_data.size;
+
+	_light_data.unit[last]       = id;
+	_light_data.world[last]      = tr;
+	_light_data.range[last]      = ld.range;
+	_light_data.intensity[last]  = ld.intensity;
+	_light_data.spot_angle[last] = ld.spot_angle;
+	_light_data.color[last]      = vector4(ld.color.x, ld.color.y, ld.color.z, 1.0f);
+	_light_data.type[last]       = ld.type;
+
+	++_light_data.size;
+
+	hash::set(_light_map, id.encode(), last);
+	return make_light_instance(last);
+}
+
+void RenderWorld::destroy_light(LightInstance i)
+{
+	const uint32_t last = _light_data.size - 1;
+	const UnitId u = _light_data.unit[i.i];
+	const UnitId last_u = _light_data.unit[last];
+
+	_light_data.unit[i.i]       = _light_data.unit[last];
+	_light_data.world[i.i]      = _light_data.world[last];
+	_light_data.range[i.i]      = _light_data.range[last];
+	_light_data.intensity[i.i]  = _light_data.intensity[last];
+	_light_data.spot_angle[i.i] = _light_data.spot_angle[last];
+	_light_data.color[i.i]      = _light_data.color[last];
+	_light_data.type[i.i]       = _light_data.type[last];
+
+	--_light_data.size;
+
+	hash::set(_light_map, last_u.encode(), i.i);
+	hash::remove(_light_map, u.encode());
+}
+
+LightInstance RenderWorld::light(UnitId id)
+{
+	return make_light_instance(hash::get(_light_map, id.encode(), UINT32_MAX));
 }
 
-SpriteId RenderWorld::create_sprite(SpriteResource* sr, SceneGraph& sg, UnitId id)
+Color4 RenderWorld::light_color(LightInstance i)
 {
-	Sprite* sprite = CE_NEW(m_sprite_pool, Sprite)(*this, sg, id, sr);
-	return id_array::create(m_sprite, sprite);
+	return _light_data.color[i.i];
 }
 
-void RenderWorld::destroy_sprite(SpriteId id)
+LightType::Enum RenderWorld::light_type(LightInstance i)
 {
-	CE_DELETE(m_sprite_pool, id_array::get(m_sprite, id));
-	id_array::destroy(m_sprite, id);
+	return (LightType::Enum)_light_data.type[i.i];
 }
 
-Sprite*	RenderWorld::get_sprite(SpriteId id)
+float RenderWorld::light_range(LightInstance i)
 {
-	return id_array::get(m_sprite, id);
+	return _light_data.range[i.i];
 }
 
-GuiId RenderWorld::create_gui(uint16_t width, uint16_t height, const char* material)
+float RenderWorld::light_intensity(LightInstance i)
 {
-	Gui* gui = CE_NEW(m_gui_pool, Gui)(width, height, material);
-	GuiId id = id_array::create(m_guis, gui);
-	gui->set_id(id);
-	return id;
+	return _light_data.intensity[i.i];
 }
 
-void RenderWorld::destroy_gui(GuiId id)
+float RenderWorld::light_spot_angle(LightInstance i)
 {
-	CE_DELETE(m_gui_pool, id_array::get(m_guis, id));
-	id_array::destroy(m_guis, id);
+	return _light_data.spot_angle[i.i];
 }
 
-Gui* RenderWorld::get_gui(GuiId id)
+void RenderWorld::set_light_color(LightInstance i, const Color4& col)
 {
-	return id_array::get(m_guis, id);
+	_light_data.color[i.i] = col;
 }
 
-void RenderWorld::update(const Matrix4x4& view, const Matrix4x4& projection, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
+void RenderWorld::set_light_type(LightInstance i, LightType::Enum type)
+{
+	_light_data.type[i.i] = type;
+}
+
+void RenderWorld::set_light_range(LightInstance i, float range)
+{
+	_light_data.range[i.i] = range;
+}
+
+void RenderWorld::set_light_intensity(LightInstance i, float intensity)
+{
+	_light_data.intensity[i.i] = intensity;
+}
+
+void RenderWorld::set_light_spot_angle(LightInstance i, float angle)
+{
+	_light_data.spot_angle[i.i] = angle;
+}
+
+void RenderWorld::update_transforms(const UnitId* begin, const UnitId* end, const Matrix4x4* world)
+{
+	for (; begin != end; ++begin, ++world)
+	{
+		uint32_t inst = hash::get(_mesh_map, begin->encode(), UINT32_MAX);
+
+		if (inst != UINT32_MAX)
+			_mesh_data.world[inst] = *world;
+
+		inst = hash::get(_sprite_map, begin->encode(), UINT32_MAX);
+
+		if (inst != UINT32_MAX)
+			_sprite_data.world[inst] = *world;
+
+		inst = hash::get(_light_map, begin->encode(), UINT32_MAX);
+
+		if (inst != UINT32_MAX)
+			_light_data.world[inst] = *world;
+	}
+}
+
+void RenderWorld::render(const Matrix4x4& view, const Matrix4x4& projection, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
 {
-	// Set view 0 clear state.
 	bgfx::setViewClear(0
 		, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
 		, 0x353839FF
 		, 1.0f
-		, 0);
+		, 0
+		);
 
 	// Set view and projection matrix for view 0.
 	bgfx::setViewTransform(0, to_float_ptr(view), to_float_ptr(projection));
-	bgfx::setViewRect(0, 0, 0, width, height);
+	bgfx::setViewRect(0, x, y, width, height);
 
 	// This dummy draw call is here to make sure that view 0 is cleared
 	// if no other draw calls are submitted to view 0.
 	bgfx::touch(0);
 
-	// Draw all sprites
-	for (uint32_t s = 0; s < id_array::size(m_sprite); s++)
+	for (uint32_t ll = 0; ll < _light_data.size; ++ll)
+	{
+		const Vector4 ldir = normalize(_light_data.world[ll].z) * view;
+		const Vector3 lpos = translation(_light_data.world[ll]);
+
+		bgfx::setUniform(_u_light_pos, to_float_ptr(lpos));
+		bgfx::setUniform(_u_light_dir, to_float_ptr(ldir));
+		bgfx::setUniform(_u_light_col, to_float_ptr(_light_data.color[ll]));
+
+		// Render meshes
+		for (uint32_t i = 0; i < _mesh_data.first_hidden; ++i)
+		{
+			bgfx::setTransform(to_float_ptr(_mesh_data.world[i]));
+			bgfx::setVertexBuffer(_mesh_data.mesh[i].vbh);
+			bgfx::setIndexBuffer(_mesh_data.mesh[i].ibh);
+
+			_material_manager->get(_mesh_data.material[i])->bind();
+		}
+	}
+
+	// Render sprites
+	for (uint32_t i = 0; i < _sprite_data.first_hidden; ++i)
+	{
+		bgfx::setVertexBuffer(_sprite_data.sprite[i].vbh);
+		bgfx::setIndexBuffer(_sprite_data.sprite[i].ibh, _sprite_data.frame[i] * 6, 6);
+		bgfx::setTransform(to_float_ptr(_sprite_data.world[i]));
+
+		_material_manager->get(_sprite_data.material[i])->bind();
+	}
+}
+
+void RenderWorld::draw_debug(DebugLine& dl)
+{
+	if (!_debug_drawing)
+		return;
+
+	for (uint32_t i = 0; i < _mesh_data.size; ++i)
+	{
+		const OBB& obb = _mesh_data.obb[i];
+		const Matrix4x4& world = _mesh_data.world[i];
+		dl.add_obb(world * obb.tm, obb.half_extents, COLOR4_RED);
+	}
+
+	for (uint32_t i = 0; i < _light_data.size; ++i)
+	{
+		const Vector3 pos = translation(_light_data.world[i]);
+		const Vector3 dir = -z(_light_data.world[i]);
+
+		// Draw tiny sphere for all light types
+		dl.add_sphere(pos, 0.1f, _light_data.color[i]);
+
+		switch (_light_data.type[i])
+		{
+			case LightType::DIRECTIONAL:
+			{
+				const Vector3 end = pos + dir*3.0f;
+				dl.add_line(pos, end, COLOR4_YELLOW);
+				dl.add_cone(pos + dir*2.8f, end, 0.1f, COLOR4_YELLOW);
+				break;
+			}
+			case LightType::OMNI:
+			{
+				dl.add_sphere(pos, _light_data.range[i], COLOR4_YELLOW);
+				break;
+			}
+			case LightType::SPOT:
+			{
+				const float angle = _light_data.spot_angle[i];
+				const float range = _light_data.range[i];
+				const float radius = tan(angle)*range;
+				dl.add_cone(pos + range*dir, pos, radius, COLOR4_YELLOW);
+				break;
+			}
+			default:
+			{
+				CE_ASSERT(false, "Bad light type");
+				break;
+			}
+		}
+	}
+}
+
+void RenderWorld::enable_debug_drawing(bool enable)
+{
+	_debug_drawing = enable;
+}
+
+void RenderWorld::unit_destroyed_callback(UnitId id)
+{
 	{
-		m_sprite[s]->render();
+		MeshInstance first = first_mesh(id);
+		MeshInstance curr = first;
+		MeshInstance next = make_mesh_instance(UINT32_MAX);
+
+		while (is_valid(curr))
+		{
+			next = next_mesh(curr);
+			destroy_mesh(curr);
+			curr = next;
+		}
 	}
+
+	{
+		SpriteInstance first = first_sprite(id);
+		SpriteInstance curr = first;
+		SpriteInstance next = make_sprite_instance(UINT32_MAX);
+
+		while (is_valid(curr))
+		{
+			next = next_sprite(curr);
+			destroy_sprite(curr);
+			curr = next;
+		}
+	}
+
+	{
+		LightInstance first = light(id);
+
+		if (is_valid(first))
+			destroy_light(first);
+	}
+}
+
+void RenderWorld::allocate_mesh(uint32_t num)
+{
+	CE_ENSURE(num > _mesh_data.size);
+
+	const uint32_t bytes = num * (0
+		+ sizeof(UnitId)
+		+ sizeof(MeshResource*)
+		+ sizeof(MeshData)
+		+ sizeof(StringId64)
+		+ sizeof(Matrix4x4)
+		+ sizeof(OBB)
+		+ sizeof(MeshInstance)
+		);
+
+	MeshInstanceData new_data;
+	new_data.size = _mesh_data.size;
+	new_data.capacity = num;
+	new_data.buffer = _allocator->allocate(bytes);
+	new_data.first_hidden = _mesh_data.first_hidden;
+
+	new_data.unit = (UnitId*)(new_data.buffer);
+	new_data.mr = (const MeshResource**)(new_data.unit + num);
+	new_data.mesh = (MeshData*)(new_data.mr + num);
+	new_data.material = (StringId64*)(new_data.mesh + num);
+	new_data.world = (Matrix4x4*)(new_data.material + num);
+	new_data.obb = (OBB*)(new_data.world + num);
+	new_data.next_instance = (MeshInstance*)(new_data.obb + num);
+
+	memcpy(new_data.unit, _mesh_data.unit, _mesh_data.size * sizeof(UnitId));
+	memcpy(new_data.mr, _mesh_data.mr, _mesh_data.size * sizeof(MeshResource*));
+	memcpy(new_data.mesh, _mesh_data.mesh, _mesh_data.size * sizeof(MeshData));
+	memcpy(new_data.material, _mesh_data.material, _mesh_data.size * sizeof(StringId64));
+	memcpy(new_data.world, _mesh_data.world, _mesh_data.size * sizeof(Matrix4x4));
+	memcpy(new_data.obb, _mesh_data.obb, _mesh_data.size * sizeof(OBB));
+	memcpy(new_data.next_instance, _mesh_data.next_instance, _mesh_data.size * sizeof(MeshInstance));
+
+	_allocator->deallocate(_mesh_data.buffer);
+	_mesh_data = new_data;
+}
+
+void RenderWorld::allocate_sprite(uint32_t num)
+{
+	CE_ENSURE(num > _sprite_data.size);
+
+	const uint32_t bytes = num * (0
+		+ sizeof(UnitId)
+		+ sizeof(SpriteResource**)
+		+ sizeof(SpriteData)
+		+ sizeof(StringId64)
+		+ sizeof(uint32_t)
+		+ sizeof(Matrix4x4)
+		+ sizeof(AABB)
+		+ sizeof(SpriteInstance)
+		);
+
+	SpriteInstanceData new_data;
+	new_data.size = _sprite_data.size;
+	new_data.capacity = num;
+	new_data.buffer = _allocator->allocate(bytes);
+	new_data.first_hidden = _sprite_data.first_hidden;
+
+	new_data.unit = (UnitId*)(new_data.buffer);
+	new_data.sr = (const SpriteResource**)(new_data.unit + num);
+	new_data.sprite = (SpriteData*)(new_data.sr + num);
+	new_data.material = (StringId64*)(new_data.sprite + num);
+	new_data.frame = (uint32_t*)(new_data.material + num);
+	new_data.world = (Matrix4x4*)(new_data.frame + num);
+	new_data.aabb = (AABB*)(new_data.world + num);
+	new_data.next_instance = (SpriteInstance*)(new_data.aabb + num);
+
+	memcpy(new_data.unit, _sprite_data.unit, _sprite_data.size * sizeof(UnitId));
+	memcpy(new_data.sr, _sprite_data.sr, _sprite_data.size * sizeof(SpriteResource**));
+	memcpy(new_data.sprite, _sprite_data.sprite, _sprite_data.size * sizeof(SpriteData));
+	memcpy(new_data.material, _sprite_data.material, _sprite_data.size * sizeof(StringId64));
+	memcpy(new_data.frame, _sprite_data.frame, _sprite_data.size * sizeof(uint32_t));
+	memcpy(new_data.world, _sprite_data.world, _sprite_data.size * sizeof(Matrix4x4));
+	memcpy(new_data.aabb, _sprite_data.aabb, _sprite_data.size * sizeof(AABB));
+	memcpy(new_data.next_instance, _sprite_data.next_instance, _sprite_data.size * sizeof(SpriteInstance));
+
+	_allocator->deallocate(_sprite_data.buffer);
+	_sprite_data = new_data;
+}
+
+void RenderWorld::allocate_light(uint32_t num)
+{
+	CE_ENSURE(num > _light_data.size);
+
+	const uint32_t bytes = num * (0
+		+ sizeof(UnitId)
+		+ sizeof(Matrix4x4)
+		+ sizeof(float)
+		+ sizeof(float)
+		+ sizeof(float)
+		+ sizeof(Color4)
+		+ sizeof(uint32_t)
+		);
+
+	LightInstanceData new_data;
+	new_data.size = _light_data.size;
+	new_data.capacity = num;
+	new_data.buffer = _allocator->allocate(bytes);
+
+	new_data.unit = (UnitId*)(new_data.buffer);
+	new_data.world = (Matrix4x4*)(new_data.unit + num);
+	new_data.range = (float*)(new_data.world + num);
+	new_data.intensity = (float*)(new_data.range + num);
+	new_data.spot_angle = (float*)(new_data.intensity + num);
+	new_data.color = (Color4*)(new_data.spot_angle + num);
+	new_data.type = (uint32_t*)(new_data.color + num);
+
+	memcpy(new_data.unit, _light_data.unit, _light_data.size * sizeof(UnitId));
+	memcpy(new_data.world, _light_data.world, _light_data.size * sizeof(Matrix4x4));
+	memcpy(new_data.range, _light_data.range, _light_data.size * sizeof(float));
+	memcpy(new_data.intensity, _light_data.intensity, _light_data.size * sizeof(float));
+	memcpy(new_data.spot_angle, _light_data.spot_angle, _light_data.size * sizeof(float));
+	memcpy(new_data.color, _light_data.color, _light_data.size * sizeof(Color4));
+	memcpy(new_data.type, _light_data.type, _light_data.size * sizeof(uint32_t));
+
+	_allocator->deallocate(_light_data.buffer);
+	_light_data = new_data;
+}
+
+void RenderWorld::grow_mesh()
+{
+	allocate_mesh(_mesh_data.capacity * 2 + 1);
+}
+void RenderWorld::grow_sprite()
+{
+	allocate_sprite(_sprite_data.capacity * 2 + 1);
+}
+void RenderWorld::grow_light()
+{
+	allocate_light(_light_data.capacity * 2 + 1);
 }
 
 } // namespace crown

+ 248 - 29
src/renderers/render_world.h

@@ -5,28 +5,17 @@
 
 #pragma once
 
-#include "id_array.h"
 #include "container_types.h"
 #include "math_types.h"
 #include "resource_types.h"
-#include "pool_allocator.h"
-#include "render_world_types.h"
-#include "material_manager.h"
-#include "resource_types.h"
-
-#define MAX_SPRITES 512
-#define MAX_GUIS 8
+#include "graphics_types.h"
+#include "world_types.h"
+#include "string_id.h"
+#include <bgfx/bgfx.h>
 
 namespace crown
 {
 
-struct Material;
-struct SceneGraph;
-struct Sprite;
-struct Mesh;
-struct Gui;
-typedef Id UnitId;
-
 /// @defgroup Graphics Graphics
 
 /// Manages graphics objects in a World.
@@ -36,30 +25,260 @@ class RenderWorld
 {
 public:
 
-	RenderWorld();
+	RenderWorld(Allocator& a, ResourceManager& rm, MaterialManager& mm, UnitManager& um);
 	~RenderWorld();
 
-	SpriteId create_sprite(SpriteResource* sr, SceneGraph& sg, UnitId id);
+	/// Creates a new mesh instance.
+	MeshInstance create_mesh(UnitId id, const MeshRendererDesc& mrd, const Matrix4x4& tr);
+
+	/// Destroys the mesh @a i.
+	void destroy_mesh(MeshInstance i);
+
+	/// Returns the first mesh of the unit @a id.
+	MeshInstance first_mesh(UnitId id);
+
+	/// Returns the next mesh in the chain.
+	MeshInstance next_mesh(MeshInstance id);
+
+	/// Returns the previous mesh in the chain.
+	MeshInstance previous_mesh(MeshInstance id);
+
+	void set_mesh_material(MeshInstance i, StringId64 id);
+	void set_mesh_visible(MeshInstance i, bool visible);
+	OBB mesh_obb(MeshInstance i);
+
+	/// Creates a new sprite instance.
+	SpriteInstance create_sprite(UnitId id, const SpriteRendererDesc& srd, const Matrix4x4& tr);
+
+	/// Destroys the sprite @a i.
+	void destroy_sprite(SpriteInstance i);
+
+	/// Returns the first sprite in the chain.
+	SpriteInstance first_sprite(UnitId id);
+
+	/// Returns the next sprite in the chain.
+	SpriteInstance next_sprite(SpriteInstance i);
+
+	/// Returns the previous sprite in the chain.
+	SpriteInstance previous_sprite(SpriteInstance id);
+
+	void set_sprite_material(SpriteInstance i, StringId64 id);
+	void set_sprite_frame(SpriteInstance i, uint32_t index);
+	void set_sprite_visible(SpriteInstance i, bool visible);
+
+	/// Creates a new light instance.
+	LightInstance create_light(UnitId id, const LightDesc& ld, const Matrix4x4& tr);
+
+	/// Destroys the light @a i.
+	void destroy_light(LightInstance i);
+
+	/// Returns the light of the unit @a id.
+	LightInstance light(UnitId id);
+
+	/// Returns the type of the light @a i.
+	LightType::Enum light_type(LightInstance i);
+
+	/// Returns the color of the light @a i.
+	Color4 light_color(LightInstance i);
+
+	/// Returns the range of the light @a i.
+	float light_range(LightInstance i);
+
+	/// Returns the intensity of the light @a i.
+	float light_intensity(LightInstance i);
+
+	/// Returns the spot angle of the light @a i.
+	float light_spot_angle(LightInstance i);
+
+	/// Sets the @a type of the light @a i.
+	void set_light_type(LightInstance i, LightType::Enum type);
+
+	/// Sets the @a color of the light @a i.
+	void set_light_color(LightInstance i, const Color4& color);
+
+	/// Sets the @a range of the light @a i.
+	void set_light_range(LightInstance i, float range);
+
+	/// Sets the @a intensity of the light @a i.
+	void set_light_intensity(LightInstance i, float intensity);
+
+	/// Sets the spot @a angle of the light @a i.
+	void set_light_spot_angle(LightInstance i, float angle);
+
+	void update_transforms(const UnitId* begin, const UnitId* end, const Matrix4x4* world);
 
-	/// Destroys the sprite @a id.
-	void destroy_sprite(SpriteId id);
+	void render(const Matrix4x4& view, const Matrix4x4& projection, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
 
-	/// Creates the sprite @a id.
-	Sprite* get_sprite(SpriteId id);
+	/// Sets whether to @a enable debug drawing
+	void enable_debug_drawing(bool enable);
 
-	GuiId create_gui(uint16_t width, uint16_t height, const char* material);
-	void destroy_gui(GuiId id);
-	Gui* get_gui(GuiId id);
+	/// Fills @a dl with debug lines
+	void draw_debug(DebugLine& dl);
 
-	void update(const Matrix4x4& view, const Matrix4x4& projection, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
+	bool is_valid(MeshInstance i) { return i.i != UINT32_MAX; }
+	bool is_valid(SpriteInstance i) { return i.i != UINT32_MAX; }
+	bool is_valid(LightInstance i) { return i.i != UINT32_MAX; }
+
+public:
+
+	enum { MARKER = 0xc82277de };
 
 private:
 
-	PoolAllocator m_sprite_pool;
-	PoolAllocator m_gui_pool;
+	static void unit_destroyed_callback(UnitId id, void* user_ptr)
+	{
+		((RenderWorld*)user_ptr)->unit_destroyed_callback(id);
+	}
+
+	void unit_destroyed_callback(UnitId id);
+
+	void allocate_mesh(uint32_t num);
+	void allocate_sprite(uint32_t num);
+	void allocate_light(uint32_t num);
+	void grow_mesh();
+	void grow_sprite();
+	void grow_light();
+
+	MeshInstance make_mesh_instance(uint32_t i) { MeshInstance inst = { i }; return inst; }
+	SpriteInstance make_sprite_instance(uint32_t i) { SpriteInstance inst = { i }; return inst; }
+	LightInstance make_light_instance(uint32_t i) { LightInstance inst = { i }; return inst; }
+
+	void add_mesh_node(MeshInstance first, MeshInstance i);
+	void remove_mesh_node(MeshInstance first, MeshInstance i);
+	void swap_mesh_node(MeshInstance a, MeshInstance b);
+	void add_sprite_node(SpriteInstance first, SpriteInstance i);
+	void remove_sprite_node(SpriteInstance first, SpriteInstance i);
+	void swap_sprite_node(SpriteInstance a, SpriteInstance b);
+
+	struct MeshData
+	{
+		bgfx::VertexBufferHandle vbh;
+		bgfx::IndexBufferHandle ibh;
+	};
+
+	struct MeshInstanceData
+	{
+		MeshInstanceData()
+			: size(0)
+			, capacity(0)
+			, buffer(NULL)
+
+			, first_hidden(0)
+
+			, unit(NULL)
+			, mr(NULL)
+			, mesh(NULL)
+			, material(NULL)
+			, world(NULL)
+			, obb(NULL)
+			, next_instance(NULL)
+		{
+		}
+
+		uint32_t size;
+		uint32_t capacity;
+		void* buffer;
+
+		uint32_t first_hidden;
+
+		UnitId* unit;
+		const MeshResource** mr;
+		MeshData* mesh;
+		StringId64* material;
+		Matrix4x4* world;
+		OBB* obb;
+		MeshInstance* next_instance;
+	};
+
+	struct SpriteData
+	{
+		bgfx::VertexBufferHandle vbh;
+		bgfx::IndexBufferHandle ibh;
+	};
+
+	struct SpriteInstanceData
+	{
+		SpriteInstanceData()
+			: size(0)
+			, capacity(0)
+			, buffer(NULL)
+
+			, first_hidden(0)
+
+			, unit(NULL)
+			, sr(NULL)
+			, sprite(NULL)
+			, material(NULL)
+			, frame(NULL)
+			, world(NULL)
+			, aabb(NULL)
+			, next_instance(NULL)
+		{
+		}
+
+		uint32_t size;
+		uint32_t capacity;
+		void* buffer;
+
+		uint32_t first_hidden;
+
+		UnitId* unit;
+		const SpriteResource** sr;
+		SpriteData* sprite;
+		StringId64* material;
+		uint32_t* frame;
+		Matrix4x4* world;
+		AABB* aabb;
+		SpriteInstance* next_instance;
+	};
+
+	struct LightInstanceData
+	{
+		LightInstanceData()
+			: size(0)
+			, capacity(0)
+			, buffer(NULL)
+
+			, unit(NULL)
+			, world(NULL)
+			, range(NULL)
+			, intensity(NULL)
+			, spot_angle(NULL)
+			, color(NULL)
+			, type(NULL)
+		{
+		}
+
+		uint32_t size;
+		uint32_t capacity;
+		void* buffer;
+
+		UnitId* unit;
+		Matrix4x4* world;
+		float* range;
+		float* intensity;
+		float* spot_angle;
+		Color4* color;
+		uint32_t* type; // LightType::Enum
+	};
+
+	uint32_t _marker;
+
+	Allocator* _allocator;
+	ResourceManager* _resource_manager;
+	MaterialManager* _material_manager;
+
+	bgfx::UniformHandle _u_light_pos;
+	bgfx::UniformHandle _u_light_dir;
+	bgfx::UniformHandle _u_light_col;
 
-	IdArray<MAX_SPRITES, Sprite*> m_sprite;
-	IdArray<MAX_GUIS, Gui*> m_guis;
+	bool _debug_drawing;
+	Hash<uint32_t> _mesh_map;
+	MeshInstanceData _mesh_data;
+	Hash<uint32_t> _sprite_map;
+	SpriteInstanceData _sprite_data;
+	Hash<uint32_t> _light_map;
+	LightInstanceData _light_data;
 };
 
 } // namespace crown

+ 31 - 0
src/world/level.cpp

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#include "level.h"
+#include "level_resource.h"
+#include "world.h"
+
+namespace crown
+{
+
+Level::Level(Allocator& a, World& w, const LevelResource& lr)
+	: _marker(MARKER)
+	, _allocator(&a)
+	, _world(&w)
+	, _resource(&lr)
+{
+}
+
+Level::~Level()
+{
+	_marker = 0;
+}
+
+void Level::load(const Vector3& pos, const Quaternion& rot)
+{
+	_world->spawn_unit(*level_resource::get_units(_resource), pos, rot);
+}
+
+} // namespace crown

+ 38 - 0
src/world/level.h

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012-2016 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#pragma once
+
+#include "world_types.h"
+#include "memory_types.h"
+#include "resource_types.h"
+#include "math_types.h"
+
+namespace crown
+{
+
+class Level
+{
+public:
+
+	Level(Allocator& a, World& w, const LevelResource& lr);
+	~Level();
+
+	void load(const Vector3& pos, const Quaternion& rot);
+
+public:
+
+	enum { MARKER = 0x1f2b43fe };
+
+private:
+
+	uint32_t _marker;
+
+	Allocator* _allocator;
+	World* _world;
+	const LevelResource* _resource;
+};
+
+} // namespace crown

+ 29 - 1
src/world/scene_graph.cpp

@@ -45,7 +45,9 @@ void SceneGraph::allocate(uint32_t num)
 		+ sizeof(UnitId)
 		+ sizeof(Matrix4x4)
 		+ sizeof(Pose)
-		+ sizeof(TransformInstance) * 4);
+		+ sizeof(TransformInstance) * 4
+		+ sizeof(bool)
+		);
 
 	InstanceData new_data;
 	new_data.size = _data.size;
@@ -59,6 +61,7 @@ void SceneGraph::allocate(uint32_t num)
 	new_data.first_child = (TransformInstance*)(new_data.parent + num);
 	new_data.next_sibling = (TransformInstance*)(new_data.first_child + num);
 	new_data.prev_sibling = (TransformInstance*)(new_data.next_sibling + num);
+	new_data.changed = (bool*)(new_data.prev_sibling + num);
 
 	memcpy(new_data.unit, _data.unit, _data.size * sizeof(UnitId));
 	memcpy(new_data.world, _data.world, _data.size * sizeof(Matrix4x4));
@@ -67,6 +70,7 @@ void SceneGraph::allocate(uint32_t num)
 	memcpy(new_data.first_child, _data.first_child, _data.size * sizeof(TransformInstance));
 	memcpy(new_data.next_sibling, _data.next_sibling, _data.size * sizeof(TransformInstance));
 	memcpy(new_data.prev_sibling, _data.prev_sibling, _data.size * sizeof(TransformInstance));
+	memcpy(new_data.changed, _data.changed, _data.size * sizeof(bool));
 
 	_allocator.deallocate(_data.buffer);
 	_data = new_data;
@@ -88,6 +92,7 @@ TransformInstance SceneGraph::create(UnitId id, const Matrix4x4& m)
 	_data.first_child[last].i = UINT32_MAX;
 	_data.next_sibling[last].i = UINT32_MAX;
 	_data.prev_sibling[last].i = UINT32_MAX;
+	_data.changed[last] = false;
 
 	++_data.size;
 
@@ -109,6 +114,7 @@ void SceneGraph::destroy(TransformInstance i)
 	_data.first_child[i.i] = _data.first_child[last];
 	_data.next_sibling[i.i] = _data.next_sibling[last];
 	_data.prev_sibling[i.i] = _data.prev_sibling[last];
+	_data.changed[i.i] = _data.changed[last];
 
 	hash::set(_map, last_u.encode(), i.i);
 	hash::remove(_map, u.encode());
@@ -259,6 +265,26 @@ void SceneGraph::unlink(TransformInstance child)
 	_data.prev_sibling[child.i].i = UINT32_MAX;
 }
 
+void SceneGraph::clear_changed()
+{
+	for (uint32_t i = 0; i < _data.size; ++i)
+	{
+		_data.changed[i] = false;
+	}
+}
+
+void SceneGraph::get_changed(Array<UnitId>& units, Array<Matrix4x4>& world_poses)
+{
+	for (uint32_t i = 0; i < _data.size; ++i)
+	{
+		if (_data.changed[i])
+		{
+			array::push_back(units, _data.unit[i]);
+			array::push_back(world_poses, _data.world[i]);
+		}
+	}
+}
+
 bool SceneGraph::is_valid(TransformInstance i)
 {
 	return i.i != UINT32_MAX;
@@ -269,6 +295,8 @@ void SceneGraph::set_local(TransformInstance i)
 	TransformInstance parent = _data.parent[i.i];
 	Matrix4x4 parent_tm = is_valid(parent) ? _data.world[parent.i] : MATRIX4X4_IDENTITY;
 	transform(parent_tm, i);
+
+	_data.changed[i.i] = true;
 }
 
 void SceneGraph::transform(const Matrix4x4& parent, TransformInstance i)

+ 5 - 5
src/world/scene_graph.h

@@ -14,11 +14,6 @@
 namespace crown
 {
 
-struct TransformInstance
-{
-	uint32_t i;
-};
-
 /// Represents a collection of nodes, possibly linked together to form a tree.
 ///
 /// @ingroup World
@@ -79,6 +74,9 @@ struct SceneGraph
 	/// After unlinking, the @child local pose is set to its previous world pose.
 	void unlink(TransformInstance child);
 
+	void clear_changed();
+	void get_changed(Array<UnitId>& units, Array<Matrix4x4>& world_poses);
+
 	bool is_valid(TransformInstance i);
 
 	void set_local(TransformInstance i);
@@ -117,6 +115,7 @@ private:
 			, first_child(NULL)
 			, next_sibling(NULL)
 			, prev_sibling(NULL)
+			, changed(NULL)
 		{
 		}
 
@@ -131,6 +130,7 @@ private:
 		TransformInstance* first_child;
 		TransformInstance* next_sibling;
 		TransformInstance* prev_sibling;
+		bool* changed;
 	};
 
 	uint32_t _marker;

+ 401 - 191
src/world/world.cpp

@@ -3,173 +3,439 @@
  * License: https://github.com/taylor001/crown/blob/master/LICENSE
  */
 
-#include "world.h"
-#include "error.h"
-#include "resource_manager.h"
 #include "debug_line.h"
-#include "actor.h"
+#include "error.h"
+#include "hash.h"
+#include "level.h"
 #include "lua_environment.h"
-#include "level_resource.h"
-#include "memory.h"
 #include "matrix4x4.h"
-#include <new>
+#include "physics_world.h"
+#include "render_world.h"
+#include "resource_manager.h"
+#include "scene_graph.h"
+#include "sound_world.h"
+#include "temp_allocator.h"
+#include "unit_manager.h"
+#include "unit_resource.h"
+#include "vector3.h"
+#include "vector4.h"
+#include "world.h"
 
 namespace crown
 {
 
-World::World(ResourceManager& rm, LuaEnvironment& env)
-	: _resource_manager(&rm)
+World::World(Allocator& a, ResourceManager& rm, LuaEnvironment& env, MaterialManager& mm, UnitManager& um)
+	: _marker(MARKER)
+	, _allocator(&a)
+	, _resource_manager(&rm)
 	, _lua_environment(&env)
-	, m_unit_pool(default_allocator(), CE_MAX_UNITS, sizeof(Unit), CE_ALIGNOF(Unit))
-	, m_camera_pool(default_allocator(), CE_MAX_CAMERAS, sizeof(Camera), CE_ALIGNOF(Camera))
+	, _unit_manager(&um)
+	, _lines(NULL)
 	, _scene_graph(NULL)
-	, _sprite_animation_player(NULL)
 	, _render_world(NULL)
 	, _physics_world(NULL)
 	, _sound_world(NULL)
-	, _events(default_allocator())
-	, _lines(NULL)
+	, _units(a)
+	, _levels(a)
+	, _camera(a)
+	, _camera_map(a)
+	, _events(a)
 {
-	_scene_graph = CE_NEW(default_allocator(), SceneGraph)(default_allocator());
-	_sprite_animation_player = CE_NEW(default_allocator(), SpriteAnimationPlayer);
-	_render_world = CE_NEW(default_allocator(), RenderWorld);
-	_physics_world = CE_NEW(default_allocator(), PhysicsWorld)(*this);
-	_sound_world = SoundWorld::create(default_allocator());
-	_lines = create_debug_line(false);
+	_lines = create_debug_line(true);
+	_scene_graph = CE_NEW(*_allocator, SceneGraph)(*_allocator);
+	_render_world = CE_NEW(*_allocator, RenderWorld)(*_allocator, rm, mm, um);
+	_physics_world = PhysicsWorld::create(*_allocator, rm, um, *_lines);
+	_sound_world = SoundWorld::create(*_allocator);
 }
 
 World::~World()
 {
-	// Destroy all units
-	for (uint32_t i = 0; i < id_array::size(m_units); i++)
+	destroy_debug_line(*_lines);
+	SoundWorld::destroy(*_allocator, _sound_world);
+	PhysicsWorld::destroy(*_allocator, _physics_world);
+	CE_DELETE(*_allocator, _render_world);
+	CE_DELETE(*_allocator, _scene_graph);
+
+	for (uint32_t i = 0; i < array::size(_levels); ++i)
 	{
-		CE_DELETE(m_unit_pool, m_units[i]);
+		CE_DELETE(*_allocator, _levels[i]);
 	}
 
-	destroy_debug_line(*_lines);
-	SoundWorld::destroy(default_allocator(), _sound_world);
-	CE_DELETE(default_allocator(), _physics_world);
-	CE_DELETE(default_allocator(), _render_world);
-	CE_DELETE(default_allocator(), _sprite_animation_player);
-	CE_DELETE(default_allocator(), _scene_graph);
+	_marker = 0;
 }
 
-UnitId World::spawn_unit(const UnitResource* ur, const Vector3& pos, const Quaternion& rot)
+UnitId World::spawn_unit(const UnitResource& ur, const Vector3& pos, const Quaternion& rot)
 {
-	Unit* u = (Unit*) m_unit_pool.allocate(sizeof(Unit), CE_ALIGNOF(Unit));
-	const UnitId unit_id = id_array::create(m_units, u);
-	new (u) Unit(*this, unit_id, ur, *_scene_graph, matrix4x4(rot, pos));
+	TempAllocator512 ta;
+	UnitId* unit_lookup = (UnitId*)ta.allocate(sizeof(UnitId) * ur.num_units);
+
+	for (uint32_t i = 0; i < ur.num_units; ++i)
+		unit_lookup[i] = _unit_manager->create();
+
+	// First component data
+	const char* component_data = (const char*)(&ur + 1);
+
+	for (uint32_t cc = 0; cc < ur.num_component_types; ++cc)
+	{
+		const ComponentData* component = (const ComponentData*)component_data;
+		const uint32_t* unit_index = (const uint32_t*)(component + 1);
+		const char* data = (const char*)(unit_index + component->num_instances);
+
+		if (component->type == StringId32("transform")._id)
+		{
+			const TransformDesc* td = (const TransformDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 matrix = matrix4x4(rot, pos);
+				Matrix4x4 matrix_res = matrix4x4(td->rotation, td->position);
+				_scene_graph->create(unit_lookup[unit_index[i]], matrix_res*matrix);
+				++td;
+			}
+		}
+
+		if (component->type == StringId32("camera")._id)
+		{
+			const CameraDesc* cd = (const CameraDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				create_camera(unit_lookup[unit_index[i]], *cd);
+				++cd;
+			}
+		}
+
+		if (component->type == StringId32("collider")._id)
+		{
+			const ShapeDesc* sd = (const ShapeDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				physics_world()->create_collider(unit_lookup[unit_index[i]], sd);
+				++sd;
+			}
+		}
+
+		if (component->type == StringId32("actor")._id)
+		{
+			const ActorResource* ar = (const ActorResource*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 tm = _scene_graph->world_pose(_scene_graph->get(unit_lookup[unit_index[i]]));
+				physics_world()->create_actor(unit_lookup[unit_index[i]], ar, tm);
+				++ar;
+			}
+		}
+
+		if (component->type == StringId32("controller")._id)
+		{
+			const ControllerDesc* cd = (const ControllerDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 tm = _scene_graph->world_pose(_scene_graph->get(unit_lookup[unit_index[i]]));
+				physics_world()->create_controller(unit_lookup[unit_index[i]], *cd, tm);
+				++cd;
+			}
+		}
+
+		if (component->type == StringId32("mesh_renderer")._id)
+		{
+			const MeshRendererDesc* mrd = (const MeshRendererDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 tm = _scene_graph->world_pose(_scene_graph->get(unit_lookup[unit_index[i]]));
+				render_world()->create_mesh(unit_lookup[unit_index[i]], *mrd, tm);
+				++mrd;
+			}
+		}
 
-	post_unit_spawned_event(unit_id);
-	return unit_id;
+		if (component->type == StringId32("sprite_renderer")._id)
+		{
+			const SpriteRendererDesc* srd = (const SpriteRendererDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 tm = _scene_graph->world_pose(_scene_graph->get(unit_lookup[unit_index[i]]));
+				render_world()->create_sprite(unit_lookup[unit_index[i]], *srd, tm);
+				++srd;
+			}
+		}
+
+		if (component->type == StringId32("light")._id)
+		{
+			const LightDesc* ld = (const LightDesc*)data;
+			for (uint32_t i = 0; i < component->num_instances; ++i)
+			{
+				Matrix4x4 tm = _scene_graph->world_pose(_scene_graph->get(unit_lookup[unit_index[i]]));
+				render_world()->create_light(unit_lookup[unit_index[i]], *ld, tm);
+				++ld;
+			}
+		}
+
+		component_data += component->size + sizeof(ComponentData);
+	}
+
+	array::push(_units, &unit_lookup[0], ur.num_units);
+
+	// Post events
+	for (uint32_t i = 0; i < ur.num_units; ++i)
+		post_unit_spawned_event(unit_lookup[i]);
+
+	return unit_lookup[0];
 }
 
 UnitId World::spawn_unit(StringId64 name, const Vector3& pos, const Quaternion& rot)
 {
-	UnitResource* ur = (UnitResource*)_resource_manager->get(UNIT_TYPE, name);
-	return spawn_unit(ur, pos, rot);
+	const UnitResource* ur = (const UnitResource*)_resource_manager->get(UNIT_TYPE, name);
+	return spawn_unit(*ur, pos, rot);
 }
 
-void World::destroy_unit(UnitId id)
+void World::spawn_empty_unit(UnitId id)
 {
-	CE_DELETE(m_unit_pool, id_array::get(m_units, id));
-	id_array::destroy(m_units, id);
-	post_unit_destroyed_event(id);
+	array::push_back(_units, id);
+	post_unit_spawned_event(id);
 }
 
-void World::reload_units(UnitResource* old_ur, UnitResource* new_ur)
+void World::destroy_unit(UnitId id)
 {
-	for (uint32_t i = 0; i < id_array::size(m_units); i++)
-	{
-		if (m_units[i]->resource() == old_ur)
-		{
-			m_units[i]->reload(new_ur);
-		}
-	}
+	_unit_manager->destroy(id);
+	post_unit_destroyed_event(id);
 }
 
 uint32_t World::num_units() const
 {
-	return id_array::size(m_units);
+	return array::size(_units);
 }
 
 void World::units(Array<UnitId>& units) const
 {
-	for (uint32_t i = 0; i < id_array::size(m_units); i++)
-	{
-		array::push_back(units, m_units[i]->id());
-	}
+	array::reserve(units, array::size(_units));
+	array::push(units, array::begin(_units), array::size(_units));
 }
 
-void World::link_unit(UnitId child, UnitId parent)
+void World::update_animations(float /*dt*/)
 {
-	TransformInstance child_ti = _scene_graph->get(child);
-	TransformInstance parent_ti = _scene_graph->get(parent);
-	_scene_graph->link(child_ti, parent_ti);
 }
 
-void World::unlink_unit(UnitId child)
+void World::update_scene(float dt)
 {
-	_scene_graph->unlink(_scene_graph->get(child));
+	TempAllocator4096 ta;
+	Array<UnitId> changed_units(ta);
+	Array<Matrix4x4> changed_world(ta);
+
+	_scene_graph->get_changed(changed_units, changed_world);
+	_scene_graph->clear_changed();
+
+	_physics_world->update_actor_world_poses(array::begin(changed_units)
+		, array::end(changed_units)
+		, array::begin(changed_world)
+		);
+
+	_physics_world->update(dt);
+
+	_render_world->update_transforms(array::begin(changed_units)
+		, array::end(changed_units)
+		, array::begin(changed_world)
+		);
+
+	_sound_world->update();
+
+	EventStream& physics_events = _physics_world->events();
+	array::clear(physics_events);
+	array::clear(_events);
 }
 
-Unit* World::get_unit(UnitId id)
+void World::update(float dt)
 {
-	return id_array::get(m_units, id);
+	update_animations(dt);
+	update_scene(dt);
 }
 
-Camera* World::get_camera(CameraId id)
+void World::render(CameraInstance i)
 {
-	return id_array::get(m_cameras, id);
+	const Camera& camera = _camera[i.i];
+
+	_render_world->render(camera_view_matrix(i)
+		, camera.projection
+		, camera.view_x
+		, camera.view_y
+		, camera.view_width
+		, camera.view_height
+		);
+
+	_physics_world->draw_debug();
+	_render_world->draw_debug(*_lines);
+
+	_lines->submit();
+	_lines->reset();
 }
 
-void World::update_animations(float dt)
+CameraInstance World::create_camera(UnitId id, const CameraDesc& cd)
 {
-	_sprite_animation_player->update(dt);
+	Camera camera;
+	camera.unit            = id;
+	camera.projection_type = (ProjectionType::Enum)cd.type;
+	camera.fov             = cd.fov;
+	camera.near            = cd.near_range;
+	camera.far             = cd.far_range;
+
+	const uint32_t last = array::size(_camera);
+	array::push_back(_camera, camera);
+
+	hash::set(_camera_map, id.encode(), last);
+	return make_camera_instance(last);
 }
 
-void World::update_scene(float dt)
+void World::destroy_camera(CameraInstance i)
 {
-	_physics_world->update(dt);
+	const uint32_t last = array::size(_camera) - 1;
+	const UnitId u = _camera[i.i].unit;
+	const UnitId last_u = _camera[last].unit;
 
-	for (uint32_t i = 0; i < id_array::size(m_units); i++)
-	{
-		m_units[i]->update();
-	}
+	_camera[i.i] = _camera[last];
 
-	_sound_world->update();
+	hash::set(_camera_map, last_u.encode(), i.i);
+	hash::remove(_camera_map, u.encode());
+}
 
-	process_physics_events();
+CameraInstance World::camera(UnitId id)
+{
+	return make_camera_instance(hash::get(_camera_map, id.encode(), UINT32_MAX));
 }
 
-void World::update(float dt)
+void World::set_camera_projection_type(CameraInstance i, ProjectionType::Enum type)
 {
-	update_animations(dt);
-	update_scene(dt);
+	_camera[i.i].projection_type = type;
+	_camera[i.i].update_projection_matrix();
 }
 
-void World::render(Camera* camera)
+ProjectionType::Enum World::camera_projection_type(CameraInstance i) const
 {
-	_render_world->update(camera->view_matrix(), camera->projection_matrix(), camera->_view_x, camera->_view_y,
-		camera->_view_width, camera->_view_height);
+	return _camera[i.i].projection_type;
+}
 
-	// _physics_world->draw_debug(*_lines);
+const Matrix4x4& World::camera_projection_matrix(CameraInstance i) const
+{
+	return _camera[i.i].projection;
 }
 
-CameraId World::create_camera(SceneGraph& sg, UnitId id, ProjectionType::Enum type, float near, float far)
+Matrix4x4 World::camera_view_matrix(CameraInstance i) const
 {
-	Camera* camera = CE_NEW(m_camera_pool, Camera)(sg, id, type, near, far);
+	const TransformInstance ti = _scene_graph->get(_camera[i.i].unit);
+	Matrix4x4 view = _scene_graph->world_pose(ti);
+	invert(view);
+	return view;
+}
 
-	return id_array::create(m_cameras, camera);
+float World::camera_fov(CameraInstance i) const
+{
+	return _camera[i.i].fov;
+}
+
+void World::set_camera_fov(CameraInstance i, float fov)
+{
+	_camera[i.i].fov = fov;
+	_camera[i.i].update_projection_matrix();
+}
+
+float World::camera_aspect(CameraInstance i) const
+{
+	return _camera[i.i].aspect;
+}
+
+void World::set_camera_aspect(CameraInstance i, float aspect)
+{
+	_camera[i.i].aspect = aspect;
+	_camera[i.i].update_projection_matrix();
+}
+
+float World::camera_near_clip_distance(CameraInstance i) const
+{
+	return _camera[i.i].near;
 }
 
-void World::destroy_camera(CameraId id)
+void World::set_camera_near_clip_distance(CameraInstance i, float near)
 {
-	CE_DELETE(m_camera_pool, id_array::get(m_cameras, id));
-	id_array::destroy(m_cameras, id);
+	_camera[i.i].near = near;
+	_camera[i.i].update_projection_matrix();
 }
 
-SoundInstanceId World::play_sound(const SoundResource* sr, const bool loop, const float volume, const Vector3& pos, const float range)
+float World::camera_far_clip_distance(CameraInstance i) const
+{
+	return _camera[i.i].far;
+}
+
+void World::set_camera_far_clip_distance(CameraInstance i, float far)
+{
+	_camera[i.i].far = far;
+	_camera[i.i].update_projection_matrix();
+}
+
+void World::set_camera_orthographic_metrics(CameraInstance i, float left, float right, float bottom, float top)
+{
+	_camera[i.i].left = left;
+	_camera[i.i].right = right;
+	_camera[i.i].bottom = bottom;
+	_camera[i.i].top = top;
+
+	_camera[i.i].update_projection_matrix();
+}
+
+void World::set_camera_viewport_metrics(CameraInstance i, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
+{
+	_camera[i.i].view_x = x;
+	_camera[i.i].view_y = y;
+	_camera[i.i].view_width = width;
+	_camera[i.i].view_height = height;
+}
+
+Vector3 World::camera_screen_to_world(CameraInstance i, const Vector3& pos)
+{
+	const Camera& c = _camera[i.i];
+
+	const TransformInstance ti = _scene_graph->get(_camera[i.i].unit);
+	Matrix4x4 world_inv = _scene_graph->world_pose(ti);
+	invert(world_inv);
+	Matrix4x4 mvp = world_inv * c.projection;
+	invert(mvp);
+
+	Vector4 ndc;
+	ndc.x = (2.0f * (pos.x - 0.0f)) / c.view_width - 1.0f;
+	ndc.y = (2.0f * (c.view_height - pos.y)) / c.view_height - 1.0f;
+	ndc.z = (2.0f * pos.z) - 1.0f;
+	ndc.w = 1.0f;
+
+	Vector4 tmp = ndc * mvp;
+	tmp *= 1.0f / tmp.w;
+
+	return vector3(tmp.x, tmp.y, tmp.z);
+}
+
+Vector3 World::camera_world_to_screen(CameraInstance i, const Vector3& pos)
+{
+	const Camera& c = _camera[i.i];
+
+	const TransformInstance ti = _scene_graph->get(_camera[i.i].unit);
+	Matrix4x4 world_inv = _scene_graph->world_pose(ti);
+	invert(world_inv);
+
+	Vector4 xyzw;
+	xyzw.x = pos.x;
+	xyzw.y = pos.y;
+	xyzw.z = pos.z;
+	xyzw.w = 1.0f;
+
+	Vector4 clip = xyzw * (world_inv * c.projection);
+
+	Vector4 ndc;
+	ndc.x = clip.x / clip.w;
+	ndc.y = clip.y / clip.w;
+
+	Vector3 screen;
+	screen.x = (c.view_x + c.view_width  * (ndc.x + 1.0f)) / 2.0f;
+	screen.y = (c.view_y + c.view_height * (1.0f - ndc.y)) / 2.0f;
+	screen.z = 0.0f;
+
+	return screen;
+}
+
+SoundInstanceId World::play_sound(const SoundResource& sr, const bool loop, const float volume, const Vector3& pos, const float range)
 {
 	return _sound_world->play(sr, loop, volume, pos);
 }
@@ -177,7 +443,7 @@ SoundInstanceId World::play_sound(const SoundResource* sr, const bool loop, cons
 SoundInstanceId World::play_sound(StringId64 name, const bool loop, const float volume, const Vector3& pos, const float range)
 {
 	const SoundResource* sr = (const SoundResource*)_resource_manager->get(SOUND_TYPE, name);
-	return play_sound(sr, loop, volume, pos, range);
+	return play_sound(*sr, loop, volume, pos, range);
 }
 
 void World::stop_sound(SoundInstanceId id)
@@ -185,8 +451,9 @@ void World::stop_sound(SoundInstanceId id)
 	_sound_world->stop(id);
 }
 
-void World::link_sound(SoundInstanceId id, Unit* unit, int32_t node)
+void World::link_sound(SoundInstanceId /*id*/, UnitId /*unit*/, int32_t /*node*/)
 {
+	CE_ASSERT(false, "Not implemented yet");
 }
 
 void World::set_listener_pose(const Matrix4x4& pose)
@@ -209,61 +476,40 @@ void World::set_sound_volume(SoundInstanceId id, float vol)
 	_sound_world->set_sound_volumes(1, &id, &vol);
 }
 
-GuiId World::create_window_gui(uint16_t width, uint16_t height, const char* material)
-{
-	return _render_world->create_gui(width, height, material);
-}
-
-void World::destroy_gui(GuiId id)
-{
-	_render_world->destroy_gui(id);
-}
-
-Gui* World::get_gui(GuiId id)
-{
-	return _render_world->get_gui(id);
-}
-
 DebugLine* World::create_debug_line(bool depth_test)
 {
-	return CE_NEW(default_allocator(), DebugLine)(depth_test);
+	return CE_NEW(*_allocator, DebugLine)(depth_test);
 }
 
 void World::destroy_debug_line(DebugLine& line)
 {
-	CE_DELETE(default_allocator(), &line);
+	CE_DELETE(*_allocator, &line);
 }
 
-void World::load_level(const LevelResource* lr)
+Level* World::load_level(const LevelResource& lr, const Vector3& pos, const Quaternion& rot)
 {
-	using namespace level_resource;
-
-	uint32_t num = level_resource::num_units(lr);
-	for (uint32_t i = 0; i < num; i++)
-	{
-		const LevelUnit* lu = level_resource::get_unit(lr, i);
-		spawn_unit(lu->name, lu->position, lu->rotation);
-	}
-
-	num = level_resource::num_sounds(lr);
-	for (uint32_t i = 0; i < num; i++)
-	{
-		const LevelSound* ls = level_resource::get_sound(lr, i);
-		play_sound(ls->name, ls->loop, ls->volume, ls->position, ls->range);
-	}
+	Level* level = CE_NEW(*_allocator, Level)(*_allocator, *this, lr);
+	level->load(pos, rot);
 
+	array::push_back(_levels, level);
 	post_level_loaded_event();
+	return level;
 }
 
-void World::load_level(StringId64 name)
+Level* World::load_level(StringId64 name, const Vector3& pos, const Quaternion& rot)
 {
-	const LevelResource* lr = (LevelResource*) _resource_manager->get(LEVEL_TYPE, name);
-	load_level(lr);
+	const LevelResource* lr = (LevelResource*)_resource_manager->get(LEVEL_TYPE, name);
+	return load_level(*lr, pos, rot);
 }
 
-SpriteAnimationPlayer* World::sprite_animation_player()
+EventStream& World::events()
 {
-	return _sprite_animation_player;
+	return _events;
+}
+
+SceneGraph* World::scene_graph()
+{
+	return _scene_graph;
 }
 
 RenderWorld* World::render_world()
@@ -301,74 +547,38 @@ void World::post_level_loaded_event()
 	event_stream::write(_events, EventType::LEVEL_LOADED, ev);
 }
 
-void World::process_physics_events()
+void World::Camera::update_projection_matrix()
 {
-	EventStream& events = _physics_world->events();
-
-	// Read all events
-	const char* ee = array::begin(events);
-	while (ee != array::end(events))
+	switch (projection_type)
 	{
-		event_stream::Header h = *(event_stream::Header*) ee;
-
-		// CE_LOGD("=== PHYSICS EVENT ===");
-		// CE_LOGD("type = %d", h.type);
-		// CE_LOGD("size = %d", h.size);
-
-		const char* event = ee + sizeof(event_stream::Header);
-
-		switch (h.type)
+		case ProjectionType::ORTHOGRAPHIC:
 		{
-			case physics_world::EventType::COLLISION:
-			{
-				physics_world::CollisionEvent coll_ev = *(physics_world::CollisionEvent*) event;
-
-				// CE_LOGD("type    = %s", coll_ev.type == physics_world::CollisionEvent::BEGIN_TOUCH ? "begin" : "end");
-				// CE_LOGD("actor_0 = (%p)", coll_ev.actors[0]);
-				// CE_LOGD("actor_1 = (%p)", coll_ev.actors[1]);
-				// CE_LOGD("unit_0  = (%p)", coll_ev.actors[0]->unit());
-				// CE_LOGD("unit_1  = (%p)", coll_ev.actors[1]->unit());
-				// CE_LOGD("where   = (%f %f %f)", coll_ev.where.x, coll_ev.where.y, coll_ev.where.z);
-				// CE_LOGD("normal  = (%f %f %f)", coll_ev.normal.x, coll_ev.normal.y, coll_ev.normal.z);
-
-				_lua_environment->call_physics_callback(
-					coll_ev.actors[0],
-					coll_ev.actors[1],
-					(id_array::has(m_units, coll_ev.actors[0]->unit_id())) ? coll_ev.actors[0]->unit() : NULL,
-					(id_array::has(m_units, coll_ev.actors[1]->unit_id())) ? coll_ev.actors[1]->unit() : NULL,
-					coll_ev.where,
-					coll_ev.normal,
-					(coll_ev.type == physics_world::CollisionEvent::BEGIN_TOUCH) ? "begin" : "end");
-				break;
-			}
-			case physics_world::EventType::TRIGGER:
-			{
-				physics_world::TriggerEvent trigg_ev = *(physics_world::TriggerEvent*) event;
-
-				// CE_LOGD("type    = %s", trigg_ev.type == physics_world::TriggerEvent::BEGIN_TOUCH ? "begin" : "end");
-				// CE_LOGD("trigger = (%p)", trigg_ev.trigger);
-				// CE_LOGD("other   = (%p)", trigg_ev.other);
-
-				_lua_environment->call_trigger_callback(
-					trigg_ev.trigger,
-					trigg_ev.other,
-					(trigg_ev.type == physics_world::TriggerEvent::BEGIN_TOUCH ? "begin" : "end"));
-				break;
-			}
-			default:
-			{
-				CE_FATAL("Unknown Physics event");
-				break;
-			}
+			orthographic(projection
+				, left
+				, right
+				, bottom
+				, top
+				, near
+				, far
+				);
+			break;
+		}
+		case ProjectionType::PERSPECTIVE:
+		{
+			perspective(projection
+				, fov
+				, aspect
+				, near
+				, far
+				);
+			break;
+		}
+		default:
+		{
+			CE_FATAL("Oops, unknown projection type");
+			break;
 		}
-
-		// CE_LOGD("=====================");
-
-		// Next event
-		ee += sizeof(event_stream::Header) + h.size;
 	}
-
-	array::clear(events);
 }
 
 } // namespace crown

+ 120 - 59
src/world/world.h

@@ -5,25 +5,16 @@
 
 #pragma once
 
-#include "camera.h"
-#include "id_array.h"
-#include "linear_allocator.h"
-#include "physics_types.h"
-#include "physics_world.h"
-#include "pool_allocator.h"
-#include "render_world.h"
-#include "render_world_types.h"
-#include "types.h"
-#include "unit.h"
-#include "vector.h"
-#include "world_types.h"
-#include "sound_world.h"
+#include "audio_types.h"
 #include "event_stream.h"
-#include "sprite_animation_player.h"
-#include "resource_types.h"
-#include "quaternion.h"
+#include "graphics_types.h"
 #include "lua_types.h"
+#include "math_types.h"
+#include "physics_types.h"
+#include "resource_types.h"
 #include "string_id.h"
+#include "types.h"
+#include "world_types.h"
 
 namespace crown
 {
@@ -37,37 +28,82 @@ class World
 {
 public:
 
-	World(ResourceManager& rm, LuaEnvironment& env);
+	World(Allocator& a, ResourceManager& rm, LuaEnvironment& env, MaterialManager& mm, UnitManager& um);
 	~World();
 
+	UnitId spawn_unit(const UnitResource& ur, const Vector3& position = VECTOR3_ZERO, const Quaternion& rotation = QUATERNION_IDENTITY);
+
 	/// Spawns a new instance of the unit @a name at the given @a position and @a rotation.
-	UnitId spawn_unit(const UnitResource* ur, const Vector3& position = VECTOR3_ZERO, const Quaternion& rotation = QUATERNION_IDENTITY);
 	UnitId spawn_unit(StringId64 name, const Vector3& pos, const Quaternion& rot);
 
+	/// Spawns a new empty unit with the given @a id.
+	void spawn_empty_unit(UnitId id);
+
 	/// Destroys the unit with the given @a id.
 	void destroy_unit(UnitId id);
 
-	/// Reloads all the units with the associated resource @a old_ur.
-	void reload_units(UnitResource* old_ur, UnitResource* new_ur);
-
 	/// Returns the number of units in the world.
 	uint32_t num_units() const;
 
 	/// Returns all the the units in the world.
 	void units(Array<UnitId>& units) const;
 
-	/// Links the unit @a child to the @a node of the unit @a parent.
-	/// After this call, @a child will follow the @a parent unit.
-	void link_unit(UnitId child, UnitId parent);
+	/// Creates a new camera.
+	CameraInstance create_camera(UnitId id, const CameraDesc& cd);
+
+	/// Destroys the camera @a id.
+	void destroy_camera(CameraInstance i);
+
+	/// Returns the camera owned by unit @a id.
+	CameraInstance camera(UnitId id);
+
+	/// Sets the projection type of the camera.
+	void set_camera_projection_type(CameraInstance i, ProjectionType::Enum type);
+
+	/// Returns the projection type of the camera.
+	ProjectionType::Enum camera_projection_type(CameraInstance i) const;
+
+	/// Returns the projection matrix of the camera.
+	const Matrix4x4& camera_projection_matrix(CameraInstance i) const;
+
+	/// Returns the view matrix of the camera.
+	Matrix4x4 camera_view_matrix(CameraInstance i) const;
+
+	/// Returns the field-of-view of the camera in degrees.
+	float camera_fov(CameraInstance i) const;
+
+	/// Sets the field-of-view of the camera in degrees.
+	void set_camera_fov(CameraInstance i, float fov);
+
+	/// Returns the aspect ratio of the camera. (Perspective projection only.)
+	float camera_aspect(CameraInstance i) const;
+
+	/// Sets the aspect ratio of the camera. (Perspective projection only.)
+	void set_camera_aspect(CameraInstance i, float aspect);
+
+	/// Returns the near clip distance of the camera.
+	float camera_near_clip_distance(CameraInstance i) const;
+
+	/// Sets the near clip distance of the camera.
+	void set_camera_near_clip_distance(CameraInstance i, float near);
+
+	/// Returns the far clip distance of the camera.
+	float camera_far_clip_distance(CameraInstance i) const;
 
-	/// Unlinks the unit @a id from its parent if it has any.
-	void unlink_unit(UnitId id);
+	/// Sets the far clip distance of the camera.
+	void set_camera_far_clip_distance(CameraInstance i, float far);
 
-	/// Returns the unit @a id.
-	Unit* get_unit(UnitId id);
+	/// Sets the coordinates for orthographic clipping planes. (Orthographic projection only.)
+	void set_camera_orthographic_metrics(CameraInstance i, float left, float right, float bottom, float top);
 
-	/// Returns the camera @a id.
-	Camera* get_camera(CameraId id);
+	/// Sets the coordinates for the camera viewport in pixels.
+	void set_camera_viewport_metrics(CameraInstance i, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
+
+	/// Returns @a pos from screen-space to world-space coordinates.
+	Vector3 camera_screen_to_world(CameraInstance i, const Vector3& pos);
+
+	/// Returns @a pos from world-space to screen-space coordinates.
+	Vector3 camera_world_to_screen(CameraInstance i, const Vector3& pos);
 
 	/// Update all animations with @a dt.
 	void update_animations(float dt);
@@ -78,17 +114,13 @@ public:
 	/// Updates all units and sub-systems with the given @a dt delta time.
 	void update(float dt);
 
-	/// Renders the world form the point of view of the given @a camera.
-	void render(Camera* camera);
-
-	CameraId create_camera(SceneGraph& sg, UnitId id, ProjectionType::Enum type, float near, float far);
+	/// Renders the world form the point of view of camera @a i.
+	void render(CameraInstance i);
 
-	/// Destroys the camera @a id.
-	void destroy_camera(CameraId id);
+	SoundInstanceId play_sound(const SoundResource& sr, bool loop = false, float volume = 1.0f, const Vector3& position = VECTOR3_ZERO, float range = 50.0f);
 
 	/// Plays the sound with the given @a name at the given @a position, with the given
 	/// @a volume and @a range. @a loop controls whether the sound must loop or not.
-	SoundInstanceId play_sound(const SoundResource* sr, bool loop = false, float volume = 1.0f, const Vector3& position = VECTOR3_ZERO, float range = 50.0f);
 	SoundInstanceId play_sound(StringId64 name, const bool loop, const float volume, const Vector3& pos, const float range);
 
 	/// Stops the sound with the given @a id.
@@ -96,7 +128,7 @@ public:
 
 	/// Links the sound @a id to the @a node of the given @a unit.
 	/// After this call, the sound @a id will follow the unit @a unit.
-	void link_sound(SoundInstanceId id, Unit* unit, int32_t node);
+	void link_sound(SoundInstanceId id, UnitId unit, int32_t node);
 
 	/// Sets the @a pose of the listener.
 	void set_listener_pose(const Matrix4x4& pose);
@@ -110,15 +142,6 @@ public:
 	/// Sets the @a volume of the sound @a id.
 	void set_sound_volume(SoundInstanceId id, float volume);
 
-	/// Creates a new window-space Gui of size @a width and @a height.
-	GuiId create_window_gui(uint16_t width, uint16_t height, const char* material);
-
-	/// Destroys the gui with the given @a id.
-	void destroy_gui(GuiId id);
-
-	/// Returns the gui @a id.
-	Gui* get_gui(GuiId id);
-
 	/// Creates a new DebugLine. @a depth_test controls whether to
 	/// enable depth test when rendering the lines.
 	DebugLine* create_debug_line(bool depth_test);
@@ -127,10 +150,14 @@ public:
 	void destroy_debug_line(DebugLine& line);
 
 	/// Loads the level @a name into the world.
-	void load_level(const LevelResource* lr);
-	void load_level(StringId64 name);
+	Level* load_level(const LevelResource& lr, const Vector3& pos, const Quaternion& rot);
+	Level* load_level(StringId64 name, const Vector3& pos, const Quaternion& rot);
+
+	/// Returns the events.
+	EventStream& events();
 
-	SpriteAnimationPlayer* sprite_animation_player();
+	/// Returns the scene graph.
+	SceneGraph* scene_graph();
 
 	/// Returns the rendering sub-world.
 	RenderWorld* render_world();
@@ -141,32 +168,66 @@ public:
 	/// Returns the sound sub-world.
 	SoundWorld* sound_world();
 
+public:
+
+	enum { MARKER = 0xfb6ce2d3 };
+
 private:
 
+	CameraInstance make_camera_instance(uint32_t i) { CameraInstance inst = { i }; return inst; }
+
 	void post_unit_spawned_event(UnitId id);
 	void post_unit_destroyed_event(UnitId id);
 	void post_level_loaded_event();
-	void process_physics_events();
 
 private:
 
-	ResourceManager* _resource_manager;
-	LuaEnvironment* _lua_environment;
+	struct Camera
+	{
+		UnitId unit;
+
+		ProjectionType::Enum projection_type;
+		Matrix4x4 projection;
+
+		Frustum frustum;
+		float fov;
+		float aspect;
+		float near;
+		float far;
+
+		// Orthographic projection only
+		float left;
+		float right;
+		float bottom;
+		float top;
+
+		uint16_t view_x;
+		uint16_t view_y;
+		uint16_t view_width;
+		uint16_t view_height;
 
-	PoolAllocator m_unit_pool;
-	PoolAllocator m_camera_pool;
+		void update_projection_matrix();
+	};
 
-	IdArray<CE_MAX_UNITS, Unit*> m_units;
-	IdArray<CE_MAX_CAMERAS, Camera*> m_cameras;
+	uint32_t _marker;
 
+	Allocator* _allocator;
+	ResourceManager* _resource_manager;
+	LuaEnvironment* _lua_environment;
+	UnitManager* _unit_manager;
+
+	DebugLine* _lines;
 	SceneGraph* _scene_graph;
-	SpriteAnimationPlayer* _sprite_animation_player;
 	RenderWorld* _render_world;
 	PhysicsWorld* _physics_world;
 	SoundWorld* _sound_world;
 
+	Array<UnitId> _units;
+	Array<Level*> _levels;
+	Array<Camera> _camera;
+	Hash<uint32_t> _camera_map;
+
 	EventStream _events;
-	DebugLine* _lines;
 };
 
 } // namespace crown

+ 82 - 7
src/world/world_types.h

@@ -6,18 +6,88 @@
 #pragma once
 
 #include "types.h"
+#include "math_types.h"
 
 namespace crown
 {
 
-typedef Id UnitId;
-typedef Id CameraId;
-
-struct Unit;
+struct UnitManager;
 struct SceneGraph;
-struct Camera;
-class WorldManager;
 class World;
+class Level;
+
+#define UNIT_INDEX_BITS 22
+#define UNIT_INDEX_MASK 0x003fffff
+#define UNIT_ID_BITS    8
+#define UNIT_ID_MASK    0x3fc00000
+
+struct UnitId
+{
+	uint32_t idx;
+
+	uint32_t index() const
+	{
+		return idx & UNIT_INDEX_MASK;
+	}
+
+	uint32_t id() const
+	{
+		return (idx >> UNIT_INDEX_BITS) & UNIT_ID_MASK;
+	}
+
+	uint32_t encode() const
+	{
+		return idx;
+	}
+
+	void decode(uint32_t id)
+	{
+		idx = id;
+	}
+
+	bool is_valid()
+	{
+		return idx != UINT32_MAX;
+	}
+};
+
+inline UnitId INVALID_UNIT() { UnitId id = { UINT32_MAX }; return id; }
+
+struct TransformInstance
+{
+	uint32_t i;
+};
+
+struct CameraInstance
+{
+	uint32_t i;
+};
+
+struct ProjectionType
+{
+	enum Enum
+	{
+		ORTHOGRAPHIC,
+		PERSPECTIVE,
+
+		COUNT
+	};
+};
+
+struct TransformDesc
+{
+	Vector3 position;
+	Quaternion rotation;
+	Vector3 scale;
+};
+
+struct CameraDesc
+{
+	uint32_t type; // ProjectionType::Enum
+	float fov;
+	float near_range;
+	float far_range;
+};
 
 struct EventType
 {
@@ -26,7 +96,12 @@ struct EventType
 		UNIT_SPAWNED,
 		UNIT_DESTROYED,
 
-		LEVEL_LOADED
+		LEVEL_LOADED,
+
+		PHYSICS_COLLISION,
+		PHYSICS_TRIGGER,
+
+		COUNT
 	};
 };