// 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" //#define LROOM_VERBOSE void LRoom::print(String sz) { #ifdef LROOM_VERBOSE LPortal::print(sz); #endif } 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_cameraID; 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) { #ifdef LROOM_VERBOSE print("DOB at pos " + pt + " ahead of portal " + port.get_name() + " by " + String(Variant(dist))); #endif // 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(); } } 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) { #ifdef LROOM_VERBOSE print("\t\t\tDEPTH LIMIT REACHED"); #endif return; } #ifdef LROOM_VERBOSE print("DetermineVisibility_Recursive from " + get_name()); #endif // 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; } #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; //print_line("\t\t\tculling dob " + pObj->get_name()); float radius = dob.m_fRadius; for (int p=0; p radius) { bShow = false; break; } } if (bShow) dob.m_bVisible = true; } } // look through portals for (int p=0; pm_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; #ifdef LROOM_VERBOSE print("\ttesting portal " + port.get_name() + " normal " + portal_normal); #endif // 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); // float dot = cam.m_ptDir.dot(portal_normal); if (dot <= -0.0f) // 0.0 { #ifdef LROOM_VERBOSE print("\t\tportal culled (wrong direction) dot is " + String(Variant(dot)) + ", dir_portal is " + dir_portal); #endif 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(cam.m_ptPos, new_planes); if (pLinkedRoom) pLinkedRoom->DetermineVisibility_Recursive(manager, depth + 1, cam, new_planes, port_id); // 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"); } } }