Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Main/scenegraph.cpp')
-rw-r--r--Source/Main/scenegraph.cpp3179
1 files changed, 3179 insertions, 0 deletions
diff --git a/Source/Main/scenegraph.cpp b/Source/Main/scenegraph.cpp
new file mode 100644
index 00000000..ab4bcafb
--- /dev/null
+++ b/Source/Main/scenegraph.cpp
@@ -0,0 +1,3179 @@
+//-----------------------------------------------------------------------------
+// Name: scenegraph.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scenegraph.h"
+
+#include <Graphics/decaltextures.h>
+#include <Graphics/flares.h>
+#include <Graphics/graphics.h>
+#include <Graphics/models.h>
+#include <Graphics/sky.h>
+#include <Graphics/shaders.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/particles.h>
+#include <Graphics/geometry.h>
+
+#include <Objects/envobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/terrainobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/hotspot.h>
+#include <Objects/group.h>
+#include <Objects/itemobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/navmeshhintobject.h>
+#include <Objects/navmeshregionobject.h>
+#include <Objects/navmeshconnectionobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/reflectioncaptureobject.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletobject.h>
+#include <Physics/physics.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Internal/snprintf.h>
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+#include <Internal/config.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/engine.h>
+#include <Internal/stopwatch.h>
+#include <Compat/fileio.h>
+#include <AI/navmesh.h>
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <Sound/sound.h>
+#include <Game/level.h>
+#include <Utility/assert.h>
+#include <GUI/gui.h>
+
+#include <SDL.h>
+
+#include <cassert>
+#include <algorithm>
+#include <cstring>
+
+extern const bool kUseShadowCache;
+static const bool _draw_collision_shapes = false;
+
+extern bool g_draw_vr;
+extern bool g_simple_water;
+extern bool g_disable_fog;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_albedo_only;
+extern bool g_no_reflection_capture;
+extern bool g_no_detailmaps;
+extern bool g_no_decals;
+extern bool g_no_decal_elements;
+extern bool g_character_decals_enabled;
+extern bool g_attrib_envobj_intancing_support;
+extern bool g_attrib_envobj_intancing_enabled;
+extern bool g_ubo_batch_multiplier_force_1x;
+
+const int kGlobalShaderSuffixLen = 1024;
+char global_shader_suffix_storage[kGlobalShaderSuffixLen];
+char* global_shader_suffix = &global_shader_suffix_storage[0];
+
+extern bool g_debug_runtime_disable_scene_graph_draw;
+extern bool g_debug_runtime_disable_scene_graph_draw_depth_map;
+extern bool g_debug_runtime_disable_scene_graph_prepare_lights_and_decals;
+
+static UniformRingBuffer uniform_ring_buffer;
+
+using std::max;
+using std::min;
+
+// These MUST match shader EXACTLY, be careful when changing them
+
+// This is ClusterInfo in the shader
+struct ShaderClusterInfo {
+ unsigned int grid_size[3];
+ unsigned int num_decals;
+ unsigned int num_lights;
+ unsigned int light_cluster_data_offset;
+ unsigned int light_data_offset;
+ unsigned int cluster_width;
+ float inv_proj_mat[16];
+ float viewport[4];
+ float z_near;
+ float z_mult;
+ float pad3;
+ float pad4;
+};
+
+
+// This is in decal data texture buffer
+// must be floats only and divisible by 4
+struct ShaderDecal {
+ float decal_scale[3];
+ float decal_spawn_time;
+ float decal_rotation[4]; // quaternion
+ float decal_position[3];
+ float decal_pad1;
+ float decal_tint[4];
+ float decal_uv[4];
+ float decal_normal[4];
+};
+
+
+// This is PointLightData in the shader
+// also must be floats only and divisible by 4
+struct ShaderLight {
+ float pos[3];
+ float radius;
+ float color[3];
+ float padding;
+};
+
+
+struct DetailObjectSurfaceDrawCall {
+ EnvObject* draw_owner;
+ EnvObject** instance_array;
+ int num_instances;
+};
+
+#define NUM_GRID_COMPONENTS 2
+
+
+SceneGraph::SceneGraph()
+ :
+ particle_system(NULL),
+ terrain_object_(NULL),
+ num_update_objects(0),
+ bullet_world_(NULL),
+ abstract_bullet_world_(NULL),
+ plant_bullet_world_(NULL),
+ queued_level_reset_(false),
+ nav_mesh_(NULL),
+ haze_mult(0.0008f),
+ fog_amount(1.0f)
+ , level_has_been_previously_saved_(false)
+ , cluster_size(128)
+ , num_z_clusters(16)
+ , destruction_sanity_insert_position(0)
+ , destruction_memory_insert_position(0)
+ , infreq_update_index(0)
+ , partial_object_loop_counter(0)
+ , reflection_data_loaded(false)
+ , hotspots_modified_(false)
+{
+ memset(destruction_sanity,0,destruction_sanity_size*sizeof(Object*));
+ for( unsigned i = 0; i < destruction_memory_size; i++ ) {
+ destruction_memory_ids[i] = -1;
+ }
+ memset(destruction_memory_strings,'\0',destruction_memory_size*destruction_memory_string_size);
+
+ const unsigned int num_floats = 3 * 4 + 3 * 4;
+ LOG_ASSERT_EQ(num_floats * sizeof(float), sizeof(ShaderDecal));
+ decal_tbo.resize(kMaxDecals * num_floats, 0.0f);
+
+ // this is mainly used to accumulate decal cluster info before copying to GPU
+ // but also here during initialization to fill buffers with 0
+ decal_cluster_buffer.resize(4096, 0);
+ const char *empty = reinterpret_cast<const char *>(&decal_cluster_buffer[0]);
+
+ decal_data_texture = Textures::Instance()->makeBufferTexture(kMaxDecals * sizeof(ShaderDecal), GL_RGBA32F);
+ Textures::Instance()->SetTextureName(decal_data_texture, "Decal Data");
+ // TODO: should use 3D texture instead
+ decal_cluster_texture = Textures::Instance()->makeBufferTexture(decal_cluster_buffer.size() * 4, GL_R32UI, empty);
+ Textures::Instance()->SetTextureName(decal_cluster_texture, "Decal Clusters");
+}
+
+SceneGraph::~SceneGraph() {
+ LOG_ASSERT(bullet_world_ == NULL);
+ LOG_ASSERT(abstract_bullet_world_ == NULL);
+ LOG_ASSERT(plant_bullet_world_ == NULL);
+ LOG_ASSERT(nav_mesh_ == NULL);
+ LOG_ASSERT(particle_system == NULL);
+ LOG_ASSERT(level == NULL);
+ LOG_ASSERT(map_editor == NULL);
+ LOG_ASSERT(sky == NULL);
+ SDL_assert(bullet_world_ == NULL);
+ SDL_assert(abstract_bullet_world_ == NULL);
+ SDL_assert(plant_bullet_world_ == NULL);
+ SDL_assert(nav_mesh_ == NULL);
+ SDL_assert(particle_system == NULL);
+ SDL_assert(level == NULL);
+ SDL_assert(map_editor == NULL);
+ SDL_assert(sky == NULL);
+}
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+static void SetIdToObjectMapValue(std::vector<Object*>& object_from_id_map_, int id, Object* value) {
+ if(object_from_id_map_.size() <= id)
+ {
+ object_from_id_map_.resize((id + 1) * 2);
+ }
+ object_from_id_map_[id] = value;
+}
+
+static Object* GetIdToObjectMapValue(std::vector<Object*>& object_from_id_map_, int id) {
+ Object* result = NULL;
+ if(id >= 0 && id < object_from_id_map_.size())
+ {
+ result = object_from_id_map_[id];
+ }
+ return result;
+}
+
+void SceneGraph::AssignID(Object* obj) {
+ int temp_id = obj->GetID();
+ bool say_new_id = false;
+
+ //The id 0 is reserved for the terrain.
+ if( temp_id == 0 && obj->GetType() != _terrain_type ) {
+ temp_id = -1;
+ }
+
+ if(temp_id != -1) {
+ Object* obj_with_id = GetObjectFromID(temp_id);
+ if( temp_id < 0 || (obj_with_id != obj && obj_with_id != NULL) ) {
+ if( temp_id != -1 ) {
+ LOGI << "Object has a taken id: " << *obj << " giving it a new id" << std::endl;
+ LOGI << "It collided with: " << *obj_with_id << std::endl;
+ say_new_id = true;
+ }
+ temp_id = -1;
+ }
+ }
+
+ int id;
+ if(temp_id != -1){
+ id = temp_id;
+ } else {
+ id = GetAndReserveID();
+ if( say_new_id) {
+ LOGI << "Gave object id " << id << std::endl;
+ }
+ obj->SetID(id);
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, obj);
+}
+
+bool SceneGraph::addObject(Object* new_object) {
+ LinkObject(new_object);
+ new_object->scenegraph_ = this;
+ if( new_object->Initialize() == false ) {
+ LOGE << "Failed at initializing object on addObject in scenegraph, unlinking. Type: " << CStringFromEntityType(new_object->GetType()) << " name: " << new_object->GetName() << std::endl;
+ UnlinkObject(new_object);
+ return false;
+ }
+ new_object->GetShaderNames(preload_shaders);
+ return true;
+}
+
+void SceneGraph::LinkObject(Object* new_object) {
+ for( size_t i = 0; i < destruction_sanity_size; i++ ) {
+ if( destruction_sanity[i] == new_object ) {
+ destruction_sanity[i] = NULL;
+ }
+ }
+
+ for( size_t i = 0; i < destruction_memory_size; i++ ) {
+ if( destruction_memory_ids[i] == new_object->GetID() ) {
+ destruction_memory_ids[i] = 0;
+ }
+ }
+
+ objects_.push_back(new_object);
+ if(new_object->collidable){
+ collide_objects_.push_back(new_object);
+ }
+ if(new_object->GetType() != _light_probe_object || new_object->GetType() != _dynamic_light_object) {
+ visible_objects_.push_back(new_object);
+ if(new_object->GetType() == _env_object){
+ visible_static_meshes_.push_back((EnvObject*)new_object);
+ ShadowCacheObjectLightBounds env_object_light_bounds = {0};
+ visible_static_meshes_shadow_cache_bounds_.push_back(env_object_light_bounds);
+ visible_static_mesh_indices_.push_back(visible_static_mesh_indices_.size());
+ visible_objects_need_sort = true;
+ }
+ }
+ switch(new_object->GetType()){
+ case _terrain_type:
+ {
+ terrain_objects_.push_back((TerrainObject*)new_object);
+ ShadowCacheObjectLightBounds terrain_light_bounds = {0};
+ terrain_objects_shadow_cache_bounds_.push_back(terrain_light_bounds);
+ break;
+ }
+ case _movement_object:
+ movement_objects_.push_back(new_object); break;
+ case _item_object:
+ item_objects_.push_back(new_object); break;
+ case _decal_object:
+ decal_objects_.push_back(new_object); break;
+ case _hotspot_object:
+ hotspots_.push_back((Hotspot*)(new_object));
+ hotspots_modified_ = true;
+ break;
+ case _navmesh_hint_object:
+ navmesh_hints_.push_back((NavmeshHintObject*)(new_object)); break;
+ case _navmesh_connection_object:
+ navmesh_connections_.push_back((NavmeshConnectionObject*)(new_object)); break;
+ case _path_point_object:
+ path_points_.push_back(new_object); break;
+ case _light_volume_object:
+ light_volume_objects_.push_back((LightVolumeObject*)new_object); break;
+ default:
+ break;
+ }
+ int id = new_object->GetID();
+ if(id != -1){
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, id);
+
+ if( object_at_id != NULL )
+ {
+ if( new_object != object_at_id )
+ {
+ if( object_at_id != NULL && new_object != NULL )
+ {
+ LOGD << "Overwriting already assigned object id: " << *(object_at_id)
+ << " with the different object: " << *new_object << std::endl;
+ }
+ }
+ else
+ {
+ LOGD << "Rewriting already assigned object id: " << *(object_at_id) << std::endl;
+ }
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, new_object);
+ }
+}
+
+void SceneGraph::QueueLevelReset() {
+ queued_level_reset_ = true;
+}
+
+Collision SceneGraph::lineCheck(const vec3 &start, const vec3 &end) {
+ std::vector<Collision> collisions;
+ LineCheckAll(start, end, &collisions);
+ Collision *closest = NULL;
+ float closest_distance;
+ for(std::vector<Collision>::iterator it = collisions.begin(); it != collisions.end(); ++it) {
+ Collision &c = *it;
+ float dist = distance_squared(start, c.hit_where);
+ if(!closest || dist < closest_distance){
+ closest = &c;
+ closest_distance = dist;
+ }
+ }
+ if(closest){
+ return *closest;
+ } else {
+ return Collision();
+ }
+}
+
+Collision SceneGraph::lineCheckCollidable(const vec3 &start, const vec3 &end, Object* not_hit) {
+ std::vector<Collision> collisions;
+ LineCheckAll(start, end, &collisions);
+ Collision *closest = NULL;
+ float closest_distance;
+ for(std::vector<Collision>::iterator it = collisions.begin(); it != collisions.end(); ++it) {
+ Collision &c = *it;
+ if(c.hit_what->collidable && c.hit_what != not_hit){
+ float dist = distance_squared(start, c.hit_where);
+ if(!closest || dist < closest_distance){
+ closest = &c;
+ closest_distance = dist;
+ }
+ }
+ }
+ if(closest){
+ return *closest;
+ } else {
+ return Collision();
+ }
+}
+
+void SceneGraph::LineCheckAll(const vec3 &start, const vec3 &end, std::vector<Collision> *collisions) {
+ PROFILER_ZONE(g_profiler_ctx, "SceneGraph::LineCheckAll");
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ Object* obj = *it;
+ vec3 point, normal;
+ int collision_tri = obj->lineCheck(start, end, &point, &normal);
+ if(collision_tri != -1) {
+ Collision c;
+ c.hit = true;
+ c.hit_normal = normal;
+ c.hit_what = obj;
+ c.hit_how = collision_tri;
+ c.hit_where = point;
+ collisions->push_back(c);
+ }
+ }
+}
+
+static std::vector<EnvObject*>* g_static_mesh_draw_sort_entries = NULL;
+
+static bool StaticMeshDrawSortIndices(uint16_t a_index, uint16_t b_index){
+ const EnvObject* a = (*g_static_mesh_draw_sort_entries)[a_index];
+ const EnvObject* b = (*g_static_mesh_draw_sort_entries)[b_index];
+ if(a->transparent != b->transparent) {
+ return a->transparent < b->transparent;
+ } else if(a->ofr->shader_name != b->ofr->shader_name){
+ return a->ofr->shader_name < b->ofr->shader_name;
+ } else if(a->ofr->path_ != b->ofr->path_) {
+ return a->ofr->path_ < b->ofr->path_;
+ } else {
+ return a->winding_flip < b->winding_flip;
+ }
+}
+
+static void DrawQuad(int shader_id) {
+ Shaders* shaders = Shaders::Instance();
+ Graphics *graphics = Graphics::Instance();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+}
+
+extern VBOContainer quad_vert_vbo;
+extern VBOContainer quad_index_vbo;
+
+static void UpdateShaderSuffix(SceneGraph* scenegraph, Object::DrawType object_draw_type) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "");
+ if(object_draw_type == Object::kFullDraw){
+ if(g_simple_shadows || !g_level_shadows){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SIMPLE_SHADOW", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->light_probe_collection.probe_lighting_enabled &&
+ scenegraph->light_probe_collection.light_probe_buffer_object_id != -1 &&
+ !scenegraph->light_probe_collection.light_probes.empty())
+ {
+ FormatString(shader_str[1], kShaderStrSize, "%s #CAN_USE_LIGHT_PROBES", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_albedo_only){
+ FormatString(shader_str[1], kShaderStrSize, "%s #ALBEDO_ONLY", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_simple_water){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SIMPLE_WATER", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_disable_fog){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DISABLE_FOG", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_reflection_capture){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_REFLECTION_CAPTURE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_detailmaps){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_DETAILMAPS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_decals){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_DECALS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(config["ssao"].toNumber<bool>()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SSAO_TEST", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(config["volume_shadows"].toNumber<bool>()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #VOLUME_SHADOWS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(object_draw_type == Object::kFullDraw || object_draw_type == Object::kDrawDepthOnly || object_draw_type == Object::kDrawDepthNoAA || Object::kDrawAllShadowCascades) {
+ static int ubo_batch_size_multiplier = 1;
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1) {
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+
+ if(max_ubo_size >= 131072) {
+ ubo_batch_size_multiplier = 8;
+ } else if(max_ubo_size >= 65536) {
+ ubo_batch_size_multiplier = 4;
+ } else if(max_ubo_size >= 32768) {
+ ubo_batch_size_multiplier = 2;
+ }
+ }
+
+ if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 8) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_8X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ } else if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 4) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_4X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ } else if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 2) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_2X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ if(g_attrib_envobj_intancing_support && g_attrib_envobj_intancing_enabled) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #ATTRIB_ENVOBJ_INSTANCING", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(Graphics::Instance()->use_sample_alpha_to_coverage && object_draw_type != Object::kDrawDepthNoAA && object_draw_type != Object::kDrawAllShadowCascades){
+ FormatString(shader_str[1], kShaderStrSize, "%s #ALPHA_TO_COVERAGE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(object_draw_type != Object::kFullDraw){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DEPTH_ONLY #NO_INSTANCE_ID", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(object_draw_type == Object::kDrawAllShadowCascades){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SHADOW_CASCADE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->light_probe_collection.light_volume_enabled && scenegraph->light_probe_collection.ambient_3d_tex.valid()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #CAN_USE_3D_TEX", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(!(Graphics::Instance()->config_.motion_blur_amount_ > 0.01f)){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_VELOCITY_BUF", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->level->script_params().HasParam("Custom Shader") && config["custom_level_shaders"].toNumber<bool>()){
+ const std::string& custom_shader = scenegraph->level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], custom_shader.c_str());
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(Graphics::Instance()->config_.simple_fog()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#SIMPLE_FOG");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ for(int length = strlen(shader_str[0]), i = 0;
+ i < length && shader_str[0][0] == ' ';
+ ++i, shader_str[0]++);
+
+ FormatString(global_shader_suffix_storage, kGlobalShaderSuffixLen, "%s", shader_str[0]);
+}
+
+extern bool last_ofr_is_valid;
+
+//Draw all objects
+void SceneGraph::Draw(SceneGraph::SceneDrawType scene_draw_type) {
+ if (g_debug_runtime_disable_scene_graph_draw) {
+ return;
+ }
+
+ Graphics *graphics = Graphics::Instance();
+ Camera* camera = ActiveCameras::Get();
+
+ UpdateShaderSuffix(this, Object::kFullDraw);
+
+ graphics->setDepthFunc(GL_LEQUAL);
+ if(nav_mesh_ && (nav_mesh_renderer_.IsNavMeshVisible() || nav_mesh_renderer_.IsCollisionMeshVisible())){
+ glClearColor(0.4f,0.4f,0.4f,1.0f);
+ graphics->Clear(true);
+
+ nav_mesh_renderer_.Draw();
+ }
+
+ if(graphics->queued_screenshot && graphics->screenshot_mode == Graphics::kTransparentGameplay){
+ glClearColor(0.0f,0.0f,0.0f,0.0f);
+ graphics->Clear(true);
+ }
+
+ // List static meshes
+ PROFILER_ENTER(g_profiler_ctx, "Draw env object batches");
+ if(visible_objects_need_sort){
+ PROFILER_ENTER(g_profiler_ctx, "Sort visible objects");
+ g_static_mesh_draw_sort_entries = &visible_static_meshes_;
+ std::sort(visible_static_mesh_indices_.begin(), visible_static_mesh_indices_.end(), StaticMeshDrawSortIndices);
+ visible_objects_need_sort = false;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+ static std::vector<EnvObject*> static_meshes_to_draw;
+ static_meshes_to_draw.clear();
+ static_meshes_to_draw.reserve(visible_static_meshes_.size());
+ PROFILER_ENTER(g_profiler_ctx, "List visible objects / frustum cull");
+ if( !nav_mesh_renderer_.IsCollisionMeshVisible() )
+ {
+ for(std::vector<uint16_t>::iterator it = visible_static_mesh_indices_.begin(); it != visible_static_mesh_indices_.end(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(!eo->transparent && eo->enabled_) {
+ if(camera->checkSphereInFrustum(eo->sphere_center_, eo->sphere_radius_)){
+ static_meshes_to_draw.push_back(eo);
+ }
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Issue draw calls");
+ mat4 proj_view_mat = camera->GetProjMatrix() * camera->GetViewMatrix();
+ mat4 prev_proj_view_mat = camera->GetProjMatrix() * camera->prev_view_mat;
+ vec3 cam_pos = camera->GetPos();
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = camera->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ int batch_start = 0;
+
+ if(!graphics->drawing_shadow && !(graphics->queued_screenshot && graphics->screenshot_mode == 1)){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw sky");
+ sky->Draw(this);
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw flares");
+ flares.Draw(Flares::kDiffuse);
+ }
+
+ static std::vector<DetailObjectSurfaceDrawCall> detail_objects_surfaces_to_draw;
+ detail_objects_surfaces_to_draw.clear();
+ detail_objects_surfaces_to_draw.reserve(visible_static_meshes_.size());
+
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kFullDraw);
+ if(static_meshes_to_draw[i-1]->HasDetailObjectSurfaces()){
+ detail_objects_surfaces_to_draw.push_back({ static_meshes_to_draw[i-1], &static_meshes_to_draw[batch_start], i-batch_start });
+ }
+ batch_start = i;
+ }
+ }
+ for(int i = 0; i < detail_objects_surfaces_to_draw.size(); ++i) {
+ auto current = detail_objects_surfaces_to_draw[i];
+ current.draw_owner->DrawDetailObjectInstances(current.instance_array, current.num_instances, Object::kFullDraw);
+ }
+ if(g_draw_collision){
+ batch_start = 0;
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ if(static_meshes_to_draw[i-1]->ofr->bush_collision){
+ if(static_meshes_to_draw[i-1]->plant_component_.get()){
+ HullCache* cache = static_meshes_to_draw[i-1]->plant_component_->GetHullCache();
+ if(cache){
+ for(int instance = batch_start; instance<i; ++instance){
+ EnvObject* eo = static_meshes_to_draw[instance];
+ for(unsigned face_index=0; face_index < cache->faces.size(); face_index+=3){
+ for(int side=0; side<3; ++side){
+ DebugDraw::Instance()->AddLine(eo->GetTransform() * cache->verts[cache->faces[face_index+side]], eo->GetTransform() * cache->verts[cache->faces[face_index+(side+1)%3]], vec4(0.0f,1.0f,0.0f,1.0f), _delete_on_draw);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kWireframe);
+ // Intentionally not drawing detail object surfaces
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+ batch_start = i;
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ {
+ // Update whether character decals should be enabled/disabled
+ bool character_decals_enabled = false;
+ if(config["custom_level_shaders"].toNumber<bool>() && level->script_params().HasParam("Custom Shader")){
+ const std::string& custom_shader = level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ character_decals_enabled = custom_shader.find("#CHARACTER_DECALS") != std::string::npos;
+ }
+ }
+ g_character_decals_enabled = character_decals_enabled;
+ }
+
+ {
+ //First non transparent, then transparent objects.
+ PROFILER_ZONE(g_profiler_ctx, "Draw objects");
+ for(object_list::iterator it = visible_objects_.begin(); it != visible_objects_.end(); ++it) {
+ Object& obj = *(*it);
+ const EntityType& obj_type = obj.GetType();
+ switch(obj_type){
+ case _decal_object:
+ case _env_object:
+ case _hotspot_object:
+ case _group:
+ continue;
+ case _terrain_type:
+ break;
+ default:
+ if(scene_draw_type == kStaticOnly){
+ continue;
+ }
+ }
+ if(obj.enabled_ && !obj.transparent) {
+ PROFILER_GPU_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(obj.GetType()),obj.GetID());
+ bool output_velocity_buffer = (graphics->config_.motion_blur_amount_ > 0.01f)
+ && ( (obj_type == _movement_object) || (obj_type == _item_object) );
+ if (output_velocity_buffer) {
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(2, buffers);
+ }
+ //We want to hide the terrain if we're rendering the navmesh.
+ if( obj_type == _terrain_type && nav_mesh_renderer_.IsCollisionMeshVisible() ) {
+ continue;
+ }
+ obj.ReceiveObjectMessage(OBJECT_MSG::DRAW);
+ if(output_velocity_buffer){
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(1, buffers);
+ }
+ }
+ }
+
+ bool draw_transparent = false;
+ if(!nav_mesh_renderer_.IsCollisionMeshVisible()){
+ // Iterating backwards because transparent objects are last in the list
+ for(std::vector<uint16_t>::reverse_iterator it = visible_static_mesh_indices_.rbegin(); it != visible_static_mesh_indices_.rend(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(eo->enabled_){
+ // Whatever the last enabled object in the list is will determine if there are any transparent objects
+ draw_transparent = eo->transparent;
+ break;
+ }
+ }
+ }
+
+ if(draw_transparent) {
+ if(!g_simple_water)
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update depth/color textures");
+ if (graphics->multisample_framebuffer_exists) {
+ PROFILER_ZONE(g_profiler_ctx, "Blit from MSAA buffers");
+ graphics->PushFramebuffer();
+ graphics->BlitColorBuffer();
+ graphics->BlitDepthBuffer();
+ graphics->PopFramebuffer();
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Copy screen_color_tex to temp_screen_tex");
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+
+ graphics->PushViewport();
+ graphics->PushFramebuffer();
+
+ graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+
+ int shader_id = shaders->returnProgram(graphics->post_shader_name);
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+
+ textures->bindTexture(graphics->screen_color_tex);
+ graphics->framebufferColorTexture2D(graphics->post_effects.temp_screen_tex);
+ quad_vert_vbo.Bind();
+ quad_index_vbo.Bind();
+ DrawQuad(shader_id);
+
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Draw transparent objects");
+ for(std::vector<EnvObject*>::iterator it = visible_static_meshes_.begin(); it != visible_static_meshes_.end(); ++it) {
+ EnvObject& obj = *(*it);
+ if(obj.enabled_ && obj.transparent){
+ PROFILER_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(((Object*)&obj)->GetType()),obj.GetID());
+ // Avoid calling EnvObject::Draw repeatedly, so matrices etc can be shared instead of reacquired for every draw call
+ // TODO: last_ofr_is_valid is set to false in EnvObject::Draw - is it important?
+ obj.DrawInstances(&(*it), 1, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kFullDraw);
+ obj.DrawDetailObjectInstances(&(*it), 1, Object::kFullDraw);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ if(graphics->use_sample_alpha_to_coverage){
+ glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+ }
+ if(!Graphics::Instance()->media_mode() && map_editor->state_ != MapEditor::kInGame){
+ for(object_list::iterator it = decal_objects_.begin(); it != decal_objects_.end(); ++it) {
+ Object &obj = *(*it);
+ if(obj.enabled_ && obj.Selected() && obj.editor_visible) {
+ PROFILER_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(obj.GetType()),obj.GetID());
+ obj.ReceiveObjectMessage(OBJECT_MSG::DRAW);
+ }
+ }
+ }
+ }
+ if(_draw_collision_shapes){
+ if( bullet_world_ )
+ bullet_world_->Draw(sky->GetSpecularCubeMapTexture());
+ //abstract_bullet_world->Draw();
+ //plant_bullet_world->Draw();
+ }
+
+ // Draw hotspots
+ if(map_editor->state_ == MapEditor::kInGame || map_editor->IsTypeEnabled(_hotspot_object)){
+ PROFILER_ZONE(g_profiler_ctx,"Draw Hotspots");
+ for (object_list::iterator it = hotspots_.begin(); it != hotspots_.end(); ++it) {
+ ((Hotspot*)(*it))->Draw();
+ }
+ }
+ {
+ if(!bullet_world_){
+ FatalError("Error", "No bullet world");
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw particles");
+ particle_system->Draw(this);
+ if(level->script_params().HasParam("GPU Particle Field") && config["particle_field"].toNumber<bool>()){
+ const std::string& gpu_particle_field = level->script_params().GetStringVal("GPU Particle Field");
+ if(!gpu_particle_field.empty()) {
+ DrawGPUParticleField(this, gpu_particle_field.c_str());
+ }
+ }
+ }
+}
+
+Object* SceneGraph::GetLastSelected() {
+ Object * res = NULL;
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( res == NULL || objects_[i]->Selected() > res->Selected() ) {
+ res = objects_[i];
+ }
+ }
+ return res;
+}
+
+void SceneGraph::ReturnSelected(std::vector<Object*>* selected) {
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( objects_[i]->Selected() ) {
+ selected->push_back(objects_[i]);
+ }
+ }
+}
+
+void SceneGraph::UnselectAll() {
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( objects_[i]->Selected() ) {
+ objects_[i]->Select(false);
+ }
+ }
+}
+
+//Update all objects
+void SceneGraph::Update(float timestep, float curr_game_time) {
+ if(queued_level_reset_){
+ PROFILER_ZONE(g_profiler_ctx, "Resetting level");
+ queued_level_reset_ = false;
+ Level::CollisionPtrMap blank;
+ level->HandleCollisions(blank, *this );
+ SendMessageToAllObjects(OBJECT_MSG::RESET);
+ level->Message("post_reset");
+ }
+
+ //uint64_t start_count = SDL_GetPerformanceCounter();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Bullet world update");
+ bullet_world_->Update(timestep);
+ }
+ //uint64_t end_count = SDL_GetPerformanceCounter();
+ plant_bullet_world_->Update(timestep);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Abstract world collisions");
+ level->HandleCollisions(abstract_bullet_world_->GetCollisions(), *this );
+ }
+
+ {
+ //Only updating specific subtypes of objects? -Max
+ PROFILER_ZONE(g_profiler_ctx, "Object updates");
+ for(int i=0; i<num_update_objects; ++i){
+ Object* obj = update_objects_[i];
+ PROFILER_ZONE(g_profiler_ctx, "%s %d update", CStringFromEntityType(obj->GetType()),obj->GetID());
+ obj->ReceiveObjectMessage(OBJECT_MSG::UPDATE, timestep);
+ }
+ /*
+ for(object_list::iterator it = objects_.begin(); it != objects_.end();) {
+ Object* obj = *it;
+ if(obj->active){
+ PROFILER_ZONE(g_profiler_ctx,"%s %d update",CStringFromEntityType(obj->GetType()),obj->GetID());
+ obj->Update();
+ }
+ ++it;
+ if(obj->to_delete){
+ UnlinkObject(obj);
+ delete obj;
+ }
+ }*/
+ }
+
+ //Checking sanity, a couple of objects at a time
+ int partial_loop_countdown = 10;
+ size_t objects_size = objects_.size();
+ while( objects_size > 0 && partial_loop_countdown > 0 ) {
+ if( partial_object_loop_counter >= objects_size ) {
+ partial_object_loop_counter = 0;
+ }
+
+ int i = partial_object_loop_counter;
+
+ ObjectSanityState oss = objects_[i]->GetSanity();
+
+ if( oss.Ok() ) {
+ for( unsigned k = 0; k < kMaxWarnings; k++ ) {
+ if( sanity_list[k].GetID() == oss.GetID() ) {
+ sanity_list[k] = ObjectSanityState();
+ }
+ }
+ } else {
+ int index = -1;
+ for( unsigned k = 0; k < kMaxWarnings; k++ ) {
+ if( sanity_list[k].GetID() == oss.GetID() ) {
+ index = k;
+ } else if( sanity_list[k].GetID() == -1 ) {
+ if( index == -1 ) {
+ index = k;
+ }
+ }
+ }
+
+ if( index != -1 ) {
+ sanity_list[index] = oss;
+ }
+ }
+
+ partial_object_loop_counter++;
+ partial_loop_countdown--;
+ }
+
+ if(nav_mesh_ && (nav_mesh_renderer_.IsNavMeshVisible() || nav_mesh_renderer_.IsCollisionMeshVisible())){
+ PROFILER_ZONE(g_profiler_ctx, "Navmesh update");
+ nav_mesh_->Update();
+ }
+
+ //Update hotspots.
+
+ // If a hotspot adds another hotspot, the iterator risks being invalidated,
+ // so track which objects have been modified, and then find the correct index to keep updating at
+ hotspots_modified_ = false;
+ object_list updated_hotspots;
+ updated_hotspots.reserve(hotspots_.size());
+ for (object_list::iterator it = hotspots_.begin(); it != hotspots_.end(); ++it) {
+ PROFILER_ZONE(g_profiler_ctx, "Hotspot update");
+ updated_hotspots.push_back(*it);
+ (*it)->ReceiveObjectMessage(OBJECT_MSG::UPDATE, timestep);
+
+ if(hotspots_modified_) {
+ for(int i = (int)updated_hotspots.size() - 1; i >= 0; --i) {
+ it = std::find(hotspots_.begin(), hotspots_.end(), updated_hotspots[i]);
+ if(it != hotspots_.end())
+ break;
+ }
+
+ if(it == hotspots_.end())
+ it = hotspots_.begin();
+ hotspots_modified_ = false;
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Particle update");
+ particle_system->Update(this, timestep, curr_game_time);
+ }
+
+ if( objects_.size() > 0 ) {
+ for( int c = 0; c < 64; c++ ) {
+ if( infreq_update_index >= objects_.size() ) {
+ infreq_update_index = 0;
+ }
+ objects_[infreq_update_index]->ReceiveObjectMessage(OBJECT_MSG::INFREQUENT_UPDATE);
+ infreq_update_index++;
+ }
+ }
+}
+
+static bool RemoveObjFromList(Object* o, SceneGraph::object_list* list){
+ SceneGraph::object_list::iterator it = std::find(list->begin(), list->end(), o);
+ if (it != list->end()) {
+ list->erase(it);
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+static bool RemoveDerivedObjFromList(EntityType type, Object* o, std::vector<T*>* list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ list->erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T, typename U>
+static bool RemoveDerivedObjFromList(EntityType type, Object* o, std::vector<T*>* list, std::vector<U>* second_parallel_list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ auto distance_from_begin = std::distance(list->begin(), it);
+ list->erase(it);
+ second_parallel_list->erase(second_parallel_list->begin() + distance_from_begin);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T, typename U, typename I>
+static bool RemoveDerivedObjFromListAndIndex(EntityType type, Object* o, std::vector<T*>* list, std::vector<U>* second_parallel_list, std::vector<I>* index_list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ auto distance_from_begin = std::distance(list->begin(), it);
+ list->erase(it);
+ second_parallel_list->erase(second_parallel_list->begin() + distance_from_begin);
+ auto index_list_it = std::find(index_list->begin(), index_list->end(), (I)distance_from_begin);
+ index_list->erase(index_list_it);
+ for(int i = 0; i < index_list->size(); ++i){ // Decrement all above the removed index
+ (*index_list)[i] = (*index_list)[i] - ((*index_list)[i] > (I)distance_from_begin ? 1 : 0);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void SceneGraph::UnlinkObject(Object *o) {
+ int id = o->GetID();
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, id);
+
+ LOGD << "Unlinking object: " << *o << std::endl;
+ destruction_sanity_insert_position = (destruction_sanity_insert_position+1)%destruction_sanity_size;
+ destruction_sanity[destruction_sanity_insert_position] = o;
+
+ destruction_memory_insert_position = (destruction_memory_insert_position+1)%destruction_memory_size;
+
+ destruction_memory_ids[destruction_memory_insert_position] = o->GetID();
+ o->GetDisplayName(&destruction_memory_strings[destruction_memory_insert_position*destruction_memory_string_size], destruction_memory_string_size-1);
+
+ if(object_at_id != NULL){
+ if( o != object_at_id)
+ {
+ LOGW << "Object removal mismatch." << std::endl;
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, NULL);
+ }
+
+ if(o == terrain_object_){
+ terrain_object_ = NULL;
+ }
+
+ RemoveObjFromList(o, &visible_objects_);
+ RemoveDerivedObjFromListAndIndex(_env_object, o, &visible_static_meshes_, &visible_static_meshes_shadow_cache_bounds_, &visible_static_mesh_indices_);
+ RemoveDerivedObjFromList(_terrain_type, o, &terrain_objects_, &terrain_objects_shadow_cache_bounds_);
+ RemoveObjFromList(o, &collide_objects_);
+ RemoveObjFromList(o, &movement_objects_);
+ RemoveObjFromList(o, &item_objects_);
+ RemoveObjFromList(o, &decal_objects_);
+ RemoveObjFromList(o, &objects_);
+ if(RemoveObjFromList(o, &hotspots_))
+ hotspots_modified_ = true;
+ RemoveObjFromList(o, &navmesh_hints_);
+ RemoveObjFromList(o, &navmesh_connections_);
+ RemoveObjFromList(o, &path_points_);
+ RemoveDerivedObjFromList(_light_volume_object, o, &light_volume_objects_);
+
+ for (decal_deque::iterator it = dynamic_decals.begin(); it != dynamic_decals.end();) {
+ DecalObject *obj = *it;
+ if(obj == o){
+ it = dynamic_decals.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ for( unsigned i = 0; i < kMaxWarnings; i++ ) {
+ if( sanity_list[i].GetID() == id ) {
+ sanity_list[i] = ObjectSanityState();
+ }
+ }
+}
+
+Object* SceneGraph::GetObjectFromID(int object_id) {
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, object_id);
+ if(object_at_id != NULL){
+ return object_at_id;
+ } else {
+ LOGW << "Requested an object with id " << object_id << " but found none. Last info known of this id is: " << GetDestroyedObjectInfo(object_id) << std::endl;
+ }
+ return NULL;
+}
+
+bool SceneGraph::DoesObjectWithIdExist(int object_id) {
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, object_id);
+ bool result = object_at_id != NULL;
+ return result;
+}
+
+std::vector<Object*> SceneGraph::GetObjectsOfType(enum EntityType type)
+{
+ std::vector<Object*> ret;
+
+ std::vector<Object*>::iterator objit;
+
+ for( objit = objects_.begin(); objit != objects_.end(); objit++ )
+ {
+ if( (*objit)->GetType() == type )
+ {
+ ret.push_back( *objit );
+ }
+ }
+
+ return ret;
+}
+
+void SceneGraph::CreateNavMesh() {
+ if(map_editor->GetTerrainPreviewMode()) {
+ DisplayMessage("Cannot create navmesh", "It is not possible to create navmesh while previewing terrain");
+ return;
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "CreateNavMesh");
+ if(nav_mesh_){
+ delete nav_mesh_;
+ }
+ nav_mesh_ = new NavMesh();
+
+ std::vector<Object*> regions = GetObjectsOfType(_navmesh_region_object);
+
+ if( regions.size() > 0 )
+ {
+ if(regions.size() > 1 )
+ {
+ LOGW << "THere is more than one region object, using the first one, please remove all others" << std::endl;
+ }
+
+ nav_mesh_->SetExplicitBounderies(
+ ((NavmeshRegionObject*)regions[0])->GetMinBounds(),
+ ((NavmeshRegionObject*)regions[0])->GetMaxBounds()
+ );
+ }
+
+ AddSceneToNavmesh();
+ nav_mesh_->CalcNavMesh();
+ Graphics::Instance()->nav_mesh_out_of_date = false;
+
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+}
+
+void SceneGraph::SaveNavMesh()
+{
+ PROFILER_ZONE(g_profiler_ctx, "SaveNavMesh");
+ if(!nav_mesh_){
+ LOGE << "Unable to save navmesh, none is generated." << std::endl;
+ return;
+ }
+ if(level_path_.isValid() == false){
+ LOGE << "Unable to save navmesh, level has no path." << std::endl;
+ return;
+ }
+ LOGI << "Saving NAVMESH." << std::endl;
+ nav_mesh_->Save(level_name_,level_path_);
+}
+
+bool SceneGraph::LoadNavMesh() {
+ delete nav_mesh_;
+ nav_mesh_ = new NavMesh();
+ if(!nav_mesh_->Load(level_name_,level_path_)){
+ delete nav_mesh_;
+ nav_mesh_ = NULL;
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ Graphics::Instance()->nav_mesh_out_of_date_chunk = -1;
+ }
+
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+
+ return nav_mesh_ != NULL;
+}
+
+void SceneGraph::AddSceneToNavmesh() {
+ nav_mesh_->SetNavMeshParameters(level->nav_mesh_parameters_);
+ object_list::iterator iter = collide_objects_.begin();
+ for(; iter != collide_objects_.end(); ++iter){
+ Object* obj = (*iter);
+ if(obj->GetType() == _env_object && !((EnvObject*)obj)->ofr->no_collision && !((EnvObject*)obj)->no_navmesh){
+ //Check that this object doesn't have a movementobject parent (meaning it is dynamic)
+ EnvObject *env_obj = (EnvObject*)obj;
+ if(env_obj->attached_ == NULL)
+ {
+ int model_id = env_obj->GetCollisionModelID();
+ if(model_id != -1){
+ const Model* model = &Models::Instance()->GetModel(model_id);
+ nav_mesh_->AddMesh(model->vertices, model->faces, env_obj->GetTransform());
+ }
+ }
+ }
+ if(obj->GetType() == _terrain_type){
+ TerrainObject *terrain_obj = (TerrainObject*)obj;
+ const Model* model = terrain_obj->GetModel();
+ mat4 identity;
+ nav_mesh_->AddMesh(model->vertices, model->faces, identity);
+ }
+ }
+
+ {
+ std::vector<float> verts;
+ std::vector<unsigned> faces;
+
+ GetUnitBoxVertArray(verts, faces);
+
+ for(iter = navmesh_hints_.begin(); iter != navmesh_hints_.end(); ++iter){
+ nav_mesh_->AddMesh( verts, faces, (*iter)->GetTransform() );
+ }
+ }
+
+ std::set<std::pair<int,int> > offmesh_connections;
+
+ for(iter = navmesh_connections_.begin(); iter != navmesh_connections_.end(); ++iter){
+ NavmeshConnectionObject* obj = static_cast<NavmeshConnectionObject*>(*iter);
+
+ obj->ResetConnectionOffMeshReference();
+
+ int first_id = obj->GetID();
+ int second_id;
+
+ std::vector<NavMeshConnectionData>::iterator idit = obj->connections.begin();
+ for( ;idit != obj->connections.end(); idit++ )
+ {
+ second_id = idit->other_object_id;
+ //We always add the highest value first to ensure the connections are unique. (two-way assumed)
+ if( first_id > second_id )
+ {
+ offmesh_connections.insert( std::pair<int,int>(first_id, second_id) );
+ }
+ else if( second_id > first_id )
+ {
+ offmesh_connections.insert( std::pair<int,int>(second_id, first_id) );
+ }
+ else
+ {
+ LOGE << "Offmesh connection is self-referential. \"" << first_id << "\"" << std::endl;
+ }
+ }
+ }
+
+ std::set<std::pair<int,int> >::iterator pair_it = offmesh_connections.begin();
+
+ nav_mesh_->getInputGeom().deleteAllOffMeshConnections();
+
+ int id_counter = 1000;
+
+ for(; pair_it != offmesh_connections.end(); pair_it++ )
+ {
+ Object* first = GetObjectFromID( pair_it->first );
+ Object* second = GetObjectFromID( pair_it->second );
+
+ if( first && second )
+ {
+ if( first->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* first_nco = static_cast<NavmeshConnectionObject*>(first);
+ first_nco->UpdatePolyAreas();
+ }
+
+ if( second->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* second_nco = static_cast<NavmeshConnectionObject*>(second);
+ second_nco->UpdatePolyAreas();
+ }
+
+ SamplePolyAreas area = SAMPLE_POLYAREA_DISABLED;
+
+ //TODO: We might want to assert that the poly areas are the same from both connections.
+ if( first->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject *nmco = static_cast<NavmeshConnectionObject*>(first);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( pair_it->second );
+ if( other != nmco->connections.end() )
+ {
+ other->offmesh_connection_id = id_counter;
+ area = other->poly_area;
+ }
+ }
+
+ if( second->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject *nmco = static_cast<NavmeshConnectionObject*>(second);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( pair_it->first );
+ if( other != nmco->connections.end() )
+ {
+ other->offmesh_connection_id = id_counter;
+ area = other->poly_area;
+ }
+ }
+
+ LOGI << "Adding off mesh connection between " <<
+ first->GetTranslation() << " and " <<
+ second->GetTranslation() << " type " << area << std::endl;
+
+ unsigned short jump_category = SAMPLE_POLYFLAGS_JUMP5;
+
+ if( area == SAMPLE_POLYAREA_JUMP1 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP1;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP2 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP2;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP3 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP3;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP4 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP4;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP5 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP5;
+ }
+
+ if( area == SAMPLE_POLYAREA_DISABLED )
+ {
+ jump_category = SAMPLE_POLYFLAGS_DISABLED;
+ }
+
+ nav_mesh_->getInputGeom().addOffMeshConnection(
+ first->GetTranslation(),
+ second->GetTranslation(),
+ 1.0f, //rad
+ DT_OFFMESH_CON_BIDIR, //bidir
+ area, //User defined area id
+ jump_category,
+ id_counter++); //User defined flags
+ } else {
+ LOGW << "Invalid offmesh connection between " << pair_it->first << " and " << pair_it->second << std::endl;
+ }
+ }
+}
+
+NavMesh* SceneGraph::GetNavMesh() {
+ return nav_mesh_;
+}
+
+Object* SceneGraph::GetClosestObject(const vec3 &pos, Object* excluded_object, vec3 *hit_pos, int *hit_tri){
+ ContactInfoCallback cb;
+ float size = 0.1f;
+ bool hit_something = false;
+ while(!hit_something && size < 0.5f){
+ bullet_world_->GetSphereCollisions(pos, size, cb);
+ size += 0.1f;
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo &&
+ bo->owner_object &&
+ bo->owner_object->GetType() != _movement_object &&
+ bo->owner_object != excluded_object)
+ {
+ hit_something = true;
+ }
+ }
+ }
+ //DebugDraw::Instance()->AddWireSphere(event_pos, size,vec4(1.0f),_persistent);
+ Object* closest_obj = NULL;
+ float closest_dist;
+ vec3 closest_point;
+ int closest_tri;
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo &&
+ bo->owner_object &&
+ (bo->owner_object->GetType() == _movement_object ||
+ bo->owner_object == excluded_object))
+ {
+ continue;
+ }
+ const vec3 &point = vec3(cb.contact_info[i].point[0],
+ cb.contact_info[i].point[1],
+ cb.contact_info[i].point[2]);
+ if(closest_obj == NULL ||
+ distance_squared(pos, point) < closest_dist)
+ {
+ closest_dist = distance_squared(pos, point);
+ closest_obj = bo->owner_object;
+ closest_point = point;
+ closest_tri = cb.contact_info[i].tri;
+ }
+ }
+ if(closest_obj != NULL){
+ if(hit_pos){
+ *hit_pos = closest_point;
+ }
+ if(hit_tri){
+ *hit_tri = closest_tri;
+ }
+ }
+ return closest_obj;
+}
+
+const MaterialEvent *SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos )
+{
+ int hit_tri;
+ vec3 hit_pos;
+ Object* closest_obj = GetClosestObject(event_pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, &hit_tri);
+}
+
+const MaterialEvent *SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, Object* excluded_object )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, &hit_tri);
+}
+
+const MaterialEvent * SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, mod, &hit_tri);
+}
+
+const MaterialEvent * SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, mod, &hit_tri);
+}
+
+
+const MaterialDecal *SceneGraph::GetMaterialDecal( const std::string &type,
+ const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialDecal(type, pos, &hit_tri);
+}
+
+const MaterialParticle * SceneGraph::GetMaterialParticle( const std::string &type, const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialParticle(type, pos, &hit_tri);
+}
+
+vec3 SceneGraph::GetColorAtPoint( const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return vec3(0);
+ }
+ return closest_obj->GetColorAtPoint(pos, &hit_tri);
+}
+
+void SceneGraph::UpdatePhysicsTransforms()
+{
+ for(std::vector<EnvObject*>::iterator it = visible_static_meshes_.begin(); it != visible_static_meshes_.end(); ++it){
+ (*it)->UpdatePhysicsTransform();
+ }
+}
+
+void SceneGraph::GetPlayerCharacterIDs(int* num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for(unsigned i=0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if(mo->is_player){
+ if(*num_avatars < max_avatars){
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ } else {
+ LOG_ASSERT(false);
+ }
+ }
+ }
+}
+
+void SceneGraph::GetNPCCharacterIDs(int * num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for (unsigned i = 0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if (!mo->is_player) {
+ if (*num_avatars < max_avatars) {
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ }
+ else {
+ LOG_ASSERT(false);
+ }
+ }
+ }
+}
+
+void SceneGraph::GetCharacterIDs(int *num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for (unsigned i = 0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if (*num_avatars < max_avatars) {
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ }
+ else {
+ LOG_ASSERT(false);
+ }
+ }
+}
+
+float SceneGraph::GetMaterialHardness( const vec3 &pos, Object* excluded_object )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 1.0f;
+ }
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetHardness();
+}
+
+float SceneGraph::GetMaterialFriction( const vec3 &pos, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 1.0f;
+ }
+ //DebugDraw::Instance()->AddWireSphere(hit_pos, 0.1f, vec4(1.0f), _fade);
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetFriction();
+}
+
+float SceneGraph::GetMaterialSharpPenetration( const vec3 &pos, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 0.0f;
+ }
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetSharpPenetration();
+}
+
+void SceneGraph::GetSweptSphereCollisionCharacters( const vec3 &pos, const vec3 &pos2, float radius, SphereCollision &as_col ) {
+ vec3 end = pos2;
+ for(unsigned i=0; i<movement_objects_.size(); ++i){
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ mo->rigged_object()->skeleton().GetSweptSphereCollisionCharacter(pos, end, radius, as_col);
+ end = as_col.position;
+ }
+}
+
+int SceneGraph::CheckRayCollisionCharacters( const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal, int *bone ) {
+ vec3 new_end = end;
+ vec3 temp_point;
+ vec3 temp_normal;
+ int char_id = -1;
+ for(unsigned i=0; i<movement_objects_.size(); ++i){
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ const btCollisionObject* bone_col = mo->rigged_object()->skeleton().CheckRayCollision(start, new_end, &temp_point, &temp_normal);
+ if(bone_col != NULL){
+ if(point){
+ *point = temp_point;
+ }
+ if(normal){
+ *normal = temp_normal;
+ }
+ if(bone){
+ BulletObject* bullet_obj = (BulletObject*)bone_col->getUserPointer();
+ if(bullet_obj){
+ PhysicsBone* pb = (PhysicsBone*)bullet_obj->owner_object;
+ if(pb){
+ *bone = pb->bone;
+ }
+ }
+ }
+ new_end = temp_point;
+ char_id = mo->GetID();
+ }
+ }
+ return char_id;
+}
+
+void SceneGraph::SendMessageToAllObjects( OBJECT_MSG::Type type ) {
+ //This is a simple loop instead of an iterator because sometimes this would crash as an iterator after
+ //passing way past the end. Likely because objects_ is sometimes changed as a result of this call.
+ //This solution means that most objects get called, some might not if they are first destroyed.
+ //New objects will also be called assuming they are added last to the list.
+ for( unsigned int i = 0; i < objects_.size(); i++ ) {
+ Object* obj = objects_[i];
+ if( obj )
+ {
+ obj->ReceiveObjectMessage(type);
+ }
+ else
+ {
+ LOGE << "One of the objects is NULL when trying to SendMessageToAllObjects." << std::endl;
+ }
+ }
+}
+
+void SceneGraph::SendScriptMessageToAllObjects( std::string& msg ) {
+ for( unsigned int i = 0; i < objects_.size(); i++ ) {
+ Object* obj = objects_[i];
+ if( obj )
+ {
+ obj->ReceiveObjectMessage(OBJECT_MSG::SCRIPT, &msg);
+ }
+ else
+ {
+ LOGE << "One of the objects is NULL when trying to SendMessageToAllObjects." << std::endl;
+ }
+ }
+}
+
+std::vector<MovementObject*> SceneGraph::GetControlledMovementObjects() {
+ std::vector<MovementObject*> controlled_objects;
+ for (Object* obj : movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(obj);
+
+ if (mo->controlled) {
+ controlled_objects.push_back(mo);
+ }
+ }
+ return controlled_objects;
+}
+
+std::vector<MovementObject*> SceneGraph::GetControllableMovementObjects() {
+ std::vector<MovementObject*> controllable_objects;
+ for (Object* obj : movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(obj);
+
+ if (mo->is_player) {
+ controllable_objects.push_back(mo);
+ }
+ }
+ return controllable_objects;
+}
+
+bool SceneGraph::VerifySanity() {
+ for( unsigned i = 0; i < kMaxWarnings; i++ ) {
+ ObjectSanityState& sanity = sanity_list[i];
+ if(sanity.Valid() && sanity.Ok() == false) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+// Adapted from https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/
+float orient2d(const float *a, const float *b, const float *c) {
+ return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);
+}
+
+void SceneGraph::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, SceneGraph::DepthType depth_type, SceneDrawType scene_draw_type) {
+ if (g_debug_runtime_disable_scene_graph_draw_depth_map) {
+ return;
+ }
+
+ Object::DrawType object_draw_type = Object::kUnknown;
+ if(depth_type == SceneGraph::kDepthPrePass){
+ object_draw_type = Object::kDrawDepthOnly;
+ } else if(depth_type == SceneGraph::kDepthShadow){
+ object_draw_type = Object::kDrawDepthNoAA;
+ } else if(depth_type == SceneGraph::kDepthAllShadowCascades){
+ object_draw_type = Object::kDrawAllShadowCascades;
+ }
+
+ UpdateShaderSuffix(this, object_draw_type);
+
+ {
+ static object_list visible_objects_copy;
+ visible_objects_copy = visible_objects_;
+ PROFILER_ZONE(g_profiler_ctx, "Draw dynamic objects");
+ if(scene_draw_type == kStaticAndDynamic){
+ for(object_list::iterator it = visible_objects_copy.begin(); it != visible_objects_copy.end(); ++it) {
+ Object& obj = *(*it);
+ if(obj.enabled_ && obj.GetType() != _decal_object && obj.GetType() != _env_object){
+ obj.DrawDepthMap(proj_view_matrix, cull_planes, num_cull_planes, object_draw_type);
+ }
+ }
+ } else {
+ for(object_list::iterator it = visible_objects_copy.begin(); it != visible_objects_copy.end(); ++it) {
+ Object& obj = *(*it);
+ if(obj.enabled_ && obj.GetType() == _terrain_type){
+ obj.DrawDepthMap(proj_view_matrix, cull_planes, num_cull_planes, object_draw_type);
+ }
+ }
+ }
+ }
+
+ if(!kUseShadowCache){
+ if(visible_objects_need_sort){
+ PROFILER_ENTER(g_profiler_ctx, "Sort visible objects");
+ g_static_mesh_draw_sort_entries = &visible_static_meshes_;
+ std::sort(visible_static_mesh_indices_.begin(), visible_static_mesh_indices_.end(), StaticMeshDrawSortIndices);
+ visible_objects_need_sort = false;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+ vec3 cam_pos = ActiveCameras::Get()->GetPos();
+ // List static meshes
+ PROFILER_ENTER(g_profiler_ctx, "Draw env object batches (depth only)");
+ static std::vector<EnvObject*> static_meshes_to_draw;
+ static_meshes_to_draw.clear();
+ static_meshes_to_draw.reserve(visible_static_meshes_.size());
+ PROFILER_ENTER(g_profiler_ctx, "List visible objects / frustum cull");
+ for(std::vector<uint16_t>::iterator it = visible_static_mesh_indices_.begin(); it != visible_static_mesh_indices_.end(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(!eo->transparent && eo->enabled_) {
+ bool culled = false;
+ for(int plane=0; plane<num_cull_planes; ++plane){
+ if( dot(eo->sphere_center_, cull_planes[plane].xyz()) +
+ cull_planes[plane][3] + eo->sphere_radius_ <= 0.0f )
+ {
+ culled = true;
+ break;
+ }
+ }
+ if(!culled){
+ static_meshes_to_draw.push_back(eo);
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Issue draw calls");
+ int batch_start = 0;
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_matrix, proj_view_matrix, NULL, cam_pos, object_draw_type);
+ batch_start = i;
+ }
+ }
+ if(object_draw_type != Object::kDrawDepthOnly) {
+ // Batch and draw detail objects
+ static std::vector<DetailObjectSurfaceDrawCall> detail_objects_surfaces_to_draw;
+ detail_objects_surfaces_to_draw.clear();
+ detail_objects_surfaces_to_draw.reserve(visible_static_meshes_.size());
+ batch_start = 0;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ if(static_meshes_to_draw[i-1]->HasDetailObjectSurfaces()){
+ detail_objects_surfaces_to_draw.push_back({ static_meshes_to_draw[i-1], &static_meshes_to_draw[batch_start], i-batch_start });
+ }
+ batch_start = i;
+ }
+ }
+ for(unsigned int i = 0; i < detail_objects_surfaces_to_draw.size(); ++i) {
+ auto current = detail_objects_surfaces_to_draw[i];
+ current.draw_owner->DrawDetailObjectInstances(current.instance_array, current.num_instances, object_draw_type);
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_LEAVE(g_profiler_ctx);
+ } else {
+ }
+ /*if(depth_type == SceneGraph::kDepthShadow){
+ particle_system->DrawDepth(this, proj_view_matrix);
+ }*/
+}
+
+int SceneGraph::GetAndReserveID() {
+ int curr_id = 0;
+ int last_id = -1;
+ int free_id = -1;
+ for(unsigned int i = 0; i < object_from_id_map_.size(); ++i){
+ if(object_from_id_map_[i] == NULL){
+ free_id = i;
+ break;
+ }
+ }
+ if(free_id == -1){
+ free_id = object_from_id_map_.size();
+ }
+ SetIdToObjectMapValue(object_from_id_map_, free_id, NULL);
+ return free_id;
+}
+
+int SceneGraph::LinkUpdateObject(Object* obj) {
+ if(num_update_objects < kMaxUpdateObjects){
+ int id = num_update_objects++;
+ update_objects_[id] = obj;
+ return id;
+ } else {
+ FatalError("Error", "Too many update objects");
+ return -1;
+ }
+}
+
+void SceneGraph::UnlinkUpdateObject(Object* obj, int entry) {
+ SDL_assert(update_objects_[entry] == obj);
+ if(entry != num_update_objects-1){
+ update_objects_[entry] = update_objects_[num_update_objects-1];
+ update_objects_[entry]->update_list_entry = entry;
+ }
+ --num_update_objects;
+}
+
+void SceneGraph::Dispose() {
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ (*it)->Dispose();
+ }
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ delete (*it);
+ }
+ object_from_id_map_.clear();
+ if(bullet_world_){
+ bullet_world_->Dispose();
+ delete bullet_world_;
+ bullet_world_ = NULL;
+ }
+ if(abstract_bullet_world_){
+ abstract_bullet_world_->Dispose();
+ delete abstract_bullet_world_;
+ abstract_bullet_world_ = NULL;
+ }
+ if(plant_bullet_world_){
+ plant_bullet_world_->Dispose();
+ delete plant_bullet_world_;
+ plant_bullet_world_ = NULL;
+ }
+ if(nav_mesh_){
+ delete nav_mesh_;
+ nav_mesh_ = NULL;
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+ }
+ delete particle_system;
+ particle_system = NULL;
+ light_probe_collection.Dispose();
+ dynamic_light_collection.Dispose();
+ level->Dispose();
+ delete level;
+ level = NULL;
+ delete map_editor;
+ map_editor = NULL;
+ sky->Dispose();
+ delete sky;
+ sky = NULL;
+ flares.CleanupFlares();
+ DecalTextures::Instance()->Clear();
+}
+
+void SceneGraph::SetNavMeshVisible( bool v )
+{
+ nav_mesh_renderer_.SetNavMeshVisible(v);
+}
+
+void SceneGraph::SetCollisionNavMeshVisible( bool v )
+{
+ nav_mesh_renderer_.SetCollisionMeshVisible(v);
+}
+
+bool SceneGraph::IsNavMeshVisible()
+{
+ return nav_mesh_renderer_.IsNavMeshVisible();
+}
+
+bool SceneGraph::IsCollisionNavMeshVisible()
+{
+ return nav_mesh_renderer_.IsCollisionMeshVisible();
+}
+
+bool SceneGraph::AddDynamicDecal(DecalObject *decal) {
+ if (dynamic_decals.size() >= kMaxDynamicDecals) {
+ DecalObject *obj = dynamic_decals.front();
+ obj->SetParent(NULL);
+ UnlinkObject(obj);
+ obj->Dispose();
+ delete(obj);
+ }
+ // make sure we don't clump too many decals into one place
+ vec3 my_pos = decal->GetTranslation();
+
+ // find nearest decal which is same type
+ // c++11 auto would be really helpful here
+ float nearest_dist_sq = FLT_MAX;
+
+ Object* nearest_obj = NULL;
+ decal_deque::iterator e = dynamic_decals.end();
+ for (decal_deque::iterator it = dynamic_decals.begin(); it != e; ++it) {
+ DecalObject *obj = *it;
+
+ LOG_ASSERT(obj != NULL);
+ if (obj->decal_file_ref->color_map != "Data/Textures/bloodsplat_c.tga" || obj->obj_file != decal->obj_file) {
+ // not the same kind of decal, don't consider
+ continue;
+ }
+
+ vec3 other_pos = obj->GetTranslation();
+ float dist_sq = length_squared(my_pos - other_pos);
+ if (dist_sq < nearest_dist_sq) {
+ nearest_dist_sq = dist_sq;
+ nearest_obj = obj;
+ }
+ }
+
+ vec3 scale = decal->GetScale();
+ float my_radius = min(scale.x(), min(scale.y(), scale.z()));
+ float my_radius_sq = my_radius * my_radius;
+ float other_radius_sq = 0.0f;
+ if(nearest_obj){
+ scale = nearest_obj->GetScale();
+ float other_radius = min(scale.x(), min(scale.y(), scale.z()));
+ float other_radius_sq = other_radius * other_radius;
+ }
+ if (nearest_dist_sq > my_radius_sq && nearest_dist_sq > other_radius_sq) {
+ if( ActorsEditor_AddEntity(this, decal, NULL, false) ) {
+ dynamic_decals.push_back(decal);
+ } else {
+ LOGE << "Failed to add dynamic decal to scenegraph" << std::endl;
+ return false;
+ }
+ } else {
+ vec3 cur_scale = nearest_obj->GetScale();
+ vec3 add_scale = decal->GetScale();
+ float cur_volume = cur_scale[0] * cur_scale[1];
+ float add_volume = add_scale[0] * add_scale[1];
+ if(cur_scale[0] < cur_scale[1]){
+ float square_vol = cur_scale[1] * cur_scale[1];
+ if(cur_volume + add_volume > square_vol){
+ cur_scale[0] = cur_scale[1];
+ add_volume += cur_volume - square_vol;
+ } else {
+ cur_scale[0] *= (cur_volume + add_volume) / cur_volume;
+ add_volume = 0.0f;
+ }
+ } else if(cur_scale[0] > cur_scale[1]){
+ float square_vol = cur_scale[0] * cur_scale[0];
+ if(cur_volume + add_volume > square_vol){
+ cur_scale[1] = cur_scale[0];
+ add_volume += cur_volume - square_vol;
+ } else {
+ cur_scale[1] *= (cur_volume + add_volume) / cur_volume;
+ add_volume = 0.0f;
+ }
+ }
+ float increase = (cur_volume + add_volume) / cur_volume;
+ nearest_obj->SetScale(nearest_obj->GetScale() * increase);
+ // on top of existing, don't add
+ return false;
+ }
+ return true;
+}
+
+
+#define COUNT_BITS 24u
+#define COUNT_MASK ((1u << (32u - COUNT_BITS)) - 1u)
+#define INDEX_MASK ((1u << (COUNT_BITS)) - 1u)
+
+uint32_t SetCount(uint32_t val, uint32_t count){
+ uint32_t count_shifted = count << COUNT_BITS;
+ LOG_ASSERT(count_shifted >> COUNT_BITS == count);
+ return val | count_shifted;
+}
+
+uint32_t SetIndex(uint32_t val, uint32_t index){
+ LOG_ASSERT(index == (index & INDEX_MASK));
+ return val | index;
+}
+
+
+// - z_near + 1.0f = minimum of 1.0, log of that = 0.0f
+// z_mult already contains num_z_clusters
+float ZCLUSTERFUNC(float val, float z_near, float z_mult) {
+ return log(-1.0f * (val) - z_near + 1.0f) * z_mult;
+}
+
+
+void SceneGraph::PrepareLightsAndDecals(vec2 active_screen_start, vec2 active_screen_end, vec2 screen_dims) {
+ if (g_debug_runtime_disable_scene_graph_prepare_lights_and_decals) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "PrepareLightsAndDecals");
+ // http://www.humus.name/index.php?page=Articles
+ // "Practical Clustered Shading"
+ // this is similar but NOT the same algorithm
+ // 1. different z function
+ // 2. doesn't use plane tests
+
+ // 1. I couldn't get the paper's version to work
+ // somehow it doesn't cluster things correctly
+ // play with ZCLUSTERFUNC if you want to fix it (also in the shader)
+
+ // 2. this code loops through all light/decals and converts them to screen space
+ // the paper build a set of planes aligned with screen x/y/z axes
+ // and tests primitives against those
+ // the naive way was simpler and we don't have plane test primitives yet
+
+ // you need plane-box and plane-sphere tests
+ // with return value of Front, Behind, Intersect
+ // Math/enginemath, Math/overgroth_geometry and Internal/collisiondetection
+ // contain some things but not the things we need
+
+ Graphics *graphics = Graphics::Instance();
+ const unsigned int width = (unsigned int) screen_dims[0];
+ const unsigned int height = (unsigned int) screen_dims[1];
+
+ Camera* camera = ActiveCameras::Get();
+ LOG_ASSERT(camera);
+ mat4 proj_mat = camera->GetProjMatrix();
+ mat4 inv_proj_mat = invert(proj_mat);
+ mat4 view_mat = camera->GetViewMatrix();
+ mat4 proj_view_mat = proj_mat * view_mat;
+ float z_near = camera->GetNearPlane();
+ float z_far = camera->GetFarPlane();
+ mat4 cluster_mat;
+
+ {
+ const vec3 screen_start(active_screen_start, 0.0f);
+ const vec3 screen_end(active_screen_end, 1.0f);
+ const vec3 screen_mult = screen_end - screen_start;
+
+ mat4 scale;
+ mat4 translate;
+ // scale .xy from [-1.0, 1.0] to [0.0, 1.0]
+ scale.SetUniformScale(0.5f);
+ translate.SetTranslation(vec3(0.5f));
+ cluster_mat = translate * scale * proj_mat;
+
+ // account for currently active view
+ scale.SetScale(screen_mult);
+ translate.SetTranslation(screen_start);
+ cluster_mat = translate * scale * cluster_mat;
+
+ // scale to [(0,0), (width, height)]
+ scale.SetScale(vec3((float)width, (float)height, 1.0f));
+ cluster_mat = scale * cluster_mat;
+
+ // scale to cluster space
+ scale.SetScale(vec3(1.0f / cluster_size, 1.0f / cluster_size, 1.0f));
+ cluster_mat = scale * cluster_mat;
+ }
+
+ const unsigned int grid_width = (width + cluster_size - 1) / cluster_size;
+ const unsigned int grid_height = (height + cluster_size - 1) / cluster_size;
+
+ const unsigned int numclusters = grid_width * grid_height * num_z_clusters;
+
+ const vec2 grid_size = vec2(float(grid_width), float(grid_height));
+ const vec3 cluster_min_bound(active_screen_start * grid_size, -z_far);
+ const vec3 cluster_max_bound(active_screen_end * grid_size, 0.0f);
+
+ // z values will be divided by this
+ // the value inside log is maximum we should get in view space
+ // after adding 1.0 so the log works
+ // by dividing with num_z_clusters the final value ends up
+ // multiplied by it so we get [0,num_z_clusters]
+ float z_div = log(z_far - z_near + 1.0f) / num_z_clusters;
+ float z_mult = 1.0f / z_div;
+
+ ShaderClusterInfo clusterInfo;
+ clusterInfo.num_decals = 0;
+ clusterInfo.num_lights = 0;
+ clusterInfo.light_cluster_data_offset = 0;
+ clusterInfo.light_data_offset = 0;
+ clusterInfo.cluster_width = cluster_size;
+
+ clusterInfo.grid_size[0] = grid_width;
+ clusterInfo.grid_size[1] = grid_height;
+ clusterInfo.grid_size[2] = num_z_clusters;
+
+ memcpy(clusterInfo.inv_proj_mat, &inv_proj_mat, 16 * 4);
+ for (unsigned int i = 0; i < 4; i++) {
+ clusterInfo.viewport[i] = (float)graphics->viewport_dim[i];
+ }
+
+ clusterInfo.z_near = z_near;
+ clusterInfo.z_mult = z_mult;
+ clusterInfo.pad3 = 0.0f;
+ clusterInfo.pad4 = 0.0f;
+
+ // resize buffers if necessary
+ if (cluster_decal_counts.size() != numclusters) {
+ cluster_decal_counts.resize(numclusters);
+ cluster_light_counts.resize(numclusters);
+ cluster_list_heads.resize(numclusters);
+ decal_grid_lookup.resize(numclusters);
+ light_grid_lookup.resize(numclusters);
+ }
+
+ LOG_ASSERT_EQ(decal_grid_lookup.size(), cluster_decal_counts.size());
+ LOG_ASSERT_EQ(light_grid_lookup.size(), cluster_decal_counts.size());
+ LOG_ASSERT_EQ(cluster_light_counts.size(), cluster_decal_counts.size());
+
+ // number of decals alive (visible on screen) in current frame
+ unsigned int num_alive_decals = 0;
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Cluster decals");
+
+ int num_decals = decal_objects_.size();
+ LOG_ASSERT_LTEQ(num_decals, (int)kMaxDecals);
+
+ if (decal_tbo.size() < num_decals * sizeof(ShaderDecal) / sizeof(float)) {
+ decal_tbo.resize(num_decals * sizeof(ShaderDecal) / sizeof(float));
+ }
+
+ // LOGI << "num_decals = " << num_decals << std::endl;
+
+ for (unsigned int i = 0; i < numclusters; i++) {
+ cluster_decal_counts[i] = 0;
+ }
+
+ ShaderDecal decal;
+
+ bool decal_normals = config["decal_normals"].toNumber<bool>();
+
+ memset(&cluster_list_heads[0], 0, sizeof(uint32_t) * cluster_list_heads.size());
+ cluster_list_contents.clear();
+ cluster_list_contents.push_back(ClusterData());
+
+ // assign decals into clusters
+ for (int i = 0; i < num_decals; ++i){
+ DecalObject * dec = static_cast<DecalObject*>(decal_objects_[i]);
+ if(dec->enabled_ == false){
+ continue;
+ }
+
+ decal.decal_tint[3] = dec->decal_file_ref->special_type + 0.5f;
+
+ mat4 mv_mat = view_mat * dec->GetTransform();
+
+ // calculate decal bounding box in view space
+ vec3 decal_view_min(FLT_MAX, FLT_MAX, FLT_MAX);
+ vec3 decal_view_max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+
+ Box decal_box = dec->box_;
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ // strange thing: view space z decreases farther away
+ vec3 view_point = (mv_mat * vec4(decal_box.GetPoint(point), 1.0f)).xyz();
+ decal_view_min = components_min(decal_view_min, view_point);
+ decal_view_max = components_max(decal_view_max, view_point);
+ }
+
+ //LOGI << "decal " << i << std::endl;
+
+ // drop it if it's entirely behind the camera
+ if (decal_view_min.z() > -z_near) {
+ continue;
+ }
+ /*
+ LOGI << "decal_view_min: " << decal_view_min << std::endl;
+ LOGI << "decal_view_max: " << decal_view_max << std::endl;
+ */
+
+ // cap at near plane
+ decal_view_max.z() = min(-z_near, decal_view_max.z());
+
+ Box view_box;
+ view_box.center = (decal_view_min + decal_view_max) * 0.5f;
+ view_box.dims = decal_view_max - decal_view_min;
+
+ // calculate projection space bounding box
+ vec2 decal_proj_min(FLT_MAX, FLT_MAX);
+ vec2 decal_proj_max(-FLT_MAX, -FLT_MAX);
+
+ // TODO: we probably don't need to do the whole box, just the
+ // four points on the near face
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ vec4 proj_point = cluster_mat * vec4(view_box.GetPoint(point), 1.0f);
+ // we know it's in front of the screen since we capped it at near plane
+ LOG_ASSERT_GTEQ(proj_point.w(), 0.0f);
+ vec2 proj_point2 = proj_point.xy() * (1.0f / proj_point.w());
+
+ decal_proj_min = components_min2(decal_proj_min, proj_point2);
+ decal_proj_max = components_max2(decal_proj_max, proj_point2);
+ }
+
+ /*
+ LOGI << "decal_proj_min: " << decal_proj_min << std::endl;
+ LOGI << "decal_proj_max: " << decal_proj_max << std::endl;
+ */
+
+ // we use projection-space .xy and view-space .z for clustering
+ vec3 decal_cluster_min(decal_proj_min, decal_view_min.z());
+ vec3 decal_cluster_max(decal_proj_max, decal_view_max.z());
+
+ // drop the decal if it's entirely off-screen
+ if (decal_cluster_min.x() > cluster_max_bound.x()
+ || decal_cluster_max.x() < cluster_min_bound.x()
+ || decal_cluster_min.y() > cluster_max_bound.y()
+ || decal_cluster_max.y() < cluster_min_bound.y()) {
+ continue;
+ }
+
+ decal_cluster_min = components_max(decal_cluster_min, cluster_min_bound);
+ decal_cluster_max = components_max(decal_cluster_max, cluster_min_bound);
+ decal_cluster_min = components_min(decal_cluster_min, cluster_max_bound);
+ decal_cluster_max = components_min(decal_cluster_max, cluster_max_bound);
+
+ /*
+ LOGI << "decal_cluster_min: " << decal_cluster_min << std::endl;
+ LOGI << "decal_cluster_max: " << decal_cluster_max << std::endl << std::endl;
+ LOGI << "view_point " << point << ": " << view_point << std::endl;
+ LOGI << "proj_point " << point << ": " << proj_point << std::endl;
+ LOGI << "screen_point " << point << ": " << screen_point << std::endl;
+ */
+
+ if(g_no_decal_elements){
+ continue;
+ }
+
+ unsigned int decal_index = num_alive_decals;
+ num_alive_decals++;
+
+
+ vec3 combined_tint = dec->color_tint_component_.tint_ * (1.0f + dec->color_tint_component_.overbright_ * 0.3f);
+ memcpy(&decal.decal_tint, &combined_tint, 4 * 3);
+
+ vec3 scale = dec->GetScale();
+ decal.decal_scale[0] = scale.x();
+ decal.decal_scale[1] = scale.y();
+ decal.decal_scale[2] = scale.z();
+ decal.decal_spawn_time = dec->spawn_time_;
+ quaternion rotation = dec->GetRotation();
+ memcpy(&decal.decal_rotation[0], &rotation.entries[0], 4 * sizeof(float));
+ vec3 pos = dec->GetTranslation();
+ decal.decal_position[0] = pos.x();
+ decal.decal_position[1] = pos.y();
+ decal.decal_position[2] = pos.z();
+ decal.decal_pad1 = 0.0f;
+
+ decal.decal_uv[0] = dec->texture->color_texture_ref.uv_start.entries[0];
+ decal.decal_uv[1] = dec->texture->color_texture_ref.uv_start.entries[1];
+ decal.decal_uv[2] = dec->texture->color_texture_ref.uv_size.entries[0];
+ decal.decal_uv[3] = dec->texture->color_texture_ref.uv_size.entries[1];
+
+ if (decal_normals){
+ decal.decal_normal[0] = dec->texture->normal_texture_ref.uv_start.entries[0];
+ decal.decal_normal[1] = dec->texture->normal_texture_ref.uv_start.entries[1];
+ decal.decal_normal[2] = dec->texture->normal_texture_ref.uv_size.entries[0];
+ decal.decal_normal[3] = dec->texture->normal_texture_ref.uv_size.entries[1];
+ }
+
+ memcpy(&decal_tbo[sizeof(ShaderDecal) / sizeof(float) * decal_index], &decal, sizeof(ShaderDecal));
+
+ unsigned int x_min = (unsigned int)decal_cluster_min.x();
+ unsigned int x_max = (unsigned int)ceil(decal_cluster_max.x());
+
+ unsigned int y_min = (unsigned int)decal_cluster_min.y();
+ unsigned int y_max = (unsigned int)ceil(decal_cluster_max.y());
+
+ // since z is "reversed" and min is actually the farthest value
+ // we swap min and max
+ unsigned int z_min = (unsigned int)(ZCLUSTERFUNC(decal_cluster_max.z(), z_near, z_mult));
+ unsigned int z_max = (unsigned int)ceil(ZCLUSTERFUNC(decal_cluster_min.z(), z_near, z_mult));
+
+ z_min = max(0u, z_min);
+ z_max = min(z_max, num_z_clusters - 1);
+
+ LOG_ASSERT_LTEQ(x_min, x_max);
+ LOG_ASSERT_LTEQ(y_min, y_max);
+ LOG_ASSERT_LTEQ(z_min, z_max);
+
+ // these can be equal to the max since they're used in a C-style loop
+ LOG_ASSERT_LTEQ(x_max, grid_width);
+ LOG_ASSERT_LTEQ(y_max, grid_height);
+ LOG_ASSERT_LTEQ(z_max, num_z_clusters);
+
+ /*
+ LOGI << "decal " << i << std::endl;
+ LOGI << " min: " << decal_cluster_min << std::endl;
+ LOGI << " max: " << decal_cluster_max << std::endl;
+ LOGI << " z:" << z_min << " - " << z_max << std::endl;
+ LOGI << std::endl;
+ */
+
+ for (unsigned int x = x_min; x < x_max; x++) {
+ for (unsigned int y = y_min; y < y_max; y++) {
+ for (unsigned int z = z_min; z < z_max; z++) {
+ // TODO: we might get better results with something like morton order
+ // it might increase cache locality in the shader
+ unsigned int decal_cluster_index = (y * grid_width + x) * num_z_clusters + z;
+ LOG_ASSERT_LT(decal_cluster_index, numclusters);
+
+ unsigned int count = cluster_decal_counts[decal_cluster_index];
+ cluster_decal_counts[decal_cluster_index]++;
+
+ // write new to 1d buffer and update 3d buffer
+ ClusterData new_data;
+ new_data.item = decal_index;
+ new_data.next = cluster_list_heads[decal_cluster_index];
+ cluster_list_heads[decal_cluster_index] = cluster_list_contents.size();
+ cluster_list_contents.push_back(new_data);
+ }
+ }
+ }
+ }
+
+ // fill lookup buffer
+ // TODO: try to reuse buffer contents
+ cluster_decals.clear();
+ for (int unsigned i = 0; i < numclusters; i++) {
+ unsigned int cluster_num_decals = 0;
+ decal_grid_lookup[i] = SetIndex(0, cluster_decals.size());
+
+ uint32_t index = cluster_list_heads[i];
+ while (index != 0) {
+ const ClusterData &data = cluster_list_contents[index];
+ cluster_decals.push_back(data.item);
+ cluster_num_decals++;
+ index = data.next;
+ }
+
+ decal_grid_lookup[i] = SetCount(decal_grid_lookup[i], cluster_num_decals);
+ }
+
+ // LOGI << "decal buffer size: " << cluster_decals.size() << std::endl;
+ }
+
+ std::vector<DynamicLight> &dynamic_lights = dynamic_light_collection.dynamic_lights;
+ std::vector<float> light_data_buffer;
+ unsigned int num_alive_lights = 0;
+
+ unsigned int num_lights = dynamic_lights.size();
+ LOG_ASSERT_LT(num_lights, 65536);
+
+ mat4 cluster_view_mat = cluster_mat * view_mat;
+
+ LOG_ASSERT_EQ((sizeof(ShaderLight) % sizeof(float)) , 0);
+ const unsigned int light_params = sizeof(ShaderLight) / sizeof(float);
+ light_data_buffer.resize(num_lights * light_params);
+ ShaderLight l;
+ l.padding = 0.0f;
+
+ for (unsigned int i = 0; i < numclusters; i++) {
+ cluster_light_counts[i] = 0;
+ }
+
+ memset(&cluster_list_heads[0], 0, sizeof(uint32_t) * cluster_list_heads.size());
+ cluster_list_contents.clear();
+ cluster_list_contents.push_back(ClusterData());
+
+ for (unsigned int light_index = 0; light_index < num_lights; light_index++) {
+ const DynamicLight &dl = dynamic_lights[light_index];
+ const vec3 world_pos = dl.pos;
+ vec3 view_pos = view_mat * world_pos;
+
+ // TODO: don't use bounding box, there's a sphere algorithm in the paper
+
+ // FIXME: not correct, doesn't account for projection
+ vec3 view_min = view_pos - vec3(dl.radius*0.5f);
+ vec3 view_max = view_pos + vec3(dl.radius*0.5f);
+
+ // drop it if it's entirely behind the camera
+ if (view_min.z() > -z_near) {
+ continue;
+ }
+
+ // cap at near plane
+ view_max.z() = min(-z_near, view_max.z());
+
+ Box view_box;
+ view_box.center = (view_min + view_max) * 0.5f;
+ view_box.dims = view_max - view_min;
+
+ // calculate projection space bounding box
+ vec2 proj_min(FLT_MAX, FLT_MAX);
+ vec2 proj_max(-FLT_MAX, -FLT_MAX);
+
+ // TODO: we probably don't need to do the whole box, just the
+ // four points on the near face
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ vec4 proj_point = cluster_mat * vec4(view_box.GetPoint(point), 1.0f);
+ // we know it's in front of the screen since we capped it at near plane
+ LOG_ASSERT_GTEQ(proj_point.w(), 0.0f);
+ vec2 proj_point2 = proj_point.xy() * (1.0f / proj_point.w());
+
+ proj_min = components_min2(proj_min, proj_point2);
+ proj_max = components_max2(proj_max, proj_point2);
+ }
+
+ /*
+ LOGI << "proj_min: " << proj_min << std::endl;
+ LOGI << "proj_max: " << proj_max << std::endl;
+ */
+
+ // we use projection-space .xy and view-space .z for clustering
+ vec3 cluster_min(proj_min, view_min.z());
+ vec3 cluster_max(proj_max, view_max.z());
+
+ // drop the light if it's entirely off-screen
+ if (cluster_min.x() > cluster_max_bound.x()
+ || cluster_max.x() < cluster_min_bound.x()
+ || cluster_min.y() > cluster_max_bound.y()
+ || cluster_max.y() < cluster_min_bound.y()) {
+ continue;
+ }
+
+ cluster_min = components_max(cluster_min, cluster_min_bound);
+ cluster_max = components_max(cluster_max, cluster_min_bound);
+ cluster_min = components_min(cluster_min, cluster_max_bound);
+ cluster_max = components_min(cluster_max, cluster_max_bound);
+
+ if(g_no_decal_elements){
+ continue;
+ }
+ /*
+ LOGI << "cluster_min: " << cluster_min << std::endl;
+ LOGI << "cluster_max: " << cluster_max << std::endl << std::endl;
+ LOGI << "view_point " << point << ": " << view_point << std::endl;
+ LOGI << "proj_point " << point << ": " << proj_point << std::endl;
+ LOGI << "screen_point " << point << ": " << screen_point << std::endl;
+ */
+
+ unsigned int light_buf_index = num_alive_lights;
+ num_alive_lights++;
+
+ memcpy(l.pos, dl.pos.entries, 3 * sizeof(float));
+ // the bounding box and helper mesh have a size of 0.5 instead of 1.0
+ // so we divide the radius by 2 to make it look right
+ l.radius = dl.radius / 2.0f;
+ memcpy(l.color, dl.color.entries, 3 * sizeof(float));
+ memcpy(&light_data_buffer[light_buf_index * light_params], &l, sizeof(ShaderLight));
+
+ unsigned int x_min = (unsigned int)cluster_min.x();
+ unsigned int x_max = (unsigned int)ceil(cluster_max.x());
+
+ unsigned int y_min = (unsigned int)cluster_min.y();
+ unsigned int y_max = (unsigned int)ceil(cluster_max.y());
+
+ // since z is "reversed" and min is actually the farthest value
+ // we swap min and max
+ unsigned int z_min = (unsigned int)(ZCLUSTERFUNC(cluster_max.z(), z_near, z_mult));
+ unsigned int z_max = (unsigned int)ceil(ZCLUSTERFUNC(cluster_min.z(), z_near, z_mult));
+
+ z_min = max(0u, z_min);
+ z_max = min(z_max, num_z_clusters - 1);
+
+ LOG_ASSERT_LTEQ(x_min, x_max);
+ LOG_ASSERT_LTEQ(y_min, y_max);
+ LOG_ASSERT_LTEQ(z_min, z_max);
+
+ // these can be equal to the max since they're used in a C-style loop
+ LOG_ASSERT_LTEQ(x_max, grid_width);
+ LOG_ASSERT_LTEQ(y_max, grid_height);
+ LOG_ASSERT_LTEQ(z_max, num_z_clusters);
+
+ /*
+ LOGI << "decal " << i << std::endl;
+ LOGI << " min: " << cluster_min << std::endl;
+ LOGI << " max: " << cluster_max << std::endl;
+ LOGI << " z:" << z_min << " - " << z_max << std::endl;
+ LOGI << std::endl;
+ */
+
+ for (unsigned int x = x_min; x < x_max; x++) {
+ for (unsigned int y = y_min; y < y_max; y++) {
+ for (unsigned int z = z_min; z < z_max; z++) {
+ unsigned int cluster_index = (y * grid_width + x) * num_z_clusters + z;
+ LOG_ASSERT_LT(cluster_index, numclusters);
+
+ unsigned int count = cluster_light_counts[cluster_index];
+ cluster_light_counts[cluster_index]++;
+
+ // write new to 1d buffer and update 3d buffer
+ ClusterData new_data;
+ new_data.item = light_buf_index;
+ new_data.next = cluster_list_heads[cluster_index];
+ cluster_list_heads[cluster_index] = cluster_list_contents.size();
+ cluster_list_contents.push_back(new_data);
+ }
+ }
+ }
+ }
+
+ // LOGI << "num_alive_lights: " << num_alive_lights << std::endl;
+
+ // fill the lookup buffer with data
+ // TODO: try to reuse buffer contents
+ cluster_lights.clear();
+ for (int unsigned j = 0; j < numclusters; j++) {
+ unsigned int cluster_num_lights = 0;
+ light_grid_lookup[j] = SetIndex(0, cluster_lights.size());
+
+ uint32_t index = cluster_list_heads[j];
+ while (index != 0) {
+ const ClusterData &data = cluster_list_contents[index];
+ cluster_lights.push_back(data.item);
+ cluster_num_lights++;
+ index = data.next;
+ }
+
+ light_grid_lookup[j] = SetCount(light_grid_lookup[j], cluster_num_lights);
+ }
+
+ LOG_ASSERT_EQ(sizeof(ShaderDecal), (12 + 4 + 4 + 4) * 4);
+ LOG_ASSERT_EQ(sizeof(ShaderClusterInfo), (8 * 4) * 4);
+
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Setup batch data");
+
+ clusterInfo.num_decals = num_alive_decals;
+ clusterInfo.num_lights = num_alive_lights;
+
+ unsigned int decal_grid_lookup_offset = 0;
+ unsigned int decal_grid_lookup_size = numclusters * NUM_GRID_COMPONENTS;
+ unsigned int cluster_decals_offset = decal_grid_lookup_size;
+ unsigned int cluster_decals_size = cluster_decals.size();
+ unsigned int light_cluster_data_offset = cluster_decals_offset + cluster_decals_size;
+ unsigned int light_cluster_data_size = cluster_lights.size();
+
+ const bool kBandwidthDisplay = false;
+ if(kBandwidthDisplay){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_grid_lookup_size: %u", decal_grid_lookup_size);
+ Engine::Instance()->gui.AddDebugText("decal_grid_lookup_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "cluster_decals_size: %u", cluster_decals_size);
+ Engine::Instance()->gui.AddDebugText("cluster_decals_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "light_cluster_data_size: %u", light_cluster_data_size);
+ Engine::Instance()->gui.AddDebugText("light_cluster_data_size", buf, 0.5f);
+ }
+
+ unsigned int buf_size = decal_grid_lookup_size + cluster_decals.size() + cluster_lights.size();
+ if (buf_size > decal_cluster_buffer.size()) {
+ // resize the buffer
+ decal_cluster_buffer.resize(buf_size, 0);
+ }
+
+ // first copy them into decal_cluster_buffer ...
+ if (decal_grid_lookup_size > 0) {
+ for (unsigned int i = 0; i < numclusters; i++) {
+ unsigned int offs = decal_grid_lookup_offset + (NUM_GRID_COMPONENTS * i);
+ memcpy(&decal_cluster_buffer[offs + 0], &decal_grid_lookup[i], 4);
+ memcpy(&decal_cluster_buffer[offs + 1], &light_grid_lookup[i], 4);
+ }
+ }
+
+ if (!cluster_decals.empty()) {
+ memcpy(&decal_cluster_buffer[cluster_decals_offset], &cluster_decals[0], cluster_decals_size * 4);
+ }
+
+ if (!cluster_lights.empty()) {
+ memcpy(&decal_cluster_buffer[light_cluster_data_offset], &cluster_lights[0], light_cluster_data_size * 4);
+ }
+
+ // light data and decal data in the same buffer, decal data first
+ // light_data_offset is in floats because we're copying to float buffer...
+ unsigned int light_data_offset = num_alive_decals * sizeof(ShaderDecal) / sizeof(float);
+ unsigned int light_data_size = num_alive_lights * sizeof(ShaderLight) / sizeof(float);
+
+ if (light_data_size > 0) {
+ // make sure it fits
+ if (decal_tbo.size() < light_data_offset + light_data_size) {
+ decal_tbo.resize(light_data_offset + light_data_size, 0.0f);
+ }
+
+ // TODO: we could avoid this memcpy by packing the data in decal_tbo buffer
+ // when looping over lights
+ memcpy(&decal_tbo[light_data_offset], &light_data_buffer[0], light_data_size * 4);
+ }
+
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ZONE(g_profiler_ctx, "Upload buffer data");
+ // ...and then send it to the GPU
+ if(kBandwidthDisplay){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "buf_size*4: %u", buf_size*4);
+ Engine::Instance()->gui.AddDebugText("buf_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "decal_tbo.size(): %u", decal_tbo.size());
+ Engine::Instance()->gui.AddDebugText("decal_tbo.size()", buf, 0.5f);
+ FormatString(buf, kBufSize, "sizeof(ShaderClusterInfo): %u", sizeof(ShaderClusterInfo));
+ Engine::Instance()->gui.AddDebugText("sizeof(ShaderClusterInfo)", buf, 0.5f);
+ }
+
+ PrecisionStopwatch stop_watch;
+ stop_watch.Start();
+ Textures::Instance()->updateBufferTexture(decal_cluster_texture, buf_size * 4, &decal_cluster_buffer[0]);
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_cluster_texture: %u us", time / 1000);
+ Engine::Instance()->gui.AddDebugText("decal_cluster_texture", buf, 0.5f);
+ }
+ Textures::Instance()->updateBufferTexture(decal_data_texture, decal_tbo.size() * sizeof(float), &decal_tbo[0]);
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_data_texture: %u us", time / 1000);
+ Engine::Instance()->gui.AddDebugText("decal_data_texture", buf, 0.5f);
+ }
+
+ clusterInfo.light_cluster_data_offset = light_cluster_data_offset;
+ // ... but here we divide by 4 since the shader samples from a vec4 buffer
+ LOG_ASSERT_EQ((sizeof(ShaderDecal) % (sizeof(float) * 4)), 0);
+ clusterInfo.light_data_offset = light_data_offset / 4;
+
+
+ stop_watch.Start();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Cluster info buffer");
+ if(uniform_ring_buffer.gl_id == -1){
+ uniform_ring_buffer.Create(128*1024);
+ }
+ uniform_ring_buffer.Fill(sizeof(ShaderClusterInfo), &clusterInfo);
+ }
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "clusterInfo: %u us", time / 1000000);
+ Engine::Instance()->gui.AddDebugText("clusterInfo", buf, 0.5f);
+ }
+ if(kBandwidthDisplay){
+ static GLint max_tbo_size = -1;
+ if(max_tbo_size == -1){
+ glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_tbo_size);
+ }
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1){
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+ }
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "GL_MAX_TEXTURE_BUFFER_SIZE: %d", max_tbo_size);
+ Engine::Instance()->gui.AddDebugText("GL_MAX_TEXTURE_BUFFER_SIZE", buf, 0.5f);
+ FormatString(buf, kBufSize, "GL_MAX_UNIFORM_BLOCK_SIZE: %d", max_ubo_size);
+ Engine::Instance()->gui.AddDebugText("GL_MAX_UNIFORM_BLOCK_SIZE", buf, 0.5f);
+ }
+ }
+}
+
+
+void SceneGraph::BindDecals(int the_shader)
+{
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int cluster_block_index = shaders->GetUBOBindIndex(the_shader, "ClusterInfo");
+
+ if (cluster_block_index == (int)GL_INVALID_INDEX) {
+ // no decals in this shader, skip the whole thing
+ return;
+ }
+
+ // TODO: pass in decal info so they can be drawn in one pass
+ // Pass in structure of all decals that overlap any instances of this type
+ // Decal transform
+ // Decal texture
+
+ // Pass in number of intersecting decals for each instance
+ // For each instance of envobject
+ // Pass in indices of each decal
+ TextureRef decal_color_texture_ref;
+
+ //Disabling normal texture as we've reached the texture limit.
+ TextureRef decal_normal_texture_ref;
+
+ decal_color_texture_ref = DecalTextures::Instance()->coloratlas->atlas_texture;
+ if (decal_color_texture_ref.valid()){
+ textures->bindTexture(decal_color_texture_ref, TEX_DECAL_COLOR);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_DECAL_COLOR), TEX_DECAL_COLOR);
+ }
+
+ if (config["decal_normals"].toNumber<bool>()){
+ decal_normal_texture_ref = DecalTextures::Instance()->normalatlas->atlas_texture;
+ if (decal_normal_texture_ref.valid()){
+ textures->bindTexture(decal_normal_texture_ref, TEX_DECAL_NORMAL);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_DECAL_NORMAL), TEX_DECAL_NORMAL);
+ }
+ }
+}
+
+
+void SceneGraph::BindLights(int the_shader) {
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+
+ int cluster_block_index = shaders->GetUBOBindIndex(the_shader, "ClusterInfo");
+ if (cluster_block_index == (int)GL_INVALID_INDEX) {
+ // no decals in this shader, skip the whole thing
+ return;
+ }
+
+ // Set up cluster parameters UBO
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Setup decal parameters UBO");
+
+ GLint block_size = shaders->returnShaderBlockSize(cluster_block_index, the_shader);
+
+ LOG_ASSERT_LT(block_size, 16384);
+ LOG_ASSERT_EQ(block_size, sizeof(ShaderClusterInfo));
+
+ if(uniform_ring_buffer.gl_id != -1){
+ glBindBufferBase(GL_UNIFORM_BUFFER, UBO_CLUSTER_DATA, uniform_ring_buffer.gl_id);
+ glBindBufferRange( GL_UNIFORM_BUFFER, UBO_CLUSTER_DATA, uniform_ring_buffer.gl_id, uniform_ring_buffer.offset, uniform_ring_buffer.next_offset - uniform_ring_buffer.offset);
+ }
+ glUniformBlockBinding(shaders->programs[the_shader].gl_program, cluster_block_index, UBO_CLUSTER_DATA);
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_LIGHT_DECAL_DATA_BUFFER), TEX_LIGHT_DECAL_DATA_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_CLUSTER_BUFFER), TEX_CLUSTER_BUFFER);
+
+ textures->bindTexture(decal_data_texture, TEX_LIGHT_DECAL_DATA_BUFFER);
+ textures->bindTexture(decal_cluster_texture, TEX_CLUSTER_BUFFER);
+}
+
+static int HexToVal(char hex){
+ if(hex >= '0' && hex <= '9'){
+ return hex - '0';
+ } else if(hex >= 'A' && hex <= 'F'){
+ return hex - 'A' + 10;
+ } else {
+ return -1;
+ }
+}
+
+vec3 ColorFromString(const char* str) {
+ const char *char_ptr = str;
+ int color_id = 0;
+ int val = 0;
+ vec3 color;
+ char char_val = ' ';
+ while(char_val != '\0'){
+ char_val = *char_ptr;
+ if(char_val >= '0' && char_val <= '9'){
+ val *= 10;
+ val += char_val - '0';
+ } else if(char_val == ',' || char_val == '\0') {
+ color[color_id] = val / 255.0f;
+ ++color_id;
+ val = 0;
+ }
+ ++char_ptr;
+ }
+ return color;
+}
+
+void SceneGraph::ApplyScriptParams(SceneGraph* scenegraph, const ScriptParamMap& spm) {
+ scenegraph->level->SetScriptParams(spm);
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Sky Rotation");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ float new_sky_rotation = sp.GetFloat();
+ Sky* sky = scenegraph->sky;
+ if(new_sky_rotation != sky->sky_rotation){
+ sky->sky_rotation = sp.GetFloat();
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Saturation");
+ if(iter == spm.end()){
+ scenegraph->level->script_params().ASAddFloat("Saturation", 1.0f);
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Sky Tint");
+ ScriptParamMap::const_iterator iter2 = spm.find("Sky Brightness");
+ if(iter != spm.end() && iter2 != spm.end()){
+ const ScriptParam& sp = iter->second;
+ const std::string& new_sky_tint = sp.GetString();
+ /*if(new_sky_tint.size() == 7 && new_sky_tint[0] == '#'){
+ vec3 color;
+ color[0] = (HexToVal(new_sky_tint[1])*16.0f + HexToVal(new_sky_tint[2])) / 255.0f;
+ color[1] = (HexToVal(new_sky_tint[3])*16.0f + HexToVal(new_sky_tint[4])) / 255.0f;
+ color[2] = (HexToVal(new_sky_tint[5])*16.0f + HexToVal(new_sky_tint[6])) / 255.0f;
+ Sky* sky = scenegraph->sky;
+ if(color != sky->sky_tint){
+ sky->sky_tint = color;
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }*/
+ vec3 color = ColorFromString(new_sky_tint.c_str());
+ color *= iter2->second.GetFloat();
+ Sky* sky = scenegraph->sky;
+ if(color != sky->sky_tint){
+ sky->sky_tint = color;
+ sky->sky_base_tint = color;
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR White point");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_white_point = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR Black point");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_black_point = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR Bloom multiplier");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_bloom_mult = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Fog amount");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ scenegraph->fog_amount = sp.GetFloat();
+ scenegraph->haze_mult = 8.0f * (float) pow(10, scenegraph->fog_amount - 5.0f);
+ }
+ }
+}
+
+void SceneGraph::PrintCurrentObjects()
+{
+ object_list::iterator objit = objects_.begin();
+
+ LOGI << "Listing current objects in scene" << std::endl;
+ for(; objit != objects_.end(); objit++ )
+ {
+ LOGI << **objit << std::endl;
+ }
+}
+
+int SceneGraph::CountObjectsWithName(const char* name) {
+ int count = 0;
+ object_list::iterator objit = objects_.begin();
+
+ LOGI << "Listing current objects in scene" << std::endl;
+ for(; objit != objects_.end(); objit++ ) {
+ if( strmtch((*objit)->name,name) ) {
+ count++;
+ }
+ }
+ return count;
+}
+
+bool SceneGraph::IsObjectSane(Object* obj) {
+ if(obj == NULL) return false;
+
+ for( size_t i = 0; i < destruction_sanity_size; i++ ) {
+ if( destruction_sanity[i] == obj ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const char* SceneGraph::GetDestroyedObjectInfo(int object_id) {
+ if( object_id >= 0 ) {
+ for( unsigned i = 0; i < destruction_memory_size; i++ ) {
+ if( destruction_memory_ids[i] == object_id) {
+ return &destruction_memory_strings[i*destruction_memory_string_size];
+ }
+ }
+ }
+ return "";
+}
+
+void SceneGraph::DumpState() {
+ std::ofstream output;
+ char dump_path[kPathSize];
+ GetScenGraphDumpPath(dump_path, kPathSize);
+
+ my_ofstream_open(output,dump_path);
+
+ if( output.is_open() ) {
+ LOGI << "Dumping all objects in scenegraph into " << dump_path << std::endl;
+ for( size_t i = 0; i < objects_.size(); i++ ) {
+ if( output.good() ) {
+ output << "[" << objects_[i]->GetID() << "]: \"" << CStringFromEntityType(objects_[i]->GetType()) << "\" " << objects_[i]->GetName() << std::endl;
+ }
+ }
+ } else {
+ LOGE << "Failed at opening " << dump_path << " for dumping. " << std::endl;
+ }
+}
+
+void SceneGraph::GetParticleShaderNames(std::map<std::string, int>& preload_shaders) {
+ Path p = FindFilePath("Data/shader_preload.xml", kDataPaths, true);
+ if(!p.isValid()) {
+ LOGW << "Couldn't load " << p.GetOriginalPath() << std::endl;
+ return;
+ }
+
+ if(!CheckFileAccess(p.GetFullPath())) {
+ LOGW << "Couldn't access " << p.GetFullPath() << std::endl;
+ return;
+ }
+
+ TiXmlDocument doc;
+ if(!doc.LoadFile(p.GetFullPath())) {
+ LOGE << "Couldn't load " << p.GetFullPath() << ": " << doc.ErrorDesc() << " on row " << doc.ErrorRow() << std::endl;
+ return;
+ }
+
+ TiXmlElement* root = doc.RootElement();
+ if(root && strcmp(root->Value(), "Shaders") == 0) {
+ root = root->FirstChildElement();
+ } else {
+ LOGE << p.GetFullPath() << " root element null or not \"Shaders\"" << std::endl;
+ return;
+ }
+
+ for(const TiXmlElement* field = root; field; field = field->NextSiblingElement()) {
+ const char* field_value = field->Value();
+
+ if(strcmp(field_value, "Shader") == 0) {
+ const char* attribute = field->Attribute("name");
+ const char* suffix = field->Attribute("suffix");
+ const char* optional = field->Attribute("optional");
+
+ int draw_type = 0;
+ if(suffix) {
+ draw_type = (strcmp(suffix, "true") == 0) ? kFullDraw : 0;
+ }
+ if(optional) {
+ if(strcmp(optional, "none") == 0) {
+ draw_type |= kOptionalNone;
+ } else if(strcmp(optional, "geometry") == 0) {
+ draw_type |= kOptionalGeometry;
+ } else if(strcmp(optional, "tessellation") == 0) {
+ draw_type |= kOptionalTessellation;
+ }
+ }
+
+ if(attribute) {
+ preload_shaders[attribute] = draw_type;
+ } else {
+ LOGE << "No name attribute found when parsing " << p.GetFullPath() << std::endl;
+ }
+ }
+ }
+}
+
+void SceneGraph::PreloadForDrawType(std::map<std::string, int>& preload_shaders, PreloadType type) {
+ Shaders* shaders = Shaders::Instance();
+ Object::DrawType draw_type;
+ switch(type) {
+ case kDrawDepthOnly: draw_type = Object::kDrawDepthOnly; break;
+ case kDrawAllShadowCascades: draw_type = Object::kDrawAllShadowCascades; break;
+ case kDrawDepthNoAA: draw_type = Object::kDrawDepthNoAA; break;
+ case kFullDraw: draw_type = Object::kFullDraw; break;
+ case kWireframe: draw_type = Object::kWireframe; break;
+ case kDecal: draw_type = Object::kDecal; break;
+ default:
+ return;
+ }
+
+ UpdateShaderSuffix(this, draw_type);
+ for(std::map<std::string, int>::iterator iter = preload_shaders.begin(); iter != preload_shaders.end(); ++iter) {
+ if(iter->second & type) {
+ const int kShaderStrSize = 1024;
+ char buf[kShaderStrSize];
+ FormatString(buf, kShaderStrSize, "%s %s", iter->first.c_str(), global_shader_suffix);
+ Shaders::OptionalShaders optional = Shaders::kNone;
+ if(iter->second & kOptionalGeometry)
+ optional = Shaders::kGeometry;
+ else if(iter->second & kOptionalTessellation)
+ optional = Shaders::kTesselation;
+ shaders->createProgram(shaders->returnProgram(buf, optional));
+ }
+ }
+}
+
+void SceneGraph::PreloadShaders() {
+ LOGI << "Preloading shaders..." << std::endl;
+ PROFILER_ZONE(g_profiler_ctx, "Preload shaders");
+ // There is no easy way to call DetailObjectSurface::GetShaderNames, so do it here for now
+ const char* detail_object_names[6] =
+ {
+ "envobject #DETAIL_OBJECT"
+ , "envobject #DETAIL_OBJECT #PLANT"
+ , "envobject #DETAIL_OBJECT #PLANT #LESS_PLANT_MOVEMENT"
+ , "envobject #DETAIL_OBJECT #TERRAIN"
+ , "envobject #DETAIL_OBJECT #TERRAIN #PLANT"
+ , "envobject #DETAIL_OBJECT #TERRAIN #PLANT #LESS_PLANT_MOVEMENT"
+ };
+
+ for(int i = 0; i < 6; ++i) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+
+ FormatString(shader_str[0], kShaderStrSize, "%s", detail_object_names[i]);
+ if(!Graphics::Instance()->config_.detail_object_decals()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DECALS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(!Graphics::Instance()->config_.detail_object_shadows()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DETAIL_OBJECT_SHADOWS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ preload_shaders[shader_str[0]] = kFullDraw | kDrawDepthOnly;
+ }
+
+ GetParticleShaderNames(preload_shaders);
+ Engine::Instance()->GetShaderNames(preload_shaders);
+ Graphics::Instance()->GetShaderNames(preload_shaders);
+ sky->GetShaderNames(preload_shaders);
+ flares.GetShaderNames(preload_shaders);
+ const PreloadType preload_types[6] = {
+ kDrawDepthOnly
+ , kDrawAllShadowCascades
+ , kDrawDepthNoAA
+ , kFullDraw
+ , kWireframe
+ , kDecal
+ };
+ for(int i = 0; i < 6; ++i)
+ PreloadForDrawType(preload_shaders, preload_types[i]);
+
+ // Load everything that isn't any of the above types
+ Shaders* shaders = Shaders::Instance();
+ for(std::map<std::string, int>::iterator iter = preload_shaders.begin(); iter != preload_shaders.end(); ++iter) {
+ if((iter->second & kPreloadTypeAll) == 0) {
+ Shaders::OptionalShaders optional = Shaders::kNone;
+ if(iter->second & kOptionalGeometry)
+ optional = Shaders::kGeometry;
+ else if(iter->second & kOptionalTessellation)
+ optional = Shaders::kTesselation;
+ shaders->createProgram(shaders->returnProgram(iter->first, optional));
+ }
+ }
+}
+
+void SceneGraph::LoadReflectionCaptureCubemaps()
+{
+ PROFILER_ZONE(g_profiler_ctx, "Load reflection capture cubemaps");
+ for (object_list::iterator iter = objects_.begin();
+ iter != objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _reflection_capture_object){
+ char save_path[kPathSize];
+ FormatString(save_path, kPathSize, "%s_refl_cap_%d.hdrcube", level_path_.GetOriginalPath(), obj->GetID());
+ char abs_path[kPathSize];
+ if(FindFilePath(save_path, abs_path, kPathSize, kAnyPath, false) != -1){
+ ReflectionCaptureObject* rco = (ReflectionCaptureObject*)obj;
+ rco->cube_map_ref = Textures::LoadCubeMapMipmapsHDR(abs_path); //TODO: if no_reflection_capture is true, only the global capture cube should be loaded.
+ if(rco->cube_map_ref.valid()){
+ rco->dirty = false;
+ if(rco->GetScriptParams()->ASGetInt("Global") == 1){
+ sky->SetSpecularCubeMapTexture(rco->cube_map_ref);
+ }
+ } else {
+ rco->dirty = true;
+ }
+ }
+ }
+ }
+ reflection_data_loaded = true;
+}
+
+//TODO: make a version that only clears the dynamic cubemaps.
+void SceneGraph::UnloadReflectionCaptureCubemaps() {
+ for (object_list::iterator iter = objects_.begin();
+ iter != objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _reflection_capture_object){
+ ReflectionCaptureObject* rco = (ReflectionCaptureObject*)obj;
+ rco->cube_map_ref.clear();
+ rco->dirty = true;
+ }
+ }
+
+ sky->ResetSpecularCubeMapTexture();
+ reflection_data_loaded = false;
+}