// 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.h" #include "core/engine.h" #include "scene/3d/mesh_instance.h" #include "lportal.h" #include "lbitfield_dynamic.h" #include "lroom_manager.h" #include "ldebug.h" LRoom::LRoom() { m_RoomID = -1; m_uiFrameTouched = 0; m_iFirstPortal = 0; m_iNumPortals = 0; m_bVisible = true; m_iFirstSOB = 0; m_iNumSOBs = 0; m_iFirstShadowCaster_SOB = 0; m_iNumShadowCasters_SOB = 0; } Spatial * LRoom::GetGodotRoom() const { Object *pObj = ObjectDB::get_instance(m_GodotID); // assuming is a portal Spatial * pSpat = Object::cast_to(pObj); return pSpat; } void LRoom::DOB_Add(const LDob &dob) { m_DOBs.push_back(dob); } unsigned int LRoom::DOB_Find(Node * pDOB) const { ObjectID id = pDOB->get_instance_id(); for (int n=0; nget_global_transform().origin; // is it the camera? bool bCamera = pDOB->get_instance_id() == manager.m_ID_camera; float slop = 0.2f; if (bCamera) slop = 0.0f; // the camera can't have slop because we might end up front side of a door without entering the room, // hence can't see into the room through the portal! // if (bCamera) // slop = 0.0f; // check each portal - has the object crossed it into the neighbouring room? for (int p=0; p slop) { LPRINT(0, "DOB at pos " + pt + " ahead of portal " + port.get_name() + " by " + String(Variant(dist))); // we want to move into the adjoining room return &manager.Portal_GetLinkedRoom(port); } } return 0; } // instead of directly showing and hiding objects we now set their layer, // and the camera will hide them with a cull mask. This is so that // objects can still be rendered outside immediate view for casting shadows. // All objects in view (that are set to cast shadows) should cast shadows, so the actual // shown objects are a superset of the softshown. void LRoom::SoftShow(VisualInstance * pVI, uint32_t show_flags) { // hijack this layer number uint32_t mask = pVI->get_layer_mask(); uint32_t orig_mask = mask; // debug, to check shadow casters are correct for different light types //#define DEBUG_SHOW_CASTERS_ONLY #ifdef DEBUG_SHOW_CASTERS_ONLY bShow = true; if (bShow) { } #else if (show_flags & LAYER_MASK_CAMERA) mask |= LAYER_MASK_CAMERA; // set else mask &= ~LAYER_MASK_CAMERA; // clear if (show_flags & LAYER_MASK_LIGHT) mask |= LAYER_MASK_LIGHT; else mask &= ~LAYER_MASK_LIGHT; // if (bShow) // { // // set // mask |= SOFT_SHOW_MASK; // // clear // mask &= ~(1 | SOFT_HIDE_MASK); // } // else // { // // set // mask |= SOFT_HIDE_MASK; // // clear // mask &= ~(1 | SOFT_SHOW_MASK); // } #endif // noop? don't touch the visual server if no change to mask if (mask == orig_mask) return; pVI->set_layer_mask(mask); // test godot bug // GeometryInstance * pGI = Object::cast_to(pVI); // if (pGI) // { // // godot visible bug workaround // pGI->set_extra_cull_margin(0.0f); // } // test the visual server - NOT A BOTTLENECK. set_layer_mask is cheap } // naive version, adds all the non visible objects in visible rooms as shadow casters void LRoom::AddShadowCasters(LRoomManager &manager) { LPRINT_RUN(2, "ADDSHADOWCASTERS room " + get_name() + ", " + itos(m_iNumShadowCasters_SOB) + " shadow casters"); // add all the active lights in this room for (int n=0; nget_name()); manager.m_BF_caster_SOBs.SetBit(sobID, true); manager.m_CasterList_SOBs.push_back(sobID); } else { //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); } } } } // new!! use precalced list of shadow casters // int last = m_iFirstShadowCaster_SOB + m_iNumShadowCasters_SOB; // for (int n=m_iFirstShadowCaster_SOB; nget_name()); // manager.m_BF_caster_SOBs.SetBit(sobID, true); // manager.m_CasterList_SOBs.push_back(sobID); // } // else // { // //LPRINT(2, "\t" + itos(sobID) + ", ALREADY CASTER " + manager.m_SOBs[sobID].GetSpatial()->get_name()); // } // } } // hide all the objects not hit on this frame .. instead of calling godot hide without need // (it might be expensive) void LRoom::FinalizeVisibility(LRoomManager &manager) { // make sure all lights needed are turned on // int last_sob = m_iFirstSOB + m_iNumSOBs; // for (int n=m_iFirstSOB; nshow(); // else // pS->hide(); // } //print_line("FinalizeVisibility room " + get_name() + " NumSOBs " + itos(m_SOBs.size()) + ", NumDOBs " + itos(m_DOBs.size())); for (int n=0; nget_name()); // pS->show(); // } // else // pS->hide(); } } } // call when releasing a level, this should unregister all dobs within all rooms void LRoom::Release(LRoomManager &manager) { for (int n=0; nshow(); // show all dobs for (int n=0; nshow(); } } else { // hide room // GetGodotRoom()->hide(); // hide all dobs for (int n=0; nhide(); } } } // hide godot room and all linked dobs //void LRoom::Hide_All() //{ // GetGodotRoom()->hide(); // for (int n=0; nhide(); // } //} // show godot room and all linked dobs and all sobs void LRoom::Debug_ShowAll(bool bActive) { Room_MakeVisible(true); // NYI .. change layers to all be visible // for (int n=0; nshow(); // VisualInstance * pVI = sob.GetVI(); // if (pVI) // { // SoftShow(pVI, LRoom::LAYER_MASK_CAMERA | LRoom::LAYER_MASK_LIGHT); // } // } } void LRoom::FirstTouch(LRoomManager &manager) { // set the frame counter m_uiFrameTouched = manager.m_uiFrameCounter; // show this room and add to visible list of rooms Room_MakeVisible(true); manager.m_BF_visible_rooms.SetBit(m_RoomID, true); // keep track of which rooms are shown this frame manager.m_pCurr_VisibleRoomList->push_back(m_RoomID); // hide all objects // int last_sob = m_iFirstSOB + m_iNumSOBs; // for (int n=m_iFirstSOB; n &planes, int first_portal_plane) { // prevent too much depth if (depth > 8) { LPRINT_RUN(2, "\t\t\tDEPTH LIMIT REACHED"); WARN_PRINT_ONCE("LPortal Depth Limit reached (seeing through > 8 portals)"); return; } // for debugging Lawn::LDebug::m_iTabDepth = depth; LPRINT_RUN(2, ""); LPRINT_RUN(2, "ROOM '" + get_name() + "' planes " + itos(planes.size()) + " portals " + itos(m_iNumPortals) ); // only handle one touch per frame so far (one portal into room) //assert (manager.m_uiFrameCounter > m_uiFrameTouched); // first touch if (m_uiFrameTouched < manager.m_uiFrameCounter) FirstTouch(manager); // clip all objects in this room to the clipping planes int last_sob = m_iFirstSOB + m_iNumSOBs; for (int n=m_iFirstSOB; nget_name()); // already determined to be visible through another portal if (manager.m_BF_visible_SOBs.GetBit(n)) { //LPRINT_RUN(2, "\talready visible"); continue; } bool bShow = true; // estimate the radius .. for now const AABB &bb = sob.m_aabb; // print("\t\t\tculling object " + pObj->get_name()); for (int p=0; p 0.0f) { bShow = false; break; } } if (bShow) { // sob is renderable and visible (not shadow only) manager.m_BF_visible_SOBs.SetBit(n, true); //manager.m_BF_render_SOBs.SetBit(n, true); manager.m_VisibleList_SOBs.push_back(n); } } // for through sobs // cull DOBs for (int n=0; nget_global_transform().origin; float radius = dob.m_fRadius; for (int p=0; p radius) { bShow = false; break; } } if (bShow) { LPRINT_RUN(1, "\tDOB " + pObj->get_name() + " visible"); dob.m_bVisible = true; } else { LPRINT_RUN(1, "\tDOB " + pObj->get_name() + " culled"); } } } // for through dobs // look through portals for (int port_num=0; port_num partial_planes; partial_planes.clear(); // for portals, we want to ignore the near clipping plane, as we might be right on the edge of a doorway // and still want to look through the portal. // So we are starting this loop from 1, ASSUMING that plane zero is the near clipping plane. // If it isn't we would need a different strategy // Note that now this only occurs for the first portal out of the current room. After that, // 0 is passed as first_portal_plane, because the near plane will probably be irrelevant, // and we are now not necessarily copying the camera planes. for (int l=first_portal_plane; l &new_planes = manager.m_Pool.Get(uiPoolMem); new_planes.clear(); // NEW!! if portal is totally inside the planes, don't copy the old planes if (overall_res != LPortal::eClipResult::CLIP_INSIDE) { // copy the existing planes //new_planes.copy_from(planes); // new .. only copy the partial planes that the portal cuts through for (int n=0; nDetermineVisibility_Recursive(manager, depth + 1, cam, new_planes, 0); // for debugging need to reset tab depth Lawn::LDebug::m_iTabDepth = depth; } // we no longer need these planes manager.m_Pool.Free(uiPoolMem); } else { // planes pool is empty! // This will happen if the view goes through shedloads of portals // The solution is either to increase the plane pool size, or build levels // with views through multiple portals. Looking through multiple portals is likely to be // slow anyway because of the number of planes to test. WARN_PRINT_ONCE("Planes pool is empty"); } } // for p through portals }