// 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; } 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) { // LDob dob; // dob.m_ID = pDOB->get_instance_id(); 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; } // 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) { //print_line("FinalizeVisibility room " + get_name() + " NumSOBs " + itos(m_SOBs.size()) + ", NumDOBs " + itos(m_DOBs.size())); for (int n=0; nshow(); else pS->hide(); } } for (int n=0; nget_name()); pS->show(); } else pS->hide(); } } } // 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::Show_All() { GetGodotRoom()->show(); for (int n=0; nshow(); } for (int n=0; nshow(); } } void LRoom::FirstTouch(LRoomManager &manager) { // set the frame counter m_uiFrameTouched = manager.m_uiFrameCounter; // keep track of which rooms are shown this frame manager.m_pCurr_VisibleRoomList->push_back(m_RoomID); // hide all objects for (int n=0; n &planes, int portalID_from) { // 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); // show this room and add to visible list of rooms GetGodotRoom()->show(); manager.m_BF_visible_rooms.SetBit(m_RoomID, true); #define LPORTAL_CULL_STATIC #ifdef LPORTAL_CULL_STATIC // clip all objects in this room to the clipping planes for (int n=0; nget_name()); for (int p=0; p 0.0f) { bShow = false; break; } } if (bShow) sob.m_bVisible = true; } // for through sobs #else // clip all objects in this room to the clipping planes for (int n=0; n(pNode); // should always be a visual instance, only these are added as SOBs if (pObj) { //Vector3 pt = pObj->get_global_transform().origin; bool bShow = true; // estimate the radius .. for now AABB bb = pObj->get_transformed_aabb(); print("\t\t\tculling object " + pObj->get_name()); for (int p=0; p 0.0f) { bShow = false; break; } } if (bShow) sob.m_bVisible = true; // pObj->show(); // else // pObj->hide(); } } #endif // 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_numm_uiFrameTouched == manager.m_uiFrameCounter) // continue; // cull by portal angle to camera. // Note we need to deal with 'side on' portals, and the camera has a spreading view, so we cannot simply dot // the portal normal with camera direction, we need to take into account angle to the portal itself. const Vector3 &portal_normal = port.m_Plane.normal; LPRINT_RUN(2, "\tPORTAL " + itos (port_num) + " (" + itos(port_id) + ") " + port.get_name() + " normal " + portal_normal); // we will dot the portal angle with a ray from the camera to the portal centre // (there might be an even better ray direction but this will do for now) Vector3 dir_portal = port.m_ptCentre - cam.m_ptPos; // doesn't actually need to be normalized? float dot = dir_portal.dot(portal_normal); if (dot <= -0.0f) // 0.0 { //LPRINT_RUN(2, "\t\tCULLED (wrong direction) dot is " + String(Variant(dot)) + ", dir_portal is " + dir_portal); LPRINT_RUN(2, "\t\tCULLED (wrong direction)"); continue; } // is it culled by the planes? LPortal::eClipResult overall_res = LPortal::eClipResult::CLIP_INSIDE; // 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 for (int l=1; l &new_planes = manager.m_Pool.Get(uiPoolMem); // copy the existing planes new_planes.copy_from(planes); // add the planes for the portal port.AddPlanes(manager, cam.m_ptPos, new_planes); if (pLinkedRoom) { pLinkedRoom->DetermineVisibility_Recursive(manager, depth + 1, cam, new_planes, port_id); // 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 }