// Copyright (c) 2019 Lawnjelly // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "lroom_manager.h" #include "core/engine.h" #include "scene/3d/camera.h" #include "scene/3d/mesh_instance.h" #include "lroom_converter.h" #include "ldebug.h" #include "scene/3d/immediate_geometry.h" #include "scene/3d/light.h" #include "scene/3d/baked_lightmap.h" #include "lroom.h" #include "lhelper.h" #include "lscene_saver.h" #define LROOMLIST m_pRoomList #define CHECK_ROOM_LIST if (!CheckRoomList())\ {\ WARN_PRINT_ONCE("rooms is unset");\ return false;\ } LRoomManager::LRoomManager() { m_ID_camera = 0; m_ID_DebugPlanes = 0; m_ID_DebugBounds = 0; m_ID_DebugLights = 0; m_ID_DebugLightVolumes = 0; m_ID_DebugFrustums = 0; m_ID_RoomList = 0; m_uiFrameCounter = 0; m_iLoggingLevel = 2; m_bActive = true; m_bFrustumOnly = false; // to know which rooms to hide we keep track of which were shown this, and the previous frame m_pCurr_VisibleRoomList = &m_VisibleRoomList_A; m_pPrev_VisibleRoomList = &m_VisibleRoomList_B; m_bDebugPlanes = false; m_bDebugBounds = false; m_bDebugLights = false; m_bDebugLightVolumes = false; m_bDebugFrustums = false; m_bDebugFrameString = false; m_pRoomList = 0; if (!Engine::get_singleton()->is_editor_hint()) { CreateDebug(); } } int LRoomManager::FindClosestRoom(const Vector3 &pt) const { //print_line("FindClosestRoom"); int closest = -1; float closest_dist = FLT_MAX; // uses bounds if this is available int closest_within = -1; float within_dist = FLT_MAX; for (int n=0; n= m_Rooms.size()) { WARN_PRINT_ONCE("LRoomManager::GetRoom out of range"); return 0; } return &m_Rooms[i]; } LRoom * LRoomManager::GetRoom(int i) { if ((unsigned int) i >= m_Rooms.size()) { WARN_PRINT_ONCE("LRoomManager::GetRoom out of range"); return 0; } return &m_Rooms[i]; } LRoom &LRoomManager::Portal_GetLinkedRoom(const LPortal &port) { return m_Rooms[port.m_iRoomNum]; } // for lights we store the light ID in the metadata void LRoomManager::Meta_SetLightID(Node * pNode, int id) { pNode->set_meta("_llight", id); } int LRoomManager::Meta_GetLightID(Node * pNode) const { //assert (pNode->has_meta("_lroom")); Variant v = pNode->get_meta("_llight"); if (v.get_type() == Variant::NIL) return -1; return v; } void LRoomManager::Meta_SetRoomNum(Node * pNode, int num) { pNode->set_meta("_lroom", num); assert (Meta_GetRoomNum(pNode) == num); } int LRoomManager::Meta_GetRoomNum(Node * pNode) const { //assert (pNode->has_meta("_lroom")); Variant v = pNode->get_meta("_lroom"); if (v.get_type() == Variant::NIL) return -1; return v; } LRoom * LRoomManager::GetRoomFromDOB(Node * pNode) { int iRoom = Meta_GetRoomNum(pNode); if (iRoom < 0) { if (iRoom == -1) { WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : metadata is empty"); } if (iRoom == -2) { WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : are you updating an unregistered DOB?"); } return 0; } LRoom * pRoom = GetRoom(iRoom); if (pRoom == 0) { WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : pRoom is NULL"); } return pRoom; } // register but let LPortal know which room the dob should start in bool LRoomManager::dob_register_hint(Node * pDOB, float radius, Node * pRoom) { CHECK_ROOM_LIST if (!pDOB) { WARN_PRINT_ONCE("dob_register_hint : pDOB is NULL"); return false; } LPRINT(3, "dob_register_hint " + pDOB->get_name()); if (!pRoom) { WARN_PRINT_ONCE("dob_register_hint : pRoom is NULL"); return false; } int iRoom = Meta_GetRoomNum(pRoom); Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) { WARN_PRINT_ONCE("dob_register_hint : DOB is not a spatial"); return false; } return DobRegister(pSpat, radius, iRoom); } void LRoomManager::CreateDebug() { ImmediateGeometry * p = memnew(ImmediateGeometry); p->set_name("debug_planes"); add_child(p); move_child(p, get_child_count()-1); m_ID_DebugPlanes = p->get_instance_id(); // m_mat_Debug_Planes->set_as_toplevel(true); m_mat_Debug_Planes = Ref(memnew(SpatialMaterial)); m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_UNSHADED, true); // m_mat_Debug_Planes->set_line_width(6.0); // m_mat_Debug_Planes->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); m_mat_Debug_Planes->set_albedo(Color(1, 0, 1, 1)); p->set_material_override(m_mat_Debug_Planes); p->hide(); ImmediateGeometry * b = memnew(ImmediateGeometry); b->set_name("debug_bounds"); add_child(b); move_child(b, get_child_count()-1); m_ID_DebugBounds = b->get_instance_id(); m_mat_Debug_Bounds = Ref(memnew(SpatialMaterial)); //m_mat_Debug_Bounds->set_flag(SpatialMaterial::FLAG_UNSHADED, true); m_mat_Debug_Bounds->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); m_mat_Debug_Bounds->set_cull_mode(SpatialMaterial::CULL_DISABLED); m_mat_Debug_Bounds->set_albedo(Color(0, 0, 1, 0.4)); b->set_material_override(m_mat_Debug_Bounds); b->hide(); { ImmediateGeometry * b = memnew(ImmediateGeometry); b->set_name("debug_lights"); add_child(b); move_child(b, get_child_count()-1); m_ID_DebugLights = b->get_instance_id(); b->set_material_override(m_mat_Debug_Bounds); b->hide(); } { ImmediateGeometry * p = memnew(ImmediateGeometry); p->set_name("debug_lightvolumes"); add_child(p); move_child(p, get_child_count()-1); m_ID_DebugLightVolumes = p->get_instance_id(); // m_mat_Debug_Planes->set_as_toplevel(true); m_mat_Debug_LightVolumes = Ref(memnew(SpatialMaterial)); m_mat_Debug_LightVolumes->set_flag(SpatialMaterial::FLAG_UNSHADED, true); // m_mat_Debug_Planes->set_line_width(6.0); // m_mat_Debug_Planes->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); m_mat_Debug_LightVolumes->set_albedo(Color(0, 1, 1, 1)); p->set_material_override(m_mat_Debug_LightVolumes); p->hide(); } { ImmediateGeometry * p = memnew(ImmediateGeometry); p->set_name("debug_frustums"); add_child(p); move_child(p, get_child_count()-1); m_ID_DebugFrustums = p->get_instance_id(); // m_mat_Debug_Planes->set_as_toplevel(true); m_mat_Debug_Frustums = Ref(memnew(SpatialMaterial)); m_mat_Debug_Frustums->set_flag(SpatialMaterial::FLAG_UNSHADED, true); // m_mat_Debug_Planes->set_line_width(6.0); // m_mat_Debug_Planes->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); // m_mat_Debug_Planes->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); m_mat_Debug_Frustums->set_albedo(Color(1, 0, 0, 1)); p->set_material_override(m_mat_Debug_Frustums); p->hide(); } } ObjectID LRoomManager::DobRegister_FindVIRecursive(Node * pNode) const { // is the node a VI? VisualInstance * pVI = Object::cast_to(pNode); if (pVI) { // take away layer 0 from the dob, so it can be culled effectively pVI->set_layer_mask(0); return pVI->get_instance_id(); } // try the children for (int n=0; nget_child_count(); n++) { ObjectID res = DobRegister_FindVIRecursive(pNode->get_child(n)); if (res) return res; } return 0; } bool LRoomManager::DobRegister(Spatial * pDOB, float radius, int iRoom) { //LPRINT(3, "register_dob " + pDOB->get_name()); if (iRoom == -1) { WARN_PRINT_ONCE("LRoomManager::DobRegister : room ID is -1"); return false; } LRoom * pRoom = GetRoom(iRoom); if (!pRoom) return false; // The dob is derived from spatial, but the visual instances may be children of the dob // rather than the node itself .. we need visual instances for layer culling for shadows LDob dob; dob.m_ID_Spatial = pDOB->get_instance_id(); dob.m_fRadius = radius; dob.m_ID_VI = DobRegister_FindVIRecursive(pDOB); pRoom->DOB_Add(dob); // save the room ID on the dob metadata Meta_SetRoomNum(pDOB, iRoom); // change visibility DobChangeVisibility(pDOB, 0, pRoom); return true; } bool LRoomManager::dob_register(Node * pDOB, float radius) { CHECK_ROOM_LIST if (!pDOB) { WARN_PRINT_ONCE("dob_register : pDOB is NULL"); return false; } LPRINT(3, "dob_register " + pDOB->get_name()); Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) { WARN_PRINT_ONCE("dob_register : DOB is not a spatial"); return false; } Vector3 pt = pSpat->get_global_transform().origin; int iRoomNum = FindClosestRoom(pt); LPRINT(2, "dob_register closest room " + itos(iRoomNum)); return DobRegister(pSpat, radius, iRoomNum); } int LRoomManager::dob_update(Node * pDOB) { // find the room the object is attached to LRoom * pRoom = GetRoomFromDOB(pDOB); if (!pRoom) return -1; Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) return -1; LRoom * pNewRoom = pRoom->DOB_Update(*this, pSpat); if (pNewRoom) { // remove from the list in old room and add to list in new room, and change the metadata int iRoomNum = pNewRoom->m_RoomID; // get dob data to move to new room unsigned int dob_id = pRoom->DOB_Find(pDOB); // if (dob_id == -1) // { // WARN_PRINT_ONCE("DOB is not found in room"); // return -1; // } assert (dob_id != -1); // copy across data before removing const LDob &data = pRoom->DOB_Get(dob_id); pNewRoom->DOB_Add(data); // remove from old room pRoom->DOB_Remove(dob_id); // change visibility DobChangeVisibility(pSpat, pRoom, pNewRoom); // save the room ID on the dob metadata Meta_SetRoomNum(pSpat, iRoomNum); // new room number return iRoomNum; } // still in the same room return pRoom->m_RoomID; } bool LRoomManager::dob_teleport_hint(Node * pDOB, Node * pRoom) { CHECK_ROOM_LIST if (!pDOB) { WARN_PRINT_ONCE("dob_teleport_hint : pDOB is NULL"); return false; } LPRINT(1, "dob_teleport_hint " + pDOB->get_name()); if (!pRoom) { WARN_PRINT_ONCE("dob_teleport_hint : pRoom is NULL"); return false; } int iRoom = Meta_GetRoomNum(pRoom); Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) { WARN_PRINT_ONCE("dob_teleport_hint : DOB is not a spatial"); return false; } return DobTeleport(pSpat, iRoom); } bool LRoomManager::DobTeleport(Spatial * pDOB, int iNewRoomID) { // old room LRoom * pOldRoom = GetRoomFromDOB(pDOB); if (!pOldRoom) { WARN_PRINT_ONCE("LRoomManager::DobTeleport : pOldRoom is NULL"); return false; } if (iNewRoomID == -1) { WARN_PRINT_ONCE("LRoomManager::DobTeleport : iNewRoomID is -1"); return false; } LRoom * pNewRoom = GetRoom(iNewRoomID); if (!pNewRoom) return false; // detach from old room, add to new room // get dob data to move to new room unsigned int dob_id = pOldRoom->DOB_Find(pDOB); assert (dob_id != -1); // copy across data before removing const LDob &data = pOldRoom->DOB_Get(dob_id); pNewRoom->DOB_Add(data); // remove from old room pOldRoom->DOB_Remove(dob_id); // save the room ID on the dob metadata Meta_SetRoomNum(pDOB, iNewRoomID); // change visibility DobChangeVisibility(pDOB, pOldRoom, pNewRoom); return true; } // not tested... bool LRoomManager::dob_teleport(Node * pDOB) { CHECK_ROOM_LIST Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) return false; Vector3 pt = pSpat->get_global_transform().origin; int iRoomNum = FindClosestRoom(pt); //print_line("dob_teleport closest room " + itos(iRoomNum)); if (iRoomNum == -1) return false; return DobTeleport(pSpat, iRoomNum); } bool LRoomManager::dob_unregister(Node * pDOB) { CHECK_ROOM_LIST LRoom * pRoom = GetRoomFromDOB(pDOB); // change the meta data on the DOB .. this will catch trying to update an unregistered DOB Meta_SetRoomNum(pDOB, -2); if (pRoom) { unsigned int dob_id = pRoom->DOB_Find(pDOB); return pRoom->DOB_Remove(dob_id); } return false; } void LRoomManager::Light_FrameProcess(int lightID) { if (!m_BF_ProcessedLights.GetBit(lightID)) { m_BF_ProcessedLights.SetBit(lightID, true); // some lights may be processed but found not to intersect the camera frustum if (Light_FindCasters(lightID)) { m_BF_ActiveLights.SetBit(lightID, true); m_ActiveLights.push_back(lightID); } } } // now we are centralizing the tracing out from static and dynamic lights for each frame to this function // returns false if the entire light should be culled bool LRoomManager::Light_FindCasters(int lightID) { // add all shadow casters for this light (new method) const LLight &light = m_Lights[lightID]; /* if (light.m_eClass == LLight::LT_STATIC) { int last_caster = light.m_FirstCaster + light.m_NumCasters; for (int c=light.m_FirstCaster; cget_name()); m_BF_caster_SOBs.SetBit(sobID, true); m_CasterList_SOBs.push_back(sobID); } else { //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); } } // for c through caster } // static lights have a list of SOB casters */ // special case of global area lights if (light.m_iArea != -1) { // special trace for area light if (m_Trace.Trace_Light(*this, light, LTrace::LR_ALL) == false) return false; } else { // can only deal with lights in rooms for now if (light.m_Source.m_RoomID == -1) { return true; } LRoom * pRoom = GetRoom(light.m_Source.m_RoomID); if (!pRoom) return true; if (m_Trace.Trace_Light(*this, light, LTrace::LR_ALL) == false) return false; } // non-area light /* // we now need to trace either just DOBs (in the case of static lights) // or SOBs and DOBs (in the case of dynamic lights) m_LightRender.m_BF_Temp_SOBs.Blank(); m_LightRender.m_Temp_Visible_SOBs.clear(); const LSource &cam = light.m_Source; // cam.Source_SetDefaults(); // cam.m_ptPos = light.m_ptPos; // cam.m_ptDir = light.m_ptDir; m_Trace.Trace_Prepare(*this, cam, m_LightRender.m_BF_Temp_SOBs, m_BF_visible_rooms, m_LightRender.m_Temp_Visible_SOBs, *m_pCurr_VisibleRoomList); unsigned int pool_member = m_Pool.Request(); assert (pool_member != -1); LVector &planes = m_Pool.Get(pool_member); planes.clear(); // create subset planes of light frustum and camera frustum m_MainCamera.AddCameraLightPlanes(*this, cam, planes); m_Trace.Trace_Begin(*pRoom, planes); // we no longer need these planes m_Pool.Free(pool_member); */ // process the sobs that were visible for (int n=0; nget_name()); m_BF_caster_SOBs.SetBit(sobID, true); m_CasterList_SOBs.push_back(sobID); } else { //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); } } return true; } void LRoomManager::Light_UpdateTransform(LLight &light, const Light &glight) const { // assert (glight.is_in_tree()); Transform tr = glight.get_global_transform(); light.m_Source.m_ptPos = tr.origin; light.m_Source.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want } // common stuff for global and local light creation bool LRoomManager::LightCreate(Light * pLight, int roomID, String szArea) { // set culling flag for light // 1 is for lighting objects outside the room system pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); // create new light LLight l; l.Light_SetDefaults(); l.Hidable_Create(pLight); l.m_GodotID = pLight->get_instance_id(); //l.m_iArea = areaID; // store the area name as a string if an area light // as the areas aren't actually created until calling convert if (szArea != "") l.m_szArea = szArea; LSource &lsource = l.m_Source; // direction Light_UpdateTransform(l, *pLight); lsource.m_RoomID = roomID; bool bOK = false; // what kind of light? SpotLight * pSL = Object::cast_to(pLight); if (pSL) { LPRINT(2, "\tSPOTLIGHT detected " + pLight->get_name()); lsource.m_eType = LSource::ST_SPOTLIGHT; lsource.m_fSpread = pSL->get_param(Light::PARAM_SPOT_ANGLE); lsource.m_fRange = pLight->get_param(Light::PARAM_RANGE); bOK = true; } OmniLight * pOL = Object::cast_to(pLight); if (pOL) { LPRINT(2, "\tOMNILIGHT detected " + pLight->get_name()); lsource.m_eType = LSource::ST_OMNI; lsource.m_fRange = pLight->get_param(Light::PARAM_RANGE); bOK = true; } DirectionalLight * pDL = Object::cast_to(pLight); if (pDL) { LPRINT(2, "\tDIRECTIONALLIGHT detected " + pLight->get_name()); lsource.m_eType = LSource::ST_DIRECTIONAL; // no range but max distance? NYI bOK = true; } // don't add if not recognised if (!bOK) { LPRINT(2, "\tLIGHT type unrecognised " + pLight->get_name()); return false; } // turn the local light off to start with if (!lsource.IsGlobal()) { // l.Show(false); //pLight->hide(); } m_Lights.push_back(l); return true; } bool LRoomManager::dynamic_light_register(Node * pLightNode, float radius) { CHECK_ROOM_LIST if (!pLightNode) { WARN_PRINT_ONCE("dynamic_light_register : pLightNode is NULL"); return false; } ObjectID light_id = pLightNode->get_instance_id(); // does the light already exist in the light list? I.e. was it imported as part of the roomlist? for (int n=0; nis_inside_tree() == false) { #ifdef LDEBUG_LIGHT_AFFECTED_ROOMS if (m_bDebugFrameString) DebugString_Add("DynamicLight not in tree\n"); #endif return -1; } // find the light ID from meta data int light_id = Meta_GetLightID(pLightNode); if ((unsigned int) light_id >= (unsigned int) m_Lights.size()) { WARN_PRINT_ONCE("dynamic_light_update : meta light ID out of range"); return -1; } LLight &light = m_Lights[light_id]; // update the llight transform from the node Light * pGLight = light.GetGodotLight(); Light_UpdateTransform(light, *pGLight); Spatial * pSpat = Object::cast_to(pGLight); if (!pSpat) return -1; int iRoom = light.m_Source.m_RoomID; if (iRoom == -1) { WARN_PRINT_ONCE("dynamic_light_update : can't update global light"); return -1; } LRoom * pRoom = GetRoom(iRoom); if (pRoom == 0) { WARN_PRINT_ONCE("dynamic_light_update : pRoom is NULL"); return -1; } LRoom * pNewRoom = pRoom->DOB_Update(*this, pSpat); if (pNewRoom) { // remove from the list in old room and add to list in new room, and change the metadata int iNewRoomID = pNewRoom->m_RoomID; light.m_Source.m_RoomID = iNewRoomID; // change visibility //DobChangeVisibility(pSpat, pRoom, pNewRoom); } // update with a new Trace (we are assuming update is only called if the light has moved) // remove the old local lights for (int n=0; nRemoveLocalLight(light_id); } light.ClearAffectedRooms(); // now do a new trace, and add all the rooms that are hit m_Trace.Trace_Light(*this, light, LTrace::LR_ROOMS); // we should now have a list of the rooms hit in m_LightRender.m_Temp_Visible_Rooms for (int n=0; nAddLocalLight(light_id); } // this may or may not have changed return light.m_Source.m_RoomID; } void LRoomManager::DebugString_Light_AffectedRooms(int light_id) { #ifdef LDEBUG_LIGHT_AFFECTED_ROOMS if (m_bDebugFrameString) { const LLight &light = m_Lights[light_id]; if (!light.m_bShow) return; DebugString_Add("Light " + itos(light_id) + " affect room "); // affected rooms for (int n=0; nget_name()); Light * pLight = Object::cast_to(pLightNode); if (!pLight) { WARN_PRINT_ONCE("light_register : Node is not a light"); return false; } DirectionalLight * pDLight = Object::cast_to(pLightNode); if (!pDLight) { WARN_PRINT_ONCE("light_register : only DirectionalLights are supported, place spotlights and omnis within rooms"); return false; } return LightCreate(pLight, -1, szArea); } void LRoomManager::DobChangeVisibility(Spatial * pDOB, const LRoom * pOld, const LRoom * pNew) { bool bVisOld = false; bool bVisNew = false; if (pOld) bVisOld = pOld->IsVisible(); if (pNew) bVisNew = pNew->IsVisible(); if (bVisOld != bVisNew) { if (!bVisOld) pDOB->show(); else pDOB->hide(); } } int LRoomManager::dob_get_room_id(Node * pDOB) { return Meta_GetRoomNum(pDOB); } // helpers to enable the client to manage switching on and off physics and AI int LRoomManager::rooms_get_num_rooms() const { return m_Rooms.size(); } bool LRoomManager::rooms_is_room_visible(int room_id) const { if (room_id >= m_Rooms.size()) { LWARN(5, "LRoomManager::rooms_is_room_visible : room id higher than number of rooms"); return false; } return m_BF_visible_rooms.GetBit(room_id) != 0; } Array LRoomManager::rooms_get_visible_rooms() const { Array rooms; for (int n=0; nsize(); n++) { rooms.push_back((*m_pCurr_VisibleRoomList)[n]); } return rooms; } Node * LRoomManager::rooms_get_room(int room_id) { const LRoom * pRoom = GetRoom(room_id); if (!pRoom) return NULL; return pRoom->GetGodotRoom(); } // move the initial hiding to where the camera is set, so we can save the scene etc void LRoomManager::ShowAll(bool bShow) { for (int n=0; nclear(); m_pPrev_VisibleRoomList->clear(); // } // show all for (int n=0; n(pCam); if (!pCamera) { WARN_PRINT("Not a camera"); return false; } m_ID_camera = pCam->get_instance_id(); // new .. select the cull layer // 1 is for showing objects outside the room system pCamera->set_cull_mask(1 | LRoom::LAYER_MASK_CAMERA); // use this temporarily to force debug // rooms_log_frame(); return true; } // convert empties and meshes to rooms and portals bool LRoomManager::RoomsConvert(bool bVerbose, bool bDeleteLights, bool bSingleRoomMode) { ResolveRoomListPath(); CHECK_ROOM_LIST LRoomConverter conv; conv.Convert(*this, bVerbose, false, bDeleteLights, bSingleRoomMode); return true; } bool LRoomManager::rooms_single_room_convert(bool bVerbose, bool bDeleteLights) { return RoomsConvert(bVerbose, bDeleteLights, true); } // convert empties and meshes to rooms and portals bool LRoomManager::rooms_convert(bool bVerbose, bool bDeleteLights) { return RoomsConvert(bVerbose, bDeleteLights, false); } bool LRoomManager::rooms_transfer_uv2s(Node * pMeshInstance_From, Node * pMeshInstance_To) { CheckRoomList(); MeshInstance * pMI_from = Object::cast_to(pMeshInstance_From); MeshInstance * pMI_to = Object::cast_to(pMeshInstance_To); if (!pMI_from) return false; if (!pMI_to) return false; LHelper helper; Lawn::LDebug::m_bRunning = false; bool res = helper.TransferUV2(*pMI_from, *pMI_to); Lawn::LDebug::m_bRunning = true; return res; } bool LRoomManager::rooms_unmerge_sobs(Node * pMergeMeshInstance) { MeshInstance * pMI = Object::cast_to(pMergeMeshInstance); LHelper helper; Lawn::LDebug::m_bRunning = false; bool res = helper.UnMergeSOBs(*this, pMI); Lawn::LDebug::m_bRunning = true; return res; } bool LRoomManager::rooms_save_scene(Node * pNode, String szFilename) { LSceneSaver saver; return saver.SaveScene(pNode, szFilename); } MeshInstance * LRoomManager::rooms_convert_lightmap_internal(String szProxyFilename, String szLevelFilename) { ResolveRoomListPath(); if (!CheckRoomList()) { WARN_PRINT_ONCE("rooms_convert_lightmap_internal : rooms not set"); return 0; } LRoomConverter conv; conv.Convert(*this, true, true, false); LHelper helper; Lawn::LDebug::m_bRunning = false; MeshInstance * pMI = helper.CreateLightmapProxy(*this); // if there is a save filename for the proxy, save if ((szProxyFilename != "") && pMI) { LSceneSaver saver; saver.SaveScene(pMI, szProxyFilename); } Lawn::LDebug::m_bRunning = true; // save the UV2 mapped level if (szLevelFilename != "") rooms_save_scene(LROOMLIST, szLevelFilename); return pMI; } bool LRoomManager::rooms_merge_sobs(Node * pMergeMeshInstance) { MeshInstance * pMI = Object::cast_to(pMergeMeshInstance); LHelper helper; Lawn::LDebug::m_bRunning = false; bool res = helper.MergeSOBs(*this, pMI); Lawn::LDebug::m_bRunning = true; return res; } // free memory for current set of rooms, prepare for converting a new game level void LRoomManager::rooms_release() { CheckRoomList(); // unhide all the objects and reattach to scene graph rooms_set_active(false); // unregister all the dobs for (int n=0; nclear(); // keep previous m_BF_master_SOBs_prev.CopyFrom(m_BF_master_SOBs); m_BF_master_SOBs.Blank(); // note this can be done more efficiently with swapping pointer m_MasterList_SOBs_prev.copy_from(m_MasterList_SOBs); m_VisibleList_SOBs.clear(); m_CasterList_SOBs.clear(); m_MasterList_SOBs.clear(); m_BF_caster_SOBs.Blank(); m_BF_visible_SOBs.Blank(); // lights m_BF_ActiveLights_prev.CopyFrom(m_BF_ActiveLights); m_ActiveLights_prev.copy_from(m_ActiveLights); m_ActiveLights.clear(); m_BF_ActiveLights.Blank(); m_BF_ProcessedLights.Blank(); // as we hit visible rooms we will mark them in a bitset, so we can hide any rooms // that are showing that haven't been hit this frame m_BF_visible_rooms.Blank(); // reset the planes pool for another frame m_Pool.Reset(); } bool LRoomManager::FrameUpdate() { if (Engine::get_singleton()->is_editor_hint()) { WARN_PRINT_ONCE("LRoomManager::FrameUpdate should not be called in editor"); return false; } DebugString_Set(""); // could turn off internal processing? not that important if (!m_bActive) return false; CHECK_ROOM_LIST if (m_bFrustumOnly) { // debugging emulate view frustum FrameUpdate_FrustumOnly(); return true; } // we keep a frame counter to prevent visiting things multiple times on the same frame in recursive functions m_uiFrameCounter++; LPRINT(5, "\nFRAME " + itos(m_uiFrameCounter)); FrameUpdate_Prepare(); // get the camera desired and make into lcamera Camera * pCamera = 0; if (m_ID_camera) { Object *pObj = ObjectDB::get_instance(m_ID_camera); pCamera = Object::cast_to(pObj); } else // camera not set .. do nothing return false; // camera not a camera?? shouldn't happen but we'll check if (!pCamera) return false; // Which room is the camera currently in? LRoom * pRoom = GetRoomFromDOB(pCamera); if (!pRoom) { WARN_PRINT_ONCE("LRoomManager::FrameUpdate : Camera is not in an LRoom"); return false; } #ifdef LDEBUG_CAMERA if (m_bDebugFrameString) DebugString_Add("Camera in room " + itos(pRoom->m_RoomID) + "\n"); #endif // lcamera contains the info needed for running the recursive trace using the main camera LSource cam; cam.Source_SetDefaults(); cam.m_ptPos = Vector3(0, 0, 0); cam.m_ptDir = Vector3 (-1, 0, 0); // get the camera desired and make into lcamera assert (pCamera); Transform tr = pCamera->get_global_transform(); cam.m_ptPos = tr.origin; cam.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want // if we can't prepare the frustum is invalid if (!m_MainCamera.Prepare(*this, pCamera)) return false; // the first set of planes are allocated and filled with the view frustum planes // Note that the visual server doesn't actually need to do view frustum culling as a result... // (but is still doing it for now) unsigned int pool_member = m_Pool.Request(); assert (pool_member != -1); LVector &planes = m_Pool.Get(pool_member); planes.clear(); // luckily godot already has a function to return a list of the camera clipping planes planes.copy_from(m_MainCamera.m_Planes); // the whole visibility algorithm is recursive, spreading out from the camera room, // rendering through any portals in view into other rooms, etc etc m_Trace.Trace_Prepare(*this, cam, m_BF_visible_SOBs, m_BF_visible_rooms, m_VisibleList_SOBs, *m_pCurr_VisibleRoomList); m_Trace.Trace_Begin(*pRoom, planes); // we no longer need these planes m_Pool.Free(pool_member); // finally hide all the rooms that are currently visible but not in the visible bitfield as having been hit FrameUpdate_FinalizeRooms(); FrameUpdate_AddShadowCasters(); FrameUpdate_CreateMasterList(); // set soft visibility of objects within visible rooms FrameUpdate_FinalizeVisibility_WithinRooms(); FrameUpdate_FinalizeVisibility_SoftShow(); // swap the current and previous visible room list LVector * pTemp = m_pCurr_VisibleRoomList; m_pCurr_VisibleRoomList = m_pPrev_VisibleRoomList; m_pPrev_VisibleRoomList = pTemp; // draw debug FrameUpdate_DrawDebug(cam, *pRoom); // when running, emit less debugging output so as not to choke the IDE Lawn::LDebug::m_bRunning = true; return true; } void LRoomManager::FrameUpdate_FinalizeRooms() { // finally hide all the rooms that are currently visible but not in the visible bitfield as having been hit // to get started if (!m_pPrev_VisibleRoomList->size()) { // NOTE this will be done more efficiently, but is okay to start with for (int n=0; nsize(); n++) { int r = (*m_pPrev_VisibleRoomList)[n]; if (!m_BF_visible_rooms.GetBit(r)) m_Rooms[r].Room_MakeVisible(false); } } } // to optimize just 1 call to the visual server, we want a list of all sobs that are either visible or casters // this allows 1 call to show / hide, and 1 call to layer flags void LRoomManager::FrameUpdate_CreateMasterList() { for (int n=0; nsize(); n++) { int r = (*m_pCurr_VisibleRoomList)[n]; m_Rooms[r].AddShadowCasters(*this); } #ifdef LDEBUG_LIGHTS if (m_bDebugFrameString) DebugString_Add("TOTAL shadow casters " + itos(m_CasterList_SOBs.size()) + "\n"); #endif LPRINT_RUN(2, "TOTAL shadow casters " + itos(m_CasterList_SOBs.size())); } void LRoomManager::FrameUpdate_FinalizeVisibility_SoftShow() { // apply the appropriate soft show for each sob in the render list int nSOBs = m_MasterList_SOBs.size(); for (int n=0; nshow(); // FIX GODOT BUG - light cull mask is not preserved when hiding and showing // set culling flag for light // 1 is for lighting objects outside the room system //pLight->set_shadow(false); //pLight->set_shadow(true); //pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); // Vector3 ptBugFix = pLight->get_translation(); // pLight->set_translation(ptBugFix); } } // debug DebugString_Light_AffectedRooms(lid); } for (int n=0; nhide(); // } } } } void LRoomManager::FrameUpdate_FinalizeVisibility_WithinRooms() { // and hide all the dobs that are in visible rooms that haven't been made visible // if (m_pCurr_VisibleRoomList->size() == 0) // print_line("WARNING : vis room list size is 0"); for (int n=0; nsize(); n++) { int r = (*m_pCurr_VisibleRoomList)[n]; m_Rooms[r].FinalizeVisibility(*this); } // NEW shows and hides dobs according to the difference between the current and previous master list for (int n=0; n(pObj); if (!im) return; im->clear(); im->begin(Mesh::PRIMITIVE_TRIANGLES, NULL); int nVerts = m_DebugPortalLightPlanes.size(); for (int n=0; nadd_vertex(m_DebugPortalLightPlanes[n]); } im->end(); } if (m_bDebugPlanes) { Vector3 ptCam = cam.m_ptPos; // slight adjustment to prevent parallel lines in viewport ptCam += (cam.m_ptDir * 0.1f); Object * pObj = ObjectDB::get_instance(m_ID_DebugPlanes); ImmediateGeometry * im = Object::cast_to(pObj); if (!im) return; im->clear(); im->begin(Mesh::PRIMITIVE_LINES, NULL); int nVerts = m_DebugPlanes.size(); for (int n=0; nadd_vertex(ptCam); im->add_vertex(m_DebugPlanes[n]); } im->end(); } if (m_bDebugLightVolumes) { Object * pObj = ObjectDB::get_instance(m_ID_DebugLightVolumes); ImmediateGeometry * im = Object::cast_to(pObj); if (!im) return; im->clear(); im->begin(Mesh::PRIMITIVE_LINES, NULL); int nVerts = m_DebugLightVolumes.size(); for (int n=0; nadd_vertex(m_DebugLightVolumes[n]); } im->end(); } if (m_bDebugFrustums) { Object * pObj = ObjectDB::get_instance(m_ID_DebugFrustums); ImmediateGeometry * im = Object::cast_to(pObj); if (!im) return; im->clear(); im->begin(Mesh::PRIMITIVE_LINES, NULL); int nVerts = m_DebugFrustums.size(); for (int n=0; nadd_vertex(m_DebugFrustums[n]); } im->end(); } // if debug bounds are on and there is a bound for this room const Geometry::MeshData &md = lroom.m_Bound_MeshData; if (m_bDebugBounds && md.faces.size()) { Object * pObj = ObjectDB::get_instance(m_ID_DebugBounds); ImmediateGeometry * im = Object::cast_to(pObj); if (!im) return; im->clear(); im->begin(Mesh::PRIMITIVE_TRIANGLES, NULL); for (int n=0; nset_normal(f.plane.normal); im->add_vertex(md.vertices[f.indices[0]]); im->add_vertex(md.vertices[f.indices[t+1]]); im->add_vertex(md.vertices[f.indices[t+2]]); } } im->end(); } } void LRoomManager::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { // turn on process, unless we are in the editor if (!Engine::get_singleton()->is_editor_hint()) { set_process_internal(true); //CreateDebug(); } else set_process_internal(false); } break; case NOTIFICATION_INTERNAL_PROCESS: { FrameUpdate(); } break; } } void LRoomManager::_bind_methods() { // main functions ClassDB::bind_method(D_METHOD("rooms_convert", "verbose", "delete lights"), &LRoomManager::rooms_convert); ClassDB::bind_method(D_METHOD("rooms_single_room_convert", "verbose", "delete lights"), &LRoomManager::rooms_single_room_convert); ClassDB::bind_method(D_METHOD("rooms_release"), &LRoomManager::rooms_release); ClassDB::bind_method(D_METHOD("rooms_set_camera", "camera"), &LRoomManager::rooms_set_camera); ClassDB::bind_method(D_METHOD("rooms_save_scene", "node", "filename"), &LRoomManager::rooms_save_scene); // stuff for external workflow .. works but don't expose yet // ClassDB::bind_method(D_METHOD("rooms_merge_sobs"), &LRoomManager::rooms_merge_sobs); // ClassDB::bind_method(D_METHOD("rooms_unmerge_sobs"), &LRoomManager::rooms_unmerge_sobs); // ClassDB::bind_method(D_METHOD("rooms_transfer_uv2s"), &LRoomManager::rooms_transfer_uv2s); // debugging ClassDB::bind_method(D_METHOD("rooms_set_logging", "level 0 to 6"), &LRoomManager::rooms_set_logging); ClassDB::bind_method(D_METHOD("rooms_log_frame"), &LRoomManager::rooms_log_frame); ClassDB::bind_method(D_METHOD("rooms_set_active", "active"), &LRoomManager::rooms_set_active); ClassDB::bind_method(D_METHOD("rooms_set_debug_planes", "active"), &LRoomManager::rooms_set_debug_planes); ClassDB::bind_method(D_METHOD("rooms_set_debug_bounds", "active"), &LRoomManager::rooms_set_debug_bounds); ClassDB::bind_method(D_METHOD("rooms_set_debug_lights", "active"), &LRoomManager::rooms_set_debug_lights); ClassDB::bind_method(D_METHOD("rooms_set_debug_shadows", "active"), &LRoomManager::rooms_set_debug_shadows); ClassDB::bind_method(D_METHOD("rooms_set_debug_frustums", "active"), &LRoomManager::rooms_set_debug_frustums); ClassDB::bind_method(D_METHOD("rooms_set_debug_frame_string", "active"), &LRoomManager::rooms_set_debug_frame_string); ClassDB::bind_method(D_METHOD("rooms_get_debug_frame_string"), &LRoomManager::rooms_get_debug_frame_string); // lightmapping ClassDB::bind_method(D_METHOD("rooms_convert_lightmap_internal", "proxy filename", "level filename"), &LRoomManager::rooms_convert_lightmap_internal); // functions to add dynamic objects to the culling system // Note that these should not be placed directly in rooms, the system will 'soft link' to them // so they can be held, e.g. in pools elsewhere in the scene graph ClassDB::bind_method(D_METHOD("dob_register", "dob", "radius"), &LRoomManager::dob_register); ClassDB::bind_method(D_METHOD("dob_unregister", "dob"), &LRoomManager::dob_unregister); ClassDB::bind_method(D_METHOD("dob_update", "dob"), &LRoomManager::dob_update); ClassDB::bind_method(D_METHOD("dob_teleport", "dob"), &LRoomManager::dob_teleport); ClassDB::bind_method(D_METHOD("dob_register_hint", "dob", "radius", "room"), &LRoomManager::dob_register_hint); ClassDB::bind_method(D_METHOD("dob_teleport_hint", "dob", "room"), &LRoomManager::dob_teleport_hint); ClassDB::bind_method(D_METHOD("dob_get_room_id", "dob"), &LRoomManager::dob_get_room_id); ClassDB::bind_method(D_METHOD("light_register", "light", "area"), &LRoomManager::light_register); ClassDB::bind_method(D_METHOD("dynamic_light_register", "light", "radius"), &LRoomManager::dynamic_light_register); ClassDB::bind_method(D_METHOD("dynamic_light_register_hint", "light", "radius", "room"), &LRoomManager::dynamic_light_register_hint); ClassDB::bind_method(D_METHOD("dynamic_light_unregister", "light"), &LRoomManager::dynamic_light_unregister); ClassDB::bind_method(D_METHOD("dynamic_light_update", "light"), &LRoomManager::dynamic_light_update); // helper ClassDB::bind_method(D_METHOD("rooms_get_room", "room id"), &LRoomManager::rooms_get_room); ClassDB::bind_method(D_METHOD("rooms_get_num_rooms"), &LRoomManager::rooms_get_num_rooms); ClassDB::bind_method(D_METHOD("rooms_is_room_visible", "room id"), &LRoomManager::rooms_is_room_visible); ClassDB::bind_method(D_METHOD("rooms_get_visible_rooms"), &LRoomManager::rooms_get_visible_rooms); ClassDB::bind_method(D_METHOD("set_rooms", "rooms"), &LRoomManager::set_rooms); ClassDB::bind_method(D_METHOD("set_rooms_path", "rooms"), &LRoomManager::set_rooms_path); ClassDB::bind_method(D_METHOD("get_rooms_path"), &LRoomManager::get_rooms_path); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "rooms"), "set_rooms_path", "get_rooms_path"); } ////////////////////////////// void LRoomManager::_set_rooms(Object *p_rooms) { if (p_rooms) { LPRINT(2, "\t_set_rooms"); } else { LPRINT(2, "\t_set_rooms NULL"); } m_ID_RoomList = 0; m_pRoomList = 0; if (p_rooms) { // check is spatial Spatial *pSpatial = Object::cast_to(p_rooms); if (pSpatial) { m_ID_RoomList = pSpatial->get_instance_id(); m_pRoomList = pSpatial; LPRINT(2, "\t\tRoomlist was Godot ID " + itos(m_ID_RoomList)); } else { // should never get here? WARN_PRINT("_set_rooms must be a Spatial"); } } } void LRoomManager::set_rooms(const Object *p_rooms) { // handle null if (p_rooms == NULL) { WARN_PRINT("SCRIPT set_rooms NULL"); remove_rooms_path(); return; } LPRINT(2, "SCRIPT set_rooms"); // is it a spatial? const Spatial * pSpatial = Object::cast_to(p_rooms); if (!pSpatial) { WARN_PRINT("set_rooms must be a spatial."); remove_rooms_path(); return; } NodePath path = get_path_to(pSpatial); set_rooms_path(path); } void LRoomManager::set_rooms_path(const NodePath &p_path) { LPRINT(2, "set_rooms_path " + p_path); m_path_RoomList = p_path; ResolveRoomListPath(); } NodePath LRoomManager::get_rooms_path() const { return m_path_RoomList; } void LRoomManager::remove_rooms_path() { NodePath pathnull; set_rooms_path(pathnull); } Spatial * LRoomManager::GetRoomList_Checked() { if (m_ID_RoomList == 0) return 0; m_pRoomList = (Spatial *) ObjectDB::get_instance(m_ID_RoomList); if (!m_pRoomList) m_ID_RoomList = 0; // the node is no longer valid return m_pRoomList; } void LRoomManager::ResolveRoomListPath() { LPRINT(2, "ResolveRoomListPath " + m_path_RoomList); if (has_node(m_path_RoomList)) { LPRINT(2, "has_node"); Spatial * pNode = Object::cast_to(get_node(m_path_RoomList)); if (pNode) { _set_rooms(pNode); return; } else { WARN_PRINT("RoomList must be a Spatial"); } } // default to setting to null _set_rooms(NULL); } ///////////////////////////////////////////////////////////////////////////////// // rooms_set_debug commands .. done as macros to make easier to change // macros to combine to single identifier //#define SCU_IDENT(x) x #define COMB_NX(A, B) A##B #define COMB_IDENT(A, B) COMB_NX(A,B) #define IMPLEMENT_DEBUG_MESH(a, b) void LRoomManager::COMB_IDENT(rooms_set_debug_, a)(bool bActive)\ {\ COMB_IDENT(m_bDebug, b) = bActive;\ Object * pObj = ObjectDB::get_instance(COMB_IDENT(m_ID_Debug, b)); \ ImmediateGeometry * im = Object::cast_to(pObj); \ if (!im) \ return; \ if (bActive) \ im->show(); \ else \ im->hide(); \ } IMPLEMENT_DEBUG_MESH(shadows,LightVolumes) IMPLEMENT_DEBUG_MESH(frustums,Frustums) IMPLEMENT_DEBUG_MESH(lights,Lights) IMPLEMENT_DEBUG_MESH(bounds,Bounds) IMPLEMENT_DEBUG_MESH(planes,Planes) #undef COMB_NX #undef COMB_IDENT #undef IMPLEMENT_DEBUG_MESH void LRoomManager::rooms_set_debug_frame_string(bool bActive) { m_bDebugFrameString = bActive; }