diff --git a/larea.cpp b/larea.cpp new file mode 100644 index 0000000..7445d11 --- /dev/null +++ b/larea.cpp @@ -0,0 +1,32 @@ +// 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 "larea.h" + +void LArea::Create(String szName) +{ + m_iFirstRoom = 0; + m_iNumRooms = 0; + + m_iFirstLight = 0; + m_iNumLights = 0; + + m_szName = szName; +} diff --git a/larea.h b/larea.h new file mode 100644 index 0000000..079d0b3 --- /dev/null +++ b/larea.h @@ -0,0 +1,38 @@ +#pragma once + +// 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 "core/ustring.h" + +class LArea +{ +public: + void Create(String szName); + + String m_szName; + + int m_iFirstRoom; + int m_iNumRooms; + + // each area contains a list of global lights that affect this area + int m_iFirstLight; + int m_iNumLights; +}; diff --git a/ldebug.cpp b/ldebug.cpp index be2cd68..faa10af 100644 --- a/ldebug.cpp +++ b/ldebug.cpp @@ -1,3 +1,23 @@ +// 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 "ldebug.h" namespace Lawn diff --git a/ldebug.h b/ldebug.h index 4a7fdf9..23b733d 100644 --- a/ldebug.h +++ b/ldebug.h @@ -1,12 +1,36 @@ #pragma once +// 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. + // most of the performance sensitive debug output will be compiled out in release builds // you won't be able to get frame debugging of the visibility tree though. #ifdef DEBUG_ENABLED + #define LPRINT_RUN(a, b) {String sz;\ for (int n=0; n= MAX_AFFECTED_ROOMS) + return false; + m_AffectedRooms[m_NumAffectedRooms++] = room_id; + return true; +} + + + +// dynamic light update +void LLight::Update() +{ + // firstly, has it crossed into another room like the dobs +} + +String LLight::MakeDebugString() const +{ + String sz; + sz += m_Source.MakeDebugString(); + return sz; } diff --git a/ldob.h b/ldob.h index a2cd876..c83289e 100644 --- a/ldob.h +++ b/ldob.h @@ -73,30 +73,83 @@ public: }; -class LLight : public LHidable +//class LCamera +// trace source can be camera or light +class LSource { public: - enum eLightType + enum eSourceType { - LT_DIRECTIONAL, - LT_SPOTLIGHT, - LT_OMNI, + ST_CAMERA, // frustum planes will have been added + ST_DIRECTIONAL, + ST_SPOTLIGHT, // trace should add back plane and cone planes + ST_OMNI, // no planes, can go in any direction }; - void SetDefaults(); - Light * GetGodotLight(); + + enum eSourceClass + { + SC_STATIC, // non moving light + SC_ROOM, // only moves within the room + SC_DYNAMIC, // can move between rooms (register as a DOB) + }; + + + void Source_SetDefaults(); + String MakeDebugString() const; + + // funcs bool IsGlobal() const {return m_RoomID == -1;} - Vector3 m_ptDir; + // all in world space, culling done in world space Vector3 m_ptPos; - ObjectID m_GodotID; - eLightType m_eType; + Vector3 m_ptDir; + eSourceType m_eType; + eSourceClass m_eClass; + float m_fSpread; // for spotlight - float m_fMaxDist; // shadow distance not light distance + float m_fRange; // shadow distance not light distance // source room int m_RoomID; // or -1 for global lights +private: + static const char * m_szTypes[]; + static const char * m_szClasses[]; +}; + + + +class LLight : public LHidable +{ +public: + enum {MAX_AFFECTED_ROOMS=64}; + + LSource m_Source; + ObjectID m_GodotID; // shadow casters int m_FirstCaster; int m_NumCasters; + + // funcs + + // for dynamic lights + // move them light dobs across planes + // and update the rooms that are affected by the light + void Update(); + String MakeDebugString() const; + + void Light_SetDefaults(); + Light * GetGodotLight(); + + + bool AddAffectedRoom(int room_id); + void ClearAffectedRooms() {m_NumAffectedRooms = 0;} + + // keep a list of the rooms affected by this light + uint16_t m_AffectedRooms[MAX_AFFECTED_ROOMS]; + int m_NumAffectedRooms; + + // for global lights, this is the area or -1 if unset + int m_iArea; + String m_szArea; // set to the area string in the case of area lights, else "" }; diff --git a/lmain_camera.cpp b/lmain_camera.cpp new file mode 100644 index 0000000..8fafe83 --- /dev/null +++ b/lmain_camera.cpp @@ -0,0 +1,1003 @@ +// 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 "lmain_camera.h" +#include "lvector.h" +#include "lroom.h" +#include "ldebug.h" +#include "lroom_manager.h" +#include "ldob.h" + +#include "scene/3d/camera.h" +#include "core/math/camera_matrix.h" +#include "core/math/plane.h" +#include "core/math/vector3.h" + +const char * LMainCamera::m_szPlanes[] = {"NEAR", "FAR", "LEFT", "TOP", "RIGHT", "BOTTOM", }; +const char * LMainCamera::m_szPoints[] = {"FAR_LEFT_TOP", "FAR_LEFT_BOTTOM", "FAR_RIGHT_TOP", "FAR_RIGHT_BOTTOM", +"NEAR_LEFT_TOP", "NEAR_LEFT_BOTTOM", "NEAR_RIGHT_TOP", "NEAR_RIGHT_BOTTOM",}; + + +uint8_t LMainCamera::m_LUT_EntrySizes[64] = {0, 4, 4, 0, 4, 6, 6, 8, 4, 6, 6, 8, 6, 6, 6, 6, 4, 6, 6, 8, 0, 8, 8, 0, 6, 6, 6, 6, 8, 6, 6, 4, 4, 6, 6, 8, 6, 6, 6, 6, 0, 8, 8, 0, 8, 6, 6, 4, 6, 6, 6, 6, 8, 6, 6, 4, 8, 6, 6, 4, 0, 4, 4, 0, }; + + +#ifdef LMAINCAMERA_CALC_LUT + + +void LMainCamera::CreateLUT() +{ + // each pair of planes that are opposite can have an edge + for (int p0 = 0; p0 &entry = m_LUT[n]; + + sz += itos (entry.size()) + ", "; + } + sz += "}"; + print_line(sz); + + for (int n=0; n &entry = m_LUT[n]; + + String sz = "{"; + + // first is the number of points in the entry + int s = entry.size(); + //assert (s <= 7); + +// sz += itos(s) + ", "; + + // in the very special (and rare case of 8 points, we can infer the 8th point at runtime) + for (int p=0; p<7; p++) + { + if (p < s) + sz += itos(entry[p]); + else + sz += "0"; // just a spacer + + sz += ", "; + } + + sz += "},"; + print_line(sz); + } + + print_line("\nLIGHT VOLUME TABLE END\n"); +} + + +void LMainCamera::DebugPrintLUT() +{ + for (int n=0; n &entry = m_LUT[n]; + + //sz += DebugStringLUT_Entry(entry); + sz = "\t" + String_LUTEntry(entry); + +// for (int i=0; i &entry) +{ + String sz; + + for (int n=0; n &entry) +{ + String sz; +// sz = "LUT" + itos(n) + ":\t"; + +// const LVector &entry = m_LUT[n]; + + for (int i=0; i &entry = m_LUT[n]; + + const ePoint &a = pts[0]; + const ePoint &b = pts[1]; + + entry.push_back(pts[0]); + entry.push_back(pts[1]); +} + +void LMainCamera::CompactLUT_Entry(int n) +{ + assert (n < LUT_SIZE); + LVector &entry = m_LUT[n]; + + int nPairs = entry.size() / 2; + + if (nPairs == 0) + return; + + LVector temp; + temp.reserve(16); // 8 max? + + String sz; + sz = "Compact LUT" + itos(n) + ":\t"; + sz += DebugStringLUT_Entry(entry); + print_line(sz); + + // add first pair + temp.push_back(entry[0]); + temp.push_back(entry[1]); + unsigned int BFpairs = 1; + + sz = DebugStringLUT_Entry(temp) + " -> "; + print_line(sz); + + // attempt to add a pair each time + for (int done=1; done &planes) const +{ + uint32_t lookup = 0; + + // directional light, we will use dot against the light direction to determine back facing planes + for (int n = 0; n < 6; n++) + { + float dot = m_Planes[n].normal.dot(lsource.m_ptDir); + if (dot > 0.0f) + { + lookup |= 1 << n; + + // add the frustum back plane to the clipping planes + planes.push_back(m_Planes[n]); + } + } + + ERR_FAIL_COND_V(lookup >= LUT_SIZE, true); + + // deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away) + // then we will add the camera frustum planes to clip the light volume .. there is no need to + // render shadow casters outside the frustum as shadows can never re-enter the frustum. + + if (lookup == 63) // should never happen with directional light?? this may be able to be removed + { + /* + num_cull_planes = 0; + for (int n = 0; n < frustum_planes.size(); n++) { + //planes.push_back(frustum_planes[n]); + add_cull_plane(frustum_planes[n]); + } + */ + + return true; + } + + // each edge forms a plane +#ifdef LMAINCAMERA_CALC_LUT + const LVector &entry = m_LUT[lookup]; + + // each edge forms a plane + int nEdges = entry.size()-1; +#else + uint8_t * entry = &m_LUT_Entries[lookup][0]; + int nEdges = m_LUT_EntrySizes[lookup] - 1; +#endif + + + for (int e = 0; e < nEdges; e++) { + int i0 = entry[e]; + int i1 = entry[e + 1]; + const Vector3 &pt0 = m_Points[i0]; + const Vector3 &pt1 = m_Points[i1]; + + // create a third point from the light direction + Vector3 pt2 = pt0 - lsource.m_ptDir; + + // create plane from 3 points + Plane p(pt0, pt1, pt2); + planes.push_back(p); + } + + // last to 0 edge + if (nEdges) { + int i0 = entry[nEdges]; // last + int i1 = entry[0]; // first + + const Vector3 &pt0 = m_Points[i0]; + const Vector3 &pt1 = m_Points[i1]; + + // create a third point from the light direction + Vector3 pt2 = pt0 - lsource.m_ptDir; + + // create plane from 3 points + Plane p(pt0, pt1, pt2); + planes.push_back(p); + } + +#ifdef LIGHT_CULLER_DEBUG_LOGGING + if (is_logging()) { + print_line("lcam.pos is " + String(lsource.pos)); + } +#endif + + return true; +} + +// returns false if the light is completely culled (does not enter the camera frustum) +bool LMainCamera::AddCameraLightPlanes(LRoomManager &manager, const LSource &lsource, LVector &planes) const +{ + // doesn't account for directional lights yet! only points + + // find which of the camera planes are facing away from the light + assert (m_Planes.size() == 6); + + // doesn't account for directional lights yet! only points + switch (lsource.m_eType) + { + case LSource::ST_SPOTLIGHT: + case LSource::ST_OMNI: + break; + case LSource::ST_DIRECTIONAL: + return AddCameraLightPlanes_Directional(manager, lsource, planes); + break; + default: + return false; // not yet supported + break; + } + + uint32_t lookup = 0; + + // record the original number of planes in case we need to revert + int nPlanesBefore = planes.size(); + + // DIRECTIONAL LIGHT +// for (int n=0; n<6; n++) +// { +// float dot = m_Planes[n].normal.dot(lcam.m_ptDir); +// if (dot > 0.0f) +// { +// lookup |= 1 << n; + +// LPRINT_RUN(2, m_szPlanes[n] + " facing away from light dot " + String(Variant(dot))); +// } +// } + + // POINT LIGHT (spotlight, omni) + // BRAINWAVE!! Instead of using dot product to compare light direction to plane, we can simply + // find out which side of the plane the camera is on!! By definition this marks the point at which the plane + // becomes invisible. This works for portals too! + + // OMNI + if (lsource.m_eType == LSource::ST_OMNI) + { + for (int n=0; n<6; n++) + { + float dist = m_Planes[n].distance_to(lsource.m_ptPos); + if (dist < 0.0f) + { + lookup |= 1 << n; + + LPRINT_RUN(2, m_szPlanes[n] + " facing away from light dist " + String(Variant(dist))); + + // add the frustum back plane to the clipping planes + planes.push_back(m_Planes[n]); + } + else + { + // is the light out of range? + // This is one of the tests. If the point source is more than range distance from a frustum plane, it can't + // be seen + if (dist >= lsource.m_fRange) + { + // if the light is out of range, no need to do anything else, everything will be culled + //out_of_range = true; + //return false; + goto LightCulled; + } + } + } + } // if omni + else + { + // SPOTLIGHTs, more complex to cull + Vector3 ptConeEnd = lsource.m_ptPos + (lsource.m_ptDir * lsource.m_fRange); + + // this is the radius of the cone at distance 1 + float radius_at_dist_one = Math::tan(Math::deg2rad(lsource.m_fSpread)); + + // the worst case radius of the cone at the end point can be calculated + // (the radius will scale linearly with length along the cone) + float radius_at_end = radius_at_dist_one * lsource.m_fRange; + + for (int n = 0; n < 6; n++) + { + float dist = m_Planes[n].distance_to(lsource.m_ptPos); + if (dist < 0.0f) + { + // either the plane is backfacing or we are inside the frustum + lookup |= 1 << n; + + // add the frustum back plane to the clipping planes + planes.push_back(m_Planes[n]); + } + else + { + // the light is in front of the plane + + // is the light out of range? + if (dist >= lsource.m_fRange) + { + //out_of_range = true; + //return false; + if (manager.m_bDebugFrameString) + manager.DebugString_Add("Light culled cone start out of range .. source room" + itos(lsource.m_RoomID) + "\n"); + goto LightCulled; + } + + // for a spotlight, we can use an extra test + // at this point the cone start is in front of the plane... + // if the cone end point is further than the maximum possible distance to the plane + // we can guarantee that the cone does not cross the plane, and hence the cone + // is outside the frustum + + // side vector (perpendicular to light direction and plane normal) + Vector3 ptDirSide = lsource.m_ptDir.cross(m_Planes[n].normal); + + // note this could be near zero length...this is not dealt with yet!! + Vector3 ptDirConeClosest = lsource.m_ptDir.cross(ptDirSide); + + // see https://bartwronski.com/2017/04/13/cull-that-cone/ + ptDirConeClosest.normalize(); + + // push out the cone end to the closest point + Vector3 ptConeClosest = ptConeEnd + (ptDirConeClosest * radius_at_end); + + float dist_end = m_Planes[n].distance_to(ptConeClosest); + + if (dist_end >= 0.0f) + { + //out_of_range = true; + //return false; + if (manager.m_bDebugFrameString) + { + manager.DebugString_Add("Light culled cone end out of range .. source room" + itos(lsource.m_RoomID) + "\n"); + manager.DebugString_Add("cone end radius " + ftos(radius_at_end) + ", dist_end " + ftos(dist_end) + "\n"); + } + goto LightCulled; + } + } + } + } + + goto NotCulled; + + LightCulled: + + // remove any added planes .. may not be necessary + planes.resize(nPlanesBefore); + + + return false; + + NotCulled: + + + LPRINT_RUN(2, "LOOKUP " + itos(lookup)); + + assert (lookup < LUT_SIZE); + + // deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away) + // then we will add the camera frustum planes to clip the light volume .. there is no need to + // render shadow casters outside the frustum as shadows can never re-enter the frustum. + if (lookup == 63) + { + /* + for (int n=0; n &entry = m_LUT[lookup]; + + // each edge forms a plane + int nEdges = entry.size()-1; +#else + uint8_t * entry = &m_LUT_Entries[lookup][0]; + int nEdges = m_LUT_EntrySizes[lookup] - 1; +#endif + + + for (int e=0; eget_frustum()); + + if (m_Points.size() != 8) + m_Points.resize(8); + +// Transform p_transform = pCam->get_global_transform(); + + // Vector planes = get_projection_planes(Transform()); + const CameraMatrix::Planes intersections[8][3] = { + { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_TOP }, + { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_BOTTOM }, + { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_TOP }, + { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_BOTTOM }, + { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_TOP }, + { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_BOTTOM }, + { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_TOP }, + { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_BOTTOM }, + }; + + m_ptCentre.zero(); + + for (int i = 0; i < 8; i++) { + + Vector3 point; + bool res = m_Planes[intersections[i][0]].intersect_3(m_Planes[intersections[i][1]], m_Planes[intersections[i][2]], &point); + ERR_FAIL_COND_V(!res, false); + + //m_Points[i] = p_transform.xform(point); + m_Points[i] = point; + + m_ptCentre += point; +// print_line("point " + itos(i) + " " + String(point) + " -> " + String(m_Points[i])); + } + + m_ptCentre *= 1.0f / 8.0f; + +#define PUSH_PT(a) manager.m_DebugFrustums.push_back(m_Points[a]) + + if (manager.m_bDebugFrustums) + { + PUSH_PT(PT_NEAR_LEFT_TOP); + PUSH_PT(PT_FAR_LEFT_TOP); + PUSH_PT(PT_NEAR_RIGHT_TOP); + PUSH_PT(PT_FAR_RIGHT_TOP); + PUSH_PT(PT_NEAR_LEFT_BOTTOM); + PUSH_PT(PT_FAR_LEFT_BOTTOM); + PUSH_PT(PT_NEAR_RIGHT_BOTTOM); + PUSH_PT(PT_FAR_RIGHT_BOTTOM); + } + + + return true; +} + + + +uint8_t LMainCamera::m_LUT_Entries[64][8] = { +{0, 0, 0, 0, 0, 0, 0, }, +{7, 6, 4, 5, 0, 0, 0, }, +{1, 0, 2, 3, 0, 0, 0, }, +{0, 0, 0, 0, 0, 0, 0, }, +{1, 5, 4, 0, 0, 0, 0, }, +{1, 5, 7, 6, 4, 0, 0, }, +{4, 0, 2, 3, 1, 5, 0, }, +{5, 7, 6, 4, 0, 2, 3, }, +{0, 4, 6, 2, 0, 0, 0, }, +{0, 4, 5, 7, 6, 2, 0, }, +{6, 2, 3, 1, 0, 4, 0, }, +{2, 3, 1, 0, 4, 5, 7, }, +{0, 1, 5, 4, 6, 2, 0, }, +{0, 1, 5, 7, 6, 2, 0, }, +{6, 2, 3, 1, 5, 4, 0, }, +{2, 3, 1, 5, 7, 6, 0, }, +{2, 6, 7, 3, 0, 0, 0, }, +{2, 6, 4, 5, 7, 3, 0, }, +{7, 3, 1, 0, 2, 6, 0, }, +{3, 1, 0, 2, 6, 4, 5, }, +{0, 0, 0, 0, 0, 0, 0, }, +{2, 6, 4, 0, 1, 5, 7, }, +{7, 3, 1, 5, 4, 0, 2, }, +{0, 0, 0, 0, 0, 0, 0, }, +{2, 0, 4, 6, 7, 3, 0, }, +{2, 0, 4, 5, 7, 3, 0, }, +{7, 3, 1, 0, 4, 6, 0, }, +{3, 1, 0, 4, 5, 7, 0, }, +{2, 0, 1, 5, 4, 6, 7, }, +{2, 0, 1, 5, 7, 3, 0, }, +{7, 3, 1, 5, 4, 6, 0, }, +{3, 1, 5, 7, 0, 0, 0, }, +{3, 7, 5, 1, 0, 0, 0, }, +{3, 7, 6, 4, 5, 1, 0, }, +{5, 1, 0, 2, 3, 7, 0, }, +{7, 6, 4, 5, 1, 0, 2, }, +{3, 7, 5, 4, 0, 1, 0, }, +{3, 7, 6, 4, 0, 1, 0, }, +{5, 4, 0, 2, 3, 7, 0, }, +{7, 6, 4, 0, 2, 3, 0, }, +{0, 0, 0, 0, 0, 0, 0, }, +{3, 7, 6, 2, 0, 4, 5, }, +{5, 1, 0, 4, 6, 2, 3, }, +{0, 0, 0, 0, 0, 0, 0, }, +{3, 7, 5, 4, 6, 2, 0, }, +{3, 7, 6, 2, 0, 1, 0, }, +{5, 4, 6, 2, 3, 7, 0, }, +{7, 6, 2, 3, 0, 0, 0, }, +{3, 2, 6, 7, 5, 1, 0, }, +{3, 2, 6, 4, 5, 1, 0, }, +{5, 1, 0, 2, 6, 7, 0, }, +{1, 0, 2, 6, 4, 5, 0, }, +{3, 2, 6, 7, 5, 4, 0, }, +{3, 2, 6, 4, 0, 1, 0, }, +{5, 4, 0, 2, 6, 7, 0, }, +{6, 4, 0, 2, 0, 0, 0, }, +{3, 2, 0, 4, 6, 7, 5, }, +{3, 2, 0, 4, 5, 1, 0, }, +{5, 1, 0, 4, 6, 7, 0, }, +{1, 0, 4, 5, 0, 0, 0, }, +{0, 0, 0, 0, 0, 0, 0, }, +{3, 2, 0, 1, 0, 0, 0, }, +{5, 4, 6, 7, 0, 0, 0, }, +{0, 0, 0, 0, 0, 0, 0, }, +}; + diff --git a/lmain_camera.h b/lmain_camera.h new file mode 100644 index 0000000..d289a9e --- /dev/null +++ b/lmain_camera.h @@ -0,0 +1,101 @@ +#pragma once + +// 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. + +//#define LMAINCAMERA_CALC_LUT + +// we get the main camera clipping planes and derive the points from 3 plane equations +// in order to cull shadow casters to the camera frustum +class LMainCamera +{ +public: + // same order as godot + enum ePlane { + P_NEAR, + P_FAR, + P_LEFT, + P_TOP, + P_RIGHT, + P_BOTTOM, + P_TOTAL, + }; + + // same order as godot + enum ePoint { + PT_FAR_LEFT_TOP, + PT_FAR_LEFT_BOTTOM, + PT_FAR_RIGHT_TOP, + PT_FAR_RIGHT_BOTTOM, + PT_NEAR_LEFT_TOP, + PT_NEAR_LEFT_BOTTOM, + PT_NEAR_RIGHT_TOP, + PT_NEAR_RIGHT_BOTTOM, + }; + + static const char * m_szPlanes[]; + static const char * m_szPoints[]; + + // 6 bits, 6 planes + enum { NUM_CAM_PLANES = 6, + NUM_CAM_POINTS = 8, + MAX_CULL_PLANES = 16, + LUT_SIZE = 64, + }; + + // create the LUT + LMainCamera(); + + bool Prepare(LRoomManager &manager, Camera * pCam); + + // main use of this object, we can create a clipping volume that is a mix of the light frustum and the camera volume + bool AddCameraLightPlanes(LRoomManager &manager, const LSource &lsource, LVector &planes) const; + + LVector m_Planes; + LVector m_Points; + + // centre of camera frustum + Vector3 m_ptCentre; + +private: + bool AddCameraLightPlanes_Directional(LRoomManager &manager, const LSource &lsource, LVector &planes) const; + String String_PlaneBF(unsigned int BF); + +#ifdef LMAINCAMERA_CALC_LUT + void GetNeighbours(ePlane p, ePlane neigh_planes[4]) const; + void GetCornersOfPlanes( ePlane _fpPlane0, ePlane _fpPlane1, ePoint _fpRet[2] ) const; + void CreateLUT(); + void CompactLUT_Entry(int n); + void DebugPrintLUT(); + void DebugPrintLUT_AsTable(); + void AddLUT(int p0, int p1, ePoint pts[2]); + void AddLUT_Entry(unsigned int n, ePoint pts[2]); + String DebugStringLUT_Entry(const LVector &entry); + String String_LUTEntry(const LVector &entry); + + // contains a list of points for each combination of plane facing directions + LVector m_LUT[LUT_SIZE]; +#endif + + // precalculated LUT + static uint8_t m_LUT_EntrySizes[LUT_SIZE]; + static uint8_t m_LUT_Entries[LUT_SIZE][8]; +}; + diff --git a/lportal.cpp b/lportal.cpp index 0ed25da..a4f1a8f 100644 --- a/lportal.cpp +++ b/lportal.cpp @@ -24,7 +24,7 @@ #include "ldebug.h" #include "lroom_manager.h" - +///////////////////////////////////////////////////////////////////// bool LPortal::NameStartsWith(Node * pNode, String szSearch) { @@ -86,7 +86,7 @@ void LPortal::AddLightPlanes(LRoomManager &manager, const LLight &light, LVector int nPoints = pts.size(); ERR_FAIL_COND(nPoints < 3); - if (light.m_eType == LLight::LT_DIRECTIONAL) + if (light.m_Source.m_eType == LSource::ST_DIRECTIONAL) { // assuming ortho light const int max_points = 32; @@ -96,7 +96,7 @@ void LPortal::AddLightPlanes(LRoomManager &manager, const LLight &light, LVector nPoints = max_points; // transform pushed points - Vector3 ptPush = light.m_ptDir * 2.0; + Vector3 ptPush = light.m_Source.m_ptDir * 2.0; 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()); - } - - } - } + #ifdef LDEBUG_LIGHT_AFFECTED_ROOMS + if (manager.m_bDebugFrameString) + manager.DebugString_Add(itos(lightID) + ", "); + #endif } +#ifdef LDEBUG_LIGHT_AFFECTED_ROOMS + if (manager.m_bDebugFrameString) + manager.DebugString_Add("\n"); +#endif + + // NEW .. global area directional lights + // could be done with area bitflags... more efficiently + for (int n=0; n &planes, int first_portal_plane) +/* +void LRoom::DetermineVisibility_Recursive(LRoomManager &manager, int depth, const LSource &cam, const LVector &planes, int first_portal_plane) { // prevent too much depth if (depth > 8) @@ -668,5 +705,5 @@ void LRoom::DetermineVisibility_Recursive(LRoomManager &manager, int depth, cons } // for p through portals } - +*/ diff --git a/lroom.h b/lroom.h index abb8e31..0695580 100644 --- a/lroom.h +++ b/lroom.h @@ -39,14 +39,6 @@ class LPortal; class LRoomManager; class MeshInstance; -class LCamera -{ -public: - // all in world space, culling done in world space - Vector3 m_ptPos; - Vector3 m_ptDir; -}; - class LRoom @@ -71,6 +63,12 @@ public: // local lights affecting this room LVector m_LocalLights; + // global lights affecting this room + LVector m_GlobalLights; + + // areas this room is in + LVector m_Areas; + // portals are stored in the manager in a contiguous list int m_iFirstPortal; int m_iNumPortals; @@ -100,8 +98,8 @@ public: const String &get_name() const {return m_szName;} // main function - void DetermineVisibility_Recursive(LRoomManager &manager, int depth, const LCamera &cam, const LVector &planes, int first_portal_plane = 1); - void FirstTouch(LRoomManager &manager); +// void DetermineVisibility_Recursive(LRoomManager &manager, int depth, const LSource &cam, const LVector &planes, int first_portal_plane = 1); +// void FirstTouch(LRoomManager &manager); // allows us to show / hide all dobs as the room visibility changes @@ -129,6 +127,10 @@ public: LRoom(); Spatial * GetGodotRoom() const; + // light casting .. changing the local light list + bool RemoveLocalLight(int light_id); + void AddLocalLight(int light_id) {m_LocalLights.push_back(light_id);} + // retained purely for debugging visualization Geometry::MeshData m_Bound_MeshData; @@ -137,6 +139,7 @@ public: // 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. static void SoftShow(VisualInstance * pVI, uint32_t show_flags); + bool IsInArea(int area) const; private: // whether lportal thinks this room is currently visible diff --git a/lroom_converter.cpp b/lroom_converter.cpp index 66770d3..0707efd 100644 --- a/lroom_converter.cpp +++ b/lroom_converter.cpp @@ -43,20 +43,6 @@ void LRoomConverter::Convert(LRoomManager &manager, bool bVerbose, bool bPrepara // except when requested by explicitly clearing this flag. Lawn::LDebug::m_bRunning = (bVerbose == false); - // test pool vector -// PoolVector arr; -// arr.append(Vector2(0, 0)); -// arr.append(Vector2(1, 0)); -// arr.append(Vector2(2, 0)); -// arr.insert(1, arr[1]); - -// LPRINT(5, "DEBUG POOLVECTOR"); -// for (int n=0; nm_Lights.size(); + //int num_global_lights = LMAN->m_Lights.size(); // make sure bitfield is right size for number of rooms LMAN->m_BF_visible_rooms.Create(count); + LMAN->m_LightRender.m_BF_Temp_Visible_Rooms.Create(count); LMAN->m_Rooms.resize(count); @@ -99,12 +86,16 @@ void LRoomConverter::Convert(LRoomManager &manager, bool bVerbose, bool bPrepara LMAN->m_BF_master_SOBs.Create(num_sobs); LMAN->m_BF_master_SOBs_prev.Create(num_sobs); + LMAN->m_LightRender.m_BF_Temp_SOBs.Create(num_sobs); + LMAN->m_BF_ActiveLights.Create(LMAN->m_Lights.size()); LMAN->m_BF_ActiveLights_prev.Create(LMAN->m_Lights.size()); + LMAN->m_BF_ProcessedLights.Create(LMAN->m_Lights.size()); // must be done after the bitfields Convert_Lights(); Convert_ShadowCasters(); + Convert_AreaLights(); // hide all in preparation for first frame //LMAN->ShowAll(false); @@ -119,6 +110,33 @@ void LRoomConverter::Convert(LRoomManager &manager, bool bVerbose, bool bPrepara +int LRoomConverter::Convert_Rooms_Recursive(Node * pParent, int count, int area) +{ + for (int n=0; nget_child_count(); n++) + { + Node * pChild = pParent->get_child(n); + + if (Node_IsRoom(pChild)) + { + Spatial * pSpat = Object::cast_to(pChild); + assert (pSpat); + + Convert_Room(pSpat, count++, area); + } + else if (Node_IsArea(pChild)) + { + // get the area name + String szArea = LPortal::FindNameAfter(pChild, "area_"); + + // find or create an area with this name + int area_child = Area_FindOrCreate(szArea); + count = Convert_Rooms_Recursive(pChild, count, area_child); + } + } + + return count; +} + void LRoomConverter::Convert_Rooms() { @@ -126,22 +144,27 @@ void LRoomConverter::Convert_Rooms() // first find all room empties and convert to LRooms int count = 0; + int area = -1; - for (int n=0; nget_child_count(); n++) + count = Convert_Rooms_Recursive(LROOMLIST, count, area); +} + +int LRoomConverter::Area_FindOrCreate(String szName) +{ + for (int n=0; nm_Areas.size(); n++) { - Node * pChild = LROOMLIST->get_child(n); - - if (!Node_IsRoom(pChild)) - continue; - - Spatial * pSpat = Object::cast_to(pChild); - assert (pSpat); - - Convert_Room(pSpat, count++); + if (LMAN->m_Areas[n].m_szName == szName) + return n; } + // create + LArea area; + area.Create(szName); + LMAN->m_Areas.push_back(area); + return LMAN->m_Areas.size() - 1; } + int LRoomConverter::FindRoom_ByName(String szName) const { for (int n=0; nm_Rooms.size(); n++) @@ -179,6 +202,14 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l { Node * pChild = pParent->get_child(n); + // ignore invisible + Spatial * pSpatialChild = Object::cast_to(pChild); + if (pSpatialChild && (pSpatialChild->is_visible_in_tree() == false)) + { + pSpatialChild->queue_delete(); + continue; + } + // we are not interested in portal meshes, as they will be deleted later in conversion if (Node_IsPortal(pChild)) continue; @@ -196,6 +227,13 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l continue; } + // area + if (Node_IsArea(pChild)) + { + LRoom_DetectedArea(lroom, pChild); + continue; + } + VisualInstance * pVI = Object::cast_to(pChild); if (pVI) { @@ -234,13 +272,21 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l } -bool LRoomConverter::Convert_Room(Spatial * pNode, int lroomID) +// areaID could be -1 if unset +bool LRoomConverter::Convert_Room(Spatial * pNode, int lroomID, int areaID) { // get the room part of the name String szFullName = pNode->get_name(); String szRoom = LPortal::FindNameAfter(pNode, "room_"); - LPRINT(4, "Convert_Room : " + szFullName); + if (areaID == -1) + { + LPRINT(4, "Convert_Room : " + szFullName); + } + else + { + LPRINT(4, "Convert_Room : " + szFullName + " area_id " + itos(areaID)); + } // get a reference to the lroom we are writing to LRoom &lroom = LMAN->m_Rooms[lroomID]; @@ -252,11 +298,15 @@ bool LRoomConverter::Convert_Room(Spatial * pNode, int lroomID) // save the room ID on the godot room metadata // This is used when registering DOBs and teleporting them with hints // i.e. the Godot room is used to lookup the room ID of the startroom. - LMAN->Obj_SetRoomNum(pNode, lroomID); + LMAN->Meta_SetRoomNum(pNode, lroomID); // create a new LRoom to exchange the children over to, and delete the original empty lroom.m_szName = szRoom; + // area + if (areaID != -1) + lroom.m_Areas.push_back(areaID); + // keep a running bounding volume as we go through the visual instances // to determine the overall bound of the room LAABB bb_room; @@ -390,33 +440,180 @@ bool LRoomConverter::Convert_Bound(LRoom &lroom, MeshInstance * pMI) //} +void LRoomConverter::Convert_AreaLights() +{ + // list the rooms in each area + for (int a=0; am_Areas.size(); a++) + { + LArea &area = LMAN->m_Areas[a]; + + // add every room in this area to the light affected rooms list + for (int r=0; rm_Rooms.size(); r++) + { + LRoom &room = LMAN->m_Rooms[r]; + if (room.IsInArea(a)) + { + // add the room to the area room list + if (area.m_iNumRooms == 0) + area.m_iFirstRoom = LMAN->m_AreaRooms.size(); + + area.m_iNumRooms += 1; + LMAN->m_AreaRooms.push_back(r); + } + } + } + + + // first identify which lights are area lights, and match area strings to area IDs + for (int n=0; nm_Lights.size(); n++) + { + LLight &l = LMAN->m_Lights[n]; + + // global area light? + if (!l.m_Source.IsGlobal()) + continue; + + assert (l.m_iArea == -1); + + // match area string to area + // find the area + for (int n=0; nm_Areas.size(); n++) + { + if (LMAN->m_Areas[n].m_szName == l.m_szArea) + { + l.m_iArea = n; + break; + } + } + + // area not found? + if (l.m_iArea == -1) + { + LWARN(2, "Convert_AreaLights area not found : " + l.m_szArea); + } + else + { + LPRINT(5,"Area light " + itos (n) + " area " + l.m_szArea + " found area_id " + itos(l.m_iArea)); + } + } + + + // add each light within an area to the area light list + for (int a=0; am_Areas.size(); a++) + { + LArea &area = LMAN->m_Areas[a]; + + for (int n=0; nm_Lights.size(); n++) + { + LLight &l = LMAN->m_Lights[n]; + + int areaID = l.m_iArea; + if (areaID != a) + continue; + + // this light affects this area + if (area.m_iNumLights == 0) + area.m_iFirstLight = LMAN->m_AreaLights.size(); + + LMAN->m_AreaLights.push_back(n); + area.m_iNumLights++; + + + } // for n + } // for a + + // for each global light we can calculate the affected rooms + for (int n=0; nm_Lights.size(); n++) + { + LLight &l = LMAN->m_Lights[n]; + + int areaID = l.m_iArea; + + // not a global light + if (areaID == -1) + continue; + + LPRINT(5,"Area light " + itos (n) + " affected rooms:"); + + // add every room in this area to the light affected rooms list + for (int r=0; rm_Rooms.size(); r++) + { + LRoom &room = LMAN->m_Rooms[r]; + if (room.IsInArea(areaID)) + { + //l.AddAffectedRoom(r); // no need as this is now done by area + LPRINT(5,"\t" + itos (r)); + + // store the global lights on the room + room.m_GlobalLights.push_back(n); + } + + } + } +} + + void LRoomConverter::Convert_Lights() { // trace local lights out from rooms and add to each room the light affects for (int n=0; nm_Lights.size(); n++) { LLight &l = LMAN->m_Lights[n]; - if (l.IsGlobal()) + if (l.m_Source.IsGlobal()) continue; // ignore globals .. affect all rooms Light_Trace(n); } } + void LRoomConverter::Light_Trace(int iLightID) { + // get the light + LLight &l = LMAN->m_Lights[iLightID]; + LPRINT(5,"_____________________________________________________________"); + LPRINT(5,"\nLight_Trace " + itos (iLightID)); + + LMAN->m_Trace.Trace_Light(*LMAN, l, LTrace::LR_CONVERT); + + // now save the data from the trace + LRoomManager::LLightRender &lr = LMAN->m_LightRender; + + // visible rooms + for (int n=0; nGetRoom(room_id); + + room.AddLocalLight(iLightID); + + // store the affected room on the light + l.AddAffectedRoom(room_id); + } + + + // sobs + for (int n=0; nm_LightCasters_SOB.size(); + + LMAN->m_LightCasters_SOB.push_back(sob_id); + l.m_NumCasters++; + } + + LPRINT(5, itos(lr.m_Temp_Visible_Rooms.size()) + " visible rooms, " + itos (lr.m_Temp_Visible_SOBs.size()) + " visible SOBs.\n"); + +/* // blank this each time as it is used to create the list of casters LMAN->m_BF_caster_SOBs.Blank(); - // get the light - LLight &l = LMAN->m_Lights[iLightID]; - - LPRINT(5,"\nLight_Trace " + itos (iLightID) + " direction " + l.m_ptDir); - // reset the planes pool for each render out from the source room LMAN->m_Pool.Reset(); - // the first set of planes are blank unsigned int pool_member = LMAN->m_Pool.Request(); assert (pool_member != -1); @@ -426,10 +623,11 @@ void LRoomConverter::Light_Trace(int iLightID) Lawn::LDebug::m_iTabDepth = 0; - Light_TraceRecursive(0, LMAN->m_Rooms[l.m_RoomID], l, iLightID, planes); + Light_TraceRecursive(0, LMAN->m_Rooms[l.m_Source.m_RoomID], l, iLightID, planes); + */ } - +/* void LRoomConverter::Light_TraceRecursive(int depth, LRoom &lroom, LLight &light, int iLightID, const LVector &planes) { // prevent too much depth @@ -458,6 +656,9 @@ void LRoomConverter::Light_TraceRecursive(int depth, LRoom &lroom, LLight &light if (!bAlreadyInList) { lroom.m_LocalLights.push_back(iLightID); + + // store the affected room on the light + light.AddAffectedRoom(lroom.m_RoomID); } // add each light caster that is within the planes to the light caster list @@ -519,7 +720,7 @@ void LRoomConverter::Light_TraceRecursive(int depth, LRoom &lroom, LLight &light LPRINT_RUN(2, "\tPORTAL " + itos (n) + " (" + itos(portalID) + ") " + port.get_name() + " normal " + port.m_Plane.normal); - float dot = port.m_Plane.normal.dot(light.m_ptDir); + float dot = port.m_Plane.normal.dot(light.m_Source.m_ptDir); if (dot <= 0.0f) { @@ -596,7 +797,7 @@ void LRoomConverter::Light_TraceRecursive(int depth, LRoom &lroom, LLight &light } } - +*/ void LRoomConverter::Convert_ShadowCasters() { @@ -608,22 +809,22 @@ void LRoomConverter::Convert_ShadowCasters() { const LLight &light = LMAN->m_Lights[l]; String sz = "Light " + itos (l); - if (light.IsGlobal()) + if (light.m_Source.IsGlobal()) sz += " GLOBAL"; else - sz += " LOCAL from room " + itos(light.m_RoomID); + sz += " LOCAL from room " + itos(light.m_Source.m_RoomID); - LPRINT(5, sz + " direction " + light.m_ptDir); + LPRINT(5, sz + " direction " + light.m_Source.m_ptDir); for (int n=0; nm_Rooms.size(); n++) { LRoom &lroom = LMAN->m_Rooms[n]; // global lights affect every room - bool bAffectsRoom = true; + bool bAffectsRoom = false; // true // if the light is local, does it affect this room? - if (!light.IsGlobal()) + if (!light.m_Source.IsGlobal()) { // a local light .. does it affect this room? bAffectsRoom = false; @@ -720,8 +921,22 @@ int LRoomConverter::CountRooms() for (int n=0; nget_child(n))) + Node * pChild = LROOMLIST->get_child(n); + if (Node_IsRoom(pChild)) count++; + else + { + // also check the children if this is an area + if (Node_IsArea(pChild)) + { + for (int c=0; cget_child_count(); c++) + { + Node * pChild2 = pChild->get_child(c); + if (Node_IsRoom(pChild2)) + count++; + } + } + } } return count; @@ -761,6 +976,7 @@ int LRoomConverter::CountRooms() // return; //} +/* void LRoomConverter::Light_AddCaster_SOB(LLight &light, int sobID) { // we will reuse the rendering bitflags for shadow casters for this ... to check for double entries (fnaa fnaa) @@ -780,7 +996,7 @@ void LRoomConverter::Light_AddCaster_SOB(LLight &light, int sobID) LMAN->m_LightCasters_SOB.push_back(sobID); light.m_NumCasters++; } - +*/ void LRoomConverter::LRoom_AddShadowCaster_SOB(LRoom &lroom, int sobID) { @@ -907,14 +1123,14 @@ void LRoomConverter::LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int // cull with light direction float dot; - if (light.m_eType == LLight::LT_DIRECTIONAL) + if (light.m_Source.m_eType == LSource::ST_DIRECTIONAL) { - dot = port.m_Plane.normal.dot(light.m_ptDir); + dot = port.m_Plane.normal.dot(light.m_Source.m_ptDir); } else { // cull with light direction to portal - Vector3 ptLightToPort = port.m_ptCentre - light.m_ptPos; + Vector3 ptLightToPort = port.m_ptCentre - light.m_Source.m_ptPos; dot = port.m_Plane.normal.dot(ptLightToPort); } @@ -1090,6 +1306,26 @@ void LRoomConverter::LRoom_MakePortalFinalList(LRoom &lroom, LTempRoom &troom) } + +void LRoomConverter::LRoom_DetectedArea(LRoom &lroom, Node * pNode) +{ + // find the area name + String szArea = LPortal::FindNameAfter(pNode, "area_"); + + // find or create an area with this name + int area = Area_FindOrCreate(szArea); + + // check for duplicates? maybe a level design mistake? + if (lroom.m_Areas.find(area) != -1) + { + LWARN(2, "LRoom_DetectedArea : duplicate area in room, ignoring : " + szArea); + return; + } + + // add it to the lroom + lroom.m_Areas.push_back(area); +} + void LRoomConverter::LRoom_DetectedLight(LRoom &lroom, Node * pNode) { Light * pLight = Object::cast_to(pNode); @@ -1214,6 +1450,18 @@ bool LRoomConverter::Node_IsLight(Node * pNode) const return true; } +bool LRoomConverter::Node_IsArea(Node * pNode) const +{ + Spatial * pSpat = Object::cast_to(pNode); + if (!pSpat) + return false; + + if (LPortal::NameStartsWith(pSpat, "area_")) + return true; + + return false; +} + bool LRoomConverter::Node_IsRoom(Node * pNode) const { diff --git a/lroom_converter.h b/lroom_converter.h index 07fa207..7afdc74 100644 --- a/lroom_converter.h +++ b/lroom_converter.h @@ -31,6 +31,7 @@ class LRoomManager; class LRoom; +class LArea; class MeshInstance; // simple min max aabb @@ -87,7 +88,8 @@ private: int CountRooms(); void Convert_Rooms(); - bool Convert_Room(Spatial * pNode, int lroomID); + int Convert_Rooms_Recursive(Node * pParent, int count, int area); + bool Convert_Room(Spatial * pNode, int lroomID, int areaID); void Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &lroom, LAABB &bb_room); void Convert_Room_SetDefaultCullMask_Recursive(Node * pParent); @@ -96,7 +98,7 @@ private: bool Convert_Bound(LRoom &lroom, MeshInstance * pMI); void Convert_ShadowCasters(); void Convert_Lights(); -// void Convert_HideAll(); + void Convert_AreaLights(); void LRoom_DetectPortalMeshes(LRoom &lroom, LTempRoom &troom); @@ -109,11 +111,9 @@ private: // lights void LRoom_DetectedLight(LRoom &lroom, Node * pNode); void Light_Trace(int iLightID); - void Light_TraceRecursive(int depth, LRoom &lroom, LLight &light, int iLightID, const LVector &planes); - void Light_AddCaster_SOB(LLight &light, int sobID); + void LRoom_DetectedArea(LRoom &lroom, Node * pNode); // shadows -// void LRoom_FindShadowCasters(LRoom &lroom, int lightID, const LLight &light); void LRoom_FindShadowCasters_FromLight(LRoom &lroom, const LLight &light); void LRoom_FindShadowCasters_Recursive(LRoom &source_lroom, int depth, LRoom &lroom, const LLight &light, const LVector &planes); void LRoom_AddShadowCaster_SOB(LRoom &lroom, int sobID); @@ -124,13 +124,14 @@ private: // helper bool Node_IsRoom(Node * pNode) const; + bool Node_IsArea(Node * pNode) const; bool Node_IsPortal(Node * pNode) const; bool Node_IsBound(Node * pNode) const; bool Node_IsIgnore(Node * pNode) const; bool Node_IsLight(Node * pNode) const; int FindRoom_ByName(String szName) const; - + int Area_FindOrCreate(String szName); // set up on entry diff --git a/lroom_manager.cpp b/lroom_manager.cpp index 78eaa5d..355fd06 100644 --- a/lroom_manager.cpp +++ b/lroom_manager.cpp @@ -45,6 +45,9 @@ LRoomManager::LRoomManager() 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; @@ -59,6 +62,9 @@ LRoomManager::LRoomManager() m_bDebugPlanes = false; m_bDebugBounds = false; m_bDebugLights = false; + m_bDebugLightVolumes = false; + m_bDebugFrustums = false; + m_bDebugFrameString = false; m_pRoomList = 0; @@ -148,14 +154,31 @@ LRoom &LRoomManager::Portal_GetLinkedRoom(const LPortal &port) } -void LRoomManager::Obj_SetRoomNum(Node * pNode, int num) +// 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 (Obj_GetRoomNum(pNode) == num); + assert (Meta_GetRoomNum(pNode) == num); } -int LRoomManager::Obj_GetRoomNum(Node * pNode) const +int LRoomManager::Meta_GetRoomNum(Node * pNode) const { //assert (pNode->has_meta("_lroom")); Variant v = pNode->get_meta("_lroom"); @@ -167,7 +190,7 @@ int LRoomManager::Obj_GetRoomNum(Node * pNode) const LRoom * LRoomManager::GetRoomFromDOB(Node * pNode) { - int iRoom = Obj_GetRoomNum(pNode); + int iRoom = Meta_GetRoomNum(pNode); if (iRoom < 0) { if (iRoom == -1) @@ -211,7 +234,7 @@ bool LRoomManager::dob_register_hint(Node * pDOB, float radius, Node * pRoom) } - int iRoom = Obj_GetRoomNum(pRoom); + int iRoom = Meta_GetRoomNum(pRoom); Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) @@ -267,7 +290,49 @@ void LRoomManager::CreateDebug() move_child(b, get_child_count()-1); m_ID_DebugLights = b->get_instance_id(); b->set_material_override(m_mat_Debug_Bounds); - //b->hide(); + 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(); } } @@ -321,7 +386,7 @@ bool LRoomManager::DobRegister(Spatial * pDOB, float radius, int iRoom) pRoom->DOB_Add(dob); // save the room ID on the dob metadata - Obj_SetRoomNum(pDOB, iRoom); + Meta_SetRoomNum(pDOB, iRoom); // change visibility DobChangeVisibility(pDOB, 0, pRoom); @@ -396,7 +461,7 @@ int LRoomManager::dob_update(Node * pDOB) DobChangeVisibility(pSpat, pRoom, pNewRoom); // save the room ID on the dob metadata - Obj_SetRoomNum(pSpat, iRoomNum); + Meta_SetRoomNum(pSpat, iRoomNum); // new room number return iRoomNum; @@ -425,7 +490,7 @@ bool LRoomManager::dob_teleport_hint(Node * pDOB, Node * pRoom) } - int iRoom = Obj_GetRoomNum(pRoom); + int iRoom = Meta_GetRoomNum(pRoom); Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) @@ -471,7 +536,7 @@ bool LRoomManager::DobTeleport(Spatial * pDOB, int iNewRoomID) pOldRoom->DOB_Remove(dob_id); // save the room ID on the dob metadata - Obj_SetRoomNum(pDOB, iNewRoomID); + Meta_SetRoomNum(pDOB, iNewRoomID); // change visibility DobChangeVisibility(pDOB, pOldRoom, pNewRoom); @@ -509,7 +574,7 @@ bool LRoomManager::dob_unregister(Node * pDOB) LRoom * pRoom = GetRoomFromDOB(pDOB); // change the meta data on the DOB .. this will catch trying to update an unregistered DOB - Obj_SetRoomNum(pDOB, -2); + Meta_SetRoomNum(pDOB, -2); if (pRoom) { @@ -521,8 +586,135 @@ bool LRoomManager::dob_unregister(Node * pDOB) } +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) +bool LRoomManager::LightCreate(Light * pLight, int roomID, String szArea) { // set culling flag for light // 1 is for lighting objects outside the room system @@ -530,23 +722,21 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) // create new light LLight l; - l.SetDefaults(); + 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 - Transform tr = pLight->get_global_transform(); - l.m_ptPos = tr.origin; - l.m_ptDir = -tr.basis.get_axis(2); // or possibly get_axis .. z is what we want - l.m_RoomID = roomID; - - l.m_fMaxDist = pLight->get_param(Light::PARAM_SHADOW_MAX_DISTANCE); - - - //l.m_eType = LLight::LT_DIRECTIONAL; - - //m_Lights.push_back(l); - + Light_UpdateTransform(l, *pLight); + lsource.m_RoomID = roomID; bool bOK = false; @@ -555,8 +745,9 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) if (pSL) { LPRINT(2, "\tSPOTLIGHT detected " + pLight->get_name()); - l.m_eType = LLight::LT_SPOTLIGHT; - l.m_fSpread = pSL->get_param(Light::PARAM_SPOT_ANGLE); + 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; } @@ -565,7 +756,8 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) if (pOL) { LPRINT(2, "\tOMNILIGHT detected " + pLight->get_name()); - l.m_eType = LLight::LT_OMNI; + lsource.m_eType = LSource::ST_OMNI; + lsource.m_fRange = pLight->get_param(Light::PARAM_RANGE); bOK = true; } @@ -573,7 +765,10 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) if (pDL) { LPRINT(2, "\tDIRECTIONALLIGHT detected " + pLight->get_name()); - l.m_eType = LLight::LT_DIRECTIONAL; + lsource.m_eType = LSource::ST_DIRECTIONAL; + + // no range but max distance? NYI + bOK = true; } @@ -586,7 +781,7 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) // turn the local light off to start with - if (!l.IsGlobal()) + if (!lsource.IsGlobal()) { // l.Show(false); //pLight->hide(); @@ -598,10 +793,168 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) } -bool LRoomManager::light_register(Node * pLightNode) +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; n(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); } @@ -646,7 +1006,7 @@ void LRoomManager::DobChangeVisibility(Spatial * pDOB, const LRoom * pOld, const int LRoomManager::dob_get_room_id(Node * pDOB) { - return Obj_GetRoomNum(pDOB); + return Meta_GetRoomNum(pDOB); } // helpers to enable the client to manage switching on and off physics and AI @@ -688,51 +1048,6 @@ Node * LRoomManager::rooms_get_room(int room_id) return pRoom->GetGodotRoom(); } -void LRoomManager::rooms_set_debug_lights(bool bActive) -{ - m_bDebugLights = bActive; - Object * pObj = ObjectDB::get_instance(m_ID_DebugLights); - ImmediateGeometry * im = Object::cast_to(pObj); - if (!im) - return; - - if (bActive) - im->show(); - else - im->hide(); -} - - -void LRoomManager::rooms_set_debug_bounds(bool bActive) -{ - m_bDebugBounds = bActive; - - Object * pObj = ObjectDB::get_instance(m_ID_DebugBounds); - ImmediateGeometry * im = Object::cast_to(pObj); - if (!im) - return; - - if (bActive) - im->show(); - else - im->hide(); -} - - -void LRoomManager::rooms_set_debug_planes(bool bActive) -{ - m_bDebugPlanes = bActive; - - Object * pObj = ObjectDB::get_instance(m_ID_DebugPlanes); - ImmediateGeometry * im = Object::cast_to(pObj); - if (!im) - return; - - if (bActive) - im->show(); - else - im->hide(); -} // move the initial hiding to where the camera is set, so we can save the scene etc @@ -748,7 +1063,7 @@ void LRoomManager::ShowAll(bool bShow) for (int n=0; nshow(); -// else -// pS->hide(); VisualInstance * pVI = sob.GetVI(); if (pVI) @@ -809,6 +1118,12 @@ void LRoomManager::rooms_set_active(bool bActive) } +String LRoomManager::rooms_get_debug_frame_string() +{ + return m_szDebugString; +} + + void LRoomManager::rooms_set_logging(int level) { // 0 is no logging, 6 is max logging (i.e. reverse of the priorities in the code) @@ -989,8 +1304,12 @@ void LRoomManager::ReleaseResources(bool bPrepareConvert) m_LightCasters_SOB.clear(); m_Rooms.clear(true); m_Portals.clear(true); + m_Areas.clear(true); m_SOBs.clear(); + m_AreaLights.clear(true); + m_AreaRooms.clear(true); + if (!bPrepareConvert) m_Lights.clear(); @@ -1018,6 +1337,13 @@ void LRoomManager::FrameUpdate_Prepare() { if (m_bDebugPlanes) m_DebugPlanes.clear(); + + if (m_bDebugLightVolumes) + m_DebugLightVolumes.clear(); + + if (m_bDebugFrustums) + m_DebugFrustums.clear(); + // clear the visible room list to write to each frame m_pCurr_VisibleRoomList->clear(); @@ -1040,6 +1366,7 @@ void LRoomManager::FrameUpdate_Prepare() 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 @@ -1057,6 +1384,8 @@ bool LRoomManager::FrameUpdate() return false; } + DebugString_Set(""); + // could turn off internal processing? not that important if (!m_bActive) return false; @@ -1101,11 +1430,26 @@ bool LRoomManager::FrameUpdate() return false; } - // lcamera contains the info needed for culling - LCamera cam; +#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... @@ -1116,18 +1460,16 @@ bool LRoomManager::FrameUpdate() LVector &planes = m_Pool.Get(pool_member); planes.clear(); - // 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 - // luckily godot already has a function to return a list of the camera clipping planes - planes.copy_from(pCamera->get_frustum()); + 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 - pRoom->DetermineVisibility_Recursive(*this, 0, cam, planes); + 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(); @@ -1221,7 +1563,12 @@ void LRoomManager::FrameUpdate_AddShadowCasters() m_Rooms[r].AddShadowCasters(*this); } - LPRINT(2, "TOTAL shadow casters " + itos(m_CasterList_SOBs.size())); +#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() @@ -1250,6 +1597,12 @@ void LRoomManager::FrameUpdate_FinalizeVisibility_SoftShow() } } + +#ifdef LDEBUG_LIGHTS + if (m_bDebugFrameString) + DebugString_Add("nActiveLights " + itos(m_ActiveLights.size()) + "\n"); +#endif + // lights for (int n=0; nset_shadow(false); //pLight->set_shadow(true); //pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); - Vector3 ptBugFix = pLight->get_translation(); - pLight->set_translation(ptBugFix); + + +// Vector3 ptBugFix = pLight->get_translation(); +// pLight->set_translation(ptBugFix); } } + + // debug + DebugString_Light_AffectedRooms(lid); } for (int n=0; nend(); } + 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()) @@ -1461,6 +1860,11 @@ void LRoomManager::_bind_methods() 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); @@ -1479,7 +1883,12 @@ void LRoomManager::_bind_methods() ClassDB::bind_method(D_METHOD("dob_get_room_id", "dob"), &LRoomManager::dob_get_room_id); - ClassDB::bind_method(D_METHOD("light_register", "light"), &LRoomManager::light_register); + 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); @@ -1614,3 +2023,37 @@ void LRoomManager::ResolveRoomListPath() _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; +} diff --git a/lroom_manager.h b/lroom_manager.h index d679edd..363db89 100644 --- a/lroom_manager.h +++ b/lroom_manager.h @@ -32,9 +32,9 @@ #include "lroom.h" #include "lportal.h" - - - +#include "larea.h" +#include "ltrace.h" +#include "lmain_camera.h" class LRoomManager : public Spatial { GDCLASS(LRoomManager, Spatial); @@ -42,7 +42,111 @@ class LRoomManager : public Spatial { friend class LRoom; friend class LRoomConverter; friend class LHelper; + friend class LTrace; + friend class LMainCamera; +public: + // PUBLIC INTERFACE TO GDSCRIPT + //______________________________________________________________________________________ + // Roomlist path + void set_rooms(const Object *p_rooms); + void _set_rooms(Object *p_rooms); + void set_rooms_path(const NodePath &p_path); + NodePath get_rooms_path() const; + void remove_rooms_path(); + + //______________________________________________________________________________________ + // MAIN + // convert empties and meshes to rooms and portals + bool rooms_convert(bool bVerbose, bool bDeleteLights); + // free memory for current set of rooms, prepare for converting a new game level + void rooms_release(); + + // choose which camera you want to use to determine visibility. + // normally this will be your main camera, but you can choose another for debugging + bool rooms_set_camera(Node * pCam); + + // get the Godot room that is associated with an LPortal room + // (can be used to find the name etc of a room ID returned by dob_update) + Node * rooms_get_room(int room_id); + + //______________________________________________________________________________________ + // DOBS + // Dynamic objects .. cameras, players, boxes etc + // These are defined by their ability to move from room to room. + // You can still move static objects within the same room (e.g. elevators, moving platforms) + // as these don't require checks for changing rooms. + bool dob_register(Node * pDOB, float radius); + // register but let LPortal know which room the dob should start in + bool dob_register_hint(Node * pDOB, float radius, Node * pRoom); + + bool dob_unregister(Node * pDOB); + + // returns the room ID within + int dob_update(Node * pDOB); + + // if we are moving the DOB possibly through multiple rooms, then teleport rather than detect + // portal crossings + bool dob_teleport(Node * pDOB); + bool dob_teleport_hint(Node * pDOB, Node * pRoom); + + //______________________________________________________________________________________ + // LIGHTS + // global directional lights that will apply to all rooms + bool light_register(Node * pLightNode, String szArea); + + // dynamic lights (spot or omni within rooms) + bool dynamic_light_register(Node * pLightNode, float radius); + bool dynamic_light_register_hint(Node * pLightNode, float radius, Node * pRoom); + bool dynamic_light_unregister(Node * pLightNode); + int dynamic_light_update(Node * pLightNode); // returns room within + + //______________________________________________________________________________________ + // LIGHTMAPS + // helper function to merge SOB meshes for producing lightmaps VIA external blender workflow + bool rooms_merge_sobs(Node * pMergeMeshInstance); + bool rooms_unmerge_sobs(Node * pMergeMeshInstance); + bool rooms_transfer_uv2s(Node * pMeshInstance_From, Node * pMeshInstance_To); + + // one function to do all the uv mapping and lightmap creation in one + // (for godot lightmap workflow) + MeshInstance * rooms_convert_lightmap_internal(String szProxyFilename, String szLevelFilename); + + //______________________________________________________________________________________ + // HELPERS + // helper function for general use .. LPortal has the functionality, why not... + bool rooms_save_scene(Node * pNode, String szFilename); + // helpers to enable the client to manage switching on and off physics and AI + int rooms_get_num_rooms() const; + bool rooms_is_room_visible(int room_id) const; + Array rooms_get_visible_rooms() const; + // helper func, not needed usually as dob_update returns the room + int dob_get_room_id(Node * pDOB); + + + //______________________________________________________________________________________ + // DEBUGGING + // turn on and off culling for debugging + void rooms_set_active(bool bActive); + void rooms_set_debug_planes(bool bActive); + void rooms_set_debug_bounds(bool bActive); + void rooms_set_debug_lights(bool bActive); + void rooms_set_debug_shadows(bool bActive); + void rooms_set_debug_frustums(bool bActive); + void rooms_set_debug_frame_string(bool bActive); + + // 0 to 6 .. less to more + // defaults to 4 which is (2) in our priorities (i.e. 6 - level) + void rooms_set_logging(int level); + + // optionally lportal can output some debug info in a string each frame + String rooms_get_debug_frame_string(); + + // provide debugging output on the next frame + void rooms_log_frame(); + +private: + // PER FRAME STUFF // godot ID of the camera (which should be registered as a DOB to allow moving between rooms) ObjectID m_ID_camera; @@ -74,10 +178,105 @@ class LRoomManager : public Spatial { LVector * m_pCurr_VisibleRoomList; LVector * m_pPrev_VisibleRoomList; + // active lights + LVector m_ActiveLights; + LVector m_ActiveLights_prev; + Lawn::LBitField_Dynamic m_BF_ActiveLights; + Lawn::LBitField_Dynamic m_BF_ActiveLights_prev; + + // some lights may be processed on a frame but found not to intersect the view frustum + Lawn::LBitField_Dynamic m_BF_ProcessedLights; + + // keep all the light rendering stuff together + struct LLightRender + { + // each time we render from a light point of view, we reuse this list to store each caster ID + Lawn::LBitField_Dynamic m_BF_Temp_SOBs; + Lawn::LBitField_Dynamic m_BF_Temp_Visible_Rooms; + LVector m_Temp_Visible_SOBs; + LVector m_Temp_Visible_Rooms; + } m_LightRender; + + // keep a frame counter, to mark when objects have been hit by the visiblity algorithm // already to prevent multiple hits on rooms and objects unsigned int m_uiFrameCounter; +private: + // lists of rooms and portals, contiguous list so cache friendly + LVector m_Rooms; + LVector m_Portals; + LVector m_Areas; + + // static objects + LVector m_SOBs; + + // lights + LVector m_Lights; + + // SHADOWS + // master list of shadow casters for each room + LVector m_ShadowCasters_SOB; // not used any more? + + // master list of casters for each light (precalculated list) + LVector m_LightCasters_SOB; + + // AREAS + // master list of lights affecting each area + LVector m_AreaLights; + + // master list of rooms in each area + LVector m_AreaRooms; + + // The recursive visibility function needs to allocate loads of planes. + // We use a pool for this instead of allocating on the fly. + LPlanesPool m_Pool; + + +public: + // whether debug planes is switched on + bool m_bDebugPlanes; + bool m_bDebugBounds; + bool m_bDebugLights; + bool m_bDebugLightVolumes; + bool m_bDebugFrustums; + + // the planes are shown as a list of lines from the camera to the portal verts + LVector m_DebugPlanes; + LVector m_DebugPortalLightPlanes; + LVector m_DebugLightVolumes; + LVector m_DebugFrustums; + + // we are now referencing the rooms indirectly via a nodepath rather than directly being children + // of the LRoomManager node + NodePath m_path_RoomList; + ObjectID m_ID_RoomList; + + +private: + LTrace m_Trace; + // unchecked + Spatial * m_pRoomList; + LMainCamera m_MainCamera; + + // DEBUGGING + // 0 to 5 + int m_iLoggingLevel; + + ObjectID m_ID_DebugPlanes; + ObjectID m_ID_DebugBounds; + ObjectID m_ID_DebugLights; + ObjectID m_ID_DebugLightVolumes; + ObjectID m_ID_DebugFrustums; + + Ref m_mat_Debug_Planes; + Ref m_mat_Debug_Bounds; + Ref m_mat_Debug_LightVolumes; + Ref m_mat_Debug_Frustums; + + String m_szDebugString; + bool m_bDebugFrameString; + // for debugging, can turn LPortal on and off bool m_bActive; @@ -85,38 +284,7 @@ class LRoomManager : public Spatial { bool m_bFrustumOnly; private: - // lists of rooms and portals, contiguous list so cache friendly - LVector m_Rooms; - LVector m_Portals; - - // static objects - LVector m_SOBs; - - // lights - LVector m_Lights; - // active lights - LVector m_ActiveLights; - LVector m_ActiveLights_prev; - Lawn::LBitField_Dynamic m_BF_ActiveLights; - Lawn::LBitField_Dynamic m_BF_ActiveLights_prev; - - // master list of shadow casters for each room - LVector m_ShadowCasters_SOB; - - // master list of casters for each light - LVector m_LightCasters_SOB; - -protected: - static void _bind_methods(); - void _notification(int p_what); - - // The recursive visibility function needs to allocate loads of planes. - // We use a pool for this instead of allocating on the fly. - LPlanesPool m_Pool; - - // 0 to 5 - int m_iLoggingLevel; -private: + // PRIVATE FUNCS // this is where we do all the culling bool FrameUpdate(); void FrameUpdate_Prepare(); @@ -130,19 +298,30 @@ private: void FrameUpdate_FrustumOnly(); // draw planes and room hulls - void FrameUpdate_DrawDebug(const LCamera &cam, const LRoom &lroom); - + void FrameUpdate_DrawDebug(const LSource &cam, const LRoom &lroom); // internal - LRoom &Portal_GetLinkedRoom(const LPortal &port); + // dobs bool DobRegister(Spatial * pDOB, float radius, int iRoom); ObjectID DobRegister_FindVIRecursive(Node * pNode) const; bool DobTeleport(Spatial * pDOB, int iNewRoomID); - bool LightCreate(Light * pLight, int roomID); - void CreateDebug(); void DobChangeVisibility(Spatial * pDOB, const LRoom * pOld, const LRoom * pNew); + + void CreateDebug(); void ReleaseResources(bool bPrepareConvert); void ShowAll(bool bShow); + void ResolveRoomListPath(); + + // frame debug string + void DebugString_Set(String sz) {m_szDebugString = sz;} + void DebugString_Add(String sz) {m_szDebugString += sz;} + void DebugString_Light_AffectedRooms(int light_id); + + // now we are centralizing the tracing out from static and dynamic lights for each frame to this function + bool LightCreate(Light * pLight, int roomID, String szArea = ""); + void Light_UpdateTransform(LLight &light, const Light &glight) const; + void Light_FrameProcess(int lightID); + bool Light_FindCasters(int lightID); // helper funcs @@ -152,37 +331,16 @@ private: LRoom * GetRoomFromDOB(Node * pNode); int FindClosestRoom(const Vector3 &pt) const; + LRoom &Portal_GetLinkedRoom(const LPortal &port); + // for DOBs, we need some way of storing the room ID on them, so we use metadata (currently) // this is pretty gross but hey ho - int Obj_GetRoomNum(Node * pNode) const; - void Obj_SetRoomNum(Node * pNode, int num); + int Meta_GetRoomNum(Node * pNode) const; + void Meta_SetRoomNum(Node * pNode, int num); -public: - // whether debug planes is switched on - bool m_bDebugPlanes; - bool m_bDebugBounds; - bool m_bDebugLights; - - // the planes are shown as a list of lines from the camera to the portal verts - LVector m_DebugPlanes; - LVector m_DebugPortalLightPlanes; - - // we are now referencing the rooms indirectly via a nodepath rather than directly being children - // of the LRoomManager node - NodePath m_path_RoomList; - ObjectID m_ID_RoomList; - -private: - ObjectID m_ID_DebugPlanes; - ObjectID m_ID_DebugBounds; - ObjectID m_ID_DebugLights; - Ref m_mat_Debug_Planes; - Ref m_mat_Debug_Bounds; - - // unchecked - Spatial * m_pRoomList; - - void ResolveRoomListPath(); + // for lights we store the light ID in the metadata + void Meta_SetLightID(Node * pNode, int id); + int Meta_GetLightID(Node * pNode) const; public: // makes sure m_pRoomList is up to date and valid @@ -191,86 +349,13 @@ public: Spatial * GetRoomList_Checked(); // unchecked, be sure to call checked version first which will set m_pRoomList Spatial * GetRoomList() const {return m_pRoomList;} + +protected: + static void _bind_methods(); + void _notification(int p_what); + public: LRoomManager(); - - // PUBLIC INTERFACE TO GDSCRIPT - void set_rooms(const Object *p_rooms); - void _set_rooms(Object *p_rooms); - void set_rooms_path(const NodePath &p_path); - NodePath get_rooms_path() const; - void remove_rooms_path(); - - - // convert empties and meshes to rooms and portals - bool rooms_convert(bool bVerbose, bool bDeleteLights); - - // free memory for current set of rooms, prepare for converting a new game level - void rooms_release(); - - // choose which camera you want to use to determine visibility. - // normally this will be your main camera, but you can choose another for debugging - bool rooms_set_camera(Node * pCam); - - // get the Godot room that is associated with an LPortal room - // (can be used to find the name etc of a room ID returned by dob_update) - Node * rooms_get_room(int room_id); - - // helpers to enable the client to manage switching on and off physics and AI - int rooms_get_num_rooms() const; - bool rooms_is_room_visible(int room_id) const; - Array rooms_get_visible_rooms() const; - - - // helper function to merge SOB meshes for producing lightmaps VIA external blender workflow - bool rooms_merge_sobs(Node * pMergeMeshInstance); - bool rooms_unmerge_sobs(Node * pMergeMeshInstance); - bool rooms_transfer_uv2s(Node * pMeshInstance_From, Node * pMeshInstance_To); - - // one function to do all the uv mapping and lightmap creation in one - // (for godot lightmap workflow) - MeshInstance * rooms_convert_lightmap_internal(String szProxyFilename, String szLevelFilename); - - // helper function for general use .. LPortal has the functionality, why not... - bool rooms_save_scene(Node * pNode, String szFilename); - - // turn on and off culling for debugging - void rooms_set_active(bool bActive); - void rooms_set_debug_planes(bool bActive); - void rooms_set_debug_bounds(bool bActive); - void rooms_set_debug_lights(bool bActive); - - // 0 to 6 .. defaults to 4 which is (2) in our priorities (i.e. 6 - level) - void rooms_set_logging(int level); - - // provide debugging output on the next frame - void rooms_log_frame(); - - // Dynamic objects .. cameras, players, boxes etc - // These are defined by their ability to move from room to room. - // You can still move static objects within the same room (e.g. elevators, moving platforms) - // as these don't require checks for changing rooms. - bool dob_register(Node * pDOB, float radius); - // register but let LPortal know which room the dob should start in - bool dob_register_hint(Node * pDOB, float radius, Node * pRoom); - - bool dob_unregister(Node * pDOB); - - // returns the room ID within - int dob_update(Node * pDOB); - - // if we are moving the DOB possibly through multiple rooms, then teleport rather than detect - // portal crossings - bool dob_teleport(Node * pDOB); - bool dob_teleport_hint(Node * pDOB, Node * pRoom); - - - // helper func, not needed usually as dob_update returns the room - int dob_get_room_id(Node * pDOB); - - // LIGHTS - // global lights that will apply to all rooms - bool light_register(Node * pLightNode); }; #endif diff --git a/ltrace.cpp b/ltrace.cpp new file mode 100644 index 0000000..cf1d49c --- /dev/null +++ b/ltrace.cpp @@ -0,0 +1,606 @@ +// 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 "ltrace.h" +#include "ldebug.h" +#include "ldob.h" +#include "lportal.h" +#include "lbitfield_dynamic.h" +#include "lroom_manager.h" + +#define LMAN m_pManager + + + +//void LTrace::Trace_Prepare(LRoomManager &manager, const LCamera &cam, Lawn::LBitField_Dynamic &BF_SOBs, Lawn::LBitField_Dynamic &BF_DOBs, Lawn::LBitField_Dynamic &BF_Rooms, LVector &visible_SOBs, LVector &visible_DOBs, LVector &visible_Rooms) +void LTrace::Trace_Prepare(LRoomManager &manager, const LSource &cam, Lawn::LBitField_Dynamic &BF_SOBs, Lawn::LBitField_Dynamic &BF_Rooms, LVector &visible_SOBs, LVector &visible_Rooms) +{ + m_pManager = &manager; + m_pCamera = &cam; + + // default + m_TraceFlags = CULL_SOBS | CULL_DOBS | TOUCH_ROOMS | MAKE_ROOM_VISIBLE; + + m_pBF_SOBs = &BF_SOBs; +// m_pBF_DOBs = &BF_DOBs; + m_pBF_Rooms = &BF_Rooms; + m_pVisible_SOBs = &visible_SOBs; +// m_pVisible_DOBs = &visible_DOBs; + m_pVisible_Rooms = &visible_Rooms; +} + +void LTrace::CullSOBs(LRoom &room, const LVector &planes) +{ + // clip all objects in this room to the clipping planes + int last_sob = room.m_iFirstSOB + room.m_iNumSOBs; + for (int n=room.m_iFirstSOB; nm_SOBs[n]; + + //LPRINT_RUN(2, "sob " + itos(n) + " " + sob.GetSpatial()->get_name()); + + // already determined to be visible through another portal + if (m_pBF_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) + { + //LPRINT_RUN(2, "\tout of view"); + bShow = false; + break; + } + } + + if (bShow) + { + // sob is renderable and visible (not shadow only) + //LPRINT_RUN(2, "\tin view"); + m_pBF_SOBs->SetBit(n, true); + m_pVisible_SOBs->push_back(n); + } + + } // for through sobs + +} + +void LTrace::CullDOBs(LRoom &room, const LVector &planes) +{ + // NYI this isn't efficient, there may be more than 1 portal to the same room + + // cull DOBs + int nDOBs = room.m_DOBs.size(); + + 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 + +} + + +bool LTrace::Trace_Light(LRoomManager &manager, const LLight &light, eLightRun eRun) +{ + m_pManager = &manager; + + + LRoom * pRoom; + + // non area light + if (light.m_iArea == -1) + { + // can only deal with lights in rooms for now + if (light.m_Source.m_RoomID == -1) + { + WARN_PRINT_ONCE("LTrace::Trace_Light can only trace lights in rooms"); + return true; + } + + pRoom = manager.GetRoom(light.m_Source.m_RoomID); + if (!pRoom) + return true; + } + else + { + // area light + pRoom = 0; + } + + const LSource &cam = light.m_Source; + + unsigned int pool_member = manager.m_Pool.Request(); + assert (pool_member != -1); + + LVector &planes = manager.m_Pool.Get(pool_member); + planes.clear(); + + // we now need to trace either just DOBs (in the case of static lights) + // or SOBs and DOBs (in the case of dynamic lights) + LRoomManager::LLightRender &lr = manager.m_LightRender; + lr.m_BF_Temp_SOBs.Blank(); + lr.m_Temp_Visible_SOBs.clear(); + lr.m_BF_Temp_Visible_Rooms.Blank(); + lr.m_Temp_Visible_Rooms.clear(); + + bool bLightInView = true; + + switch (eRun) + { + // finding all shadow casters at runtime + case LR_ALL: + { + //Trace_Prepare(manager, cam, lr.m_BF_Temp_SOBs, manager.m_BF_visible_rooms, lr.m_Temp_Visible_SOBs, *manager.m_pCurr_VisibleRoomList); + Trace_Prepare(manager, cam, lr.m_BF_Temp_SOBs, lr.m_BF_Temp_Visible_Rooms, lr.m_Temp_Visible_SOBs, lr.m_Temp_Visible_Rooms); + + Trace_SetFlags(CULL_SOBS | CULL_DOBS | MAKE_ROOM_VISIBLE); + + // create subset planes of light frustum and camera frustum + bLightInView = manager.m_MainCamera.AddCameraLightPlanes(manager, cam, planes); + } + break; + // finding only visible rooms at runtime + case LR_ROOMS: + { + Trace_Prepare(manager, cam, lr.m_BF_Temp_SOBs, lr.m_BF_Temp_Visible_Rooms, lr.m_Temp_Visible_SOBs, lr.m_Temp_Visible_Rooms); + + // we ONLY want a list of rooms hit + Trace_SetFlags(MAKE_ROOM_VISIBLE); + } + break; + // finding all in preconversion + case LR_CONVERT: + { + Trace_Prepare(manager, cam, lr.m_BF_Temp_SOBs, lr.m_BF_Temp_Visible_Rooms, lr.m_Temp_Visible_SOBs, lr.m_Temp_Visible_Rooms); + + // we want sobs but not to touch rooms + m_TraceFlags = CULL_SOBS | MAKE_ROOM_VISIBLE; // | CULL_DOBS | TOUCH_ROOMS; + } + break; + } + + + if (bLightInView) + { + // non area light + if (pRoom) + { + Trace_Begin(*pRoom, planes); + } + else + { + // area light + + // area lights don't go through portals, e.g. coming from above like sunlight + // they instead have a predefined list of rooms governed by the area + m_TraceFlags |= DONT_TRACE_PORTALS; + + // new .. trace according to area, not affected rooms, as affected rooms has a limit + assert (light.m_iArea != -1); + const LArea &area = LMAN->m_Areas[light.m_iArea]; + + int last_room = area.m_iFirstRoom + area.m_iNumRooms; + + for (int r=area.m_iFirstRoom; rm_AreaRooms[r]; + LRoom * pRoom = manager.GetRoom(room_id); + + // should not happen, assert? + assert (pRoom); + + // trace as usual but don't go through the portals + Trace_Recursive(0, *pRoom, planes, 0); + } + +/* + // go through each affected room + for (int r=0; r &planes) const +{ + Plane p(m_pCamera->m_ptPos, -m_pCamera->m_ptDir); + planes.push_back(p); + + // this is kinda crappy, because ideally we'd want a cone, but instead we'll fake a frustum + Vector3 pts[4]; + + // assuming here that d is normalized! + const Vector3 &d = m_pCamera->m_ptDir; + const Vector3 &ptCam = m_pCamera->m_ptPos; + + assert (d.length_squared() < 1.1f); + assert (d.length_squared() > 0.9f); + + // spotlight has no 'up' vector, as it is regular shape around direction axis + // so we can use anything for side vector + + // this might balls up with a light pointing straight up + Vector3 ptSide = Vector3(0, 1, 0).cross(d); + + float l = ptSide.length(); + if (l < 0.1f) + { + // special case straight up, lets cross against something else + ptSide = d.cross(Vector3(1, 0, 0)); + l = ptSide.length(); + assert (l); + } + + // unitize side + ptSide *= 1.0 / l; + + Vector3 ptUp = ptSide.cross(d); + ptUp.normalize(); + + // now we've got the vecs, lets create some planes + + // spotlight spread definition (light.cpp, line 146) + //float size = Math::tan(Math::deg2rad(param[PARAM_SPOT_ANGLE])) * len; + + // this is the size at distance 1 .. it would be more efficient to calc distance at which sides were 1, but whatever... + float size = Math::tan(Math::deg2rad(m_pCamera->m_fSpread)); + + ptSide *= size; // or half size? not sure yet + ptUp *= -size; + + // pts will be bot left, top left, top right, bot right + Vector3 ptEx = ptCam + d; + + pts[0] = ptEx - ptSide - ptUp; + pts[1] = ptEx - ptSide + ptUp; + pts[2] = ptEx + ptSide + ptUp; + pts[3] = ptEx + ptSide - ptUp; + + Plane left(ptCam, pts[0], pts[1], COUNTERCLOCKWISE); + Plane top(ptCam, pts[1], pts[2], COUNTERCLOCKWISE); + Plane right(ptCam, pts[2], pts[3], COUNTERCLOCKWISE); + Plane bottom(ptCam, pts[3], pts[0], COUNTERCLOCKWISE); + + planes.push_back(left); + planes.push_back(top); + planes.push_back(right); + planes.push_back(bottom); + + // debug + if (LMAN->m_bDebugFrustums) + { + for (int n=0; n<4; n++) + { + LMAN->m_DebugFrustums.push_back(ptCam); + LMAN->m_DebugFrustums.push_back(pts[n]); + } + } +} + +void LTrace::Trace_Begin(LRoom &room, LVector &planes) +{ + int first_plane = 0; + + switch (m_pCamera->m_eType) + { + case LSource::ST_SPOTLIGHT: + { + // special cases of spotlight, add some extra planes to define the cone + AddSpotlightPlanes(planes); + } + break; + case LSource::ST_CAMERA: + first_plane = 1; + break; + default: + break; + } + + + LPRINT_RUN(2, "TRACE BEGIN"); + LPRINT_RUN(2, m_pCamera->MakeDebugString()); + + + Trace_Recursive(0, room, planes, first_plane); +} + +void LTrace::Trace_Recursive(int depth, LRoom &room, const LVector &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 '" + itos(room.m_RoomID) + " : " + room.get_name() + "' planes " + itos(planes.size()) + " portals " + itos(room.m_iNumPortals) ); + + // only handle one touch per frame so far (one portal into room) + //assert (manager.m_uiFrameCounter > m_uiFrameTouched); + + // first touch + DetectFirstTouch(room); + + if (m_TraceFlags & CULL_SOBS) + CullSOBs(room, planes); + + if (m_TraceFlags & CULL_DOBS) + CullDOBs(room, planes); + + // portals + if (m_TraceFlags & DONT_TRACE_PORTALS) + return; + + // look through portals + int nPortals = room.m_iNumPortals; + + for (int port_num=0; port_numm_Portals[port_id]; + + // have we already handled the room on this frame? + // get the room pointed to by the portal + LRoom * pLinkedRoom = &LMAN->Portal_GetLinkedRoom(port); + + + + // cull by portal angle to camera. + + // NEW! I've come up with a much better way of culling portals by direction to camera... + // instead of using dot product with a varying view direction, we simply find which side of the portal + // plane the camera is on! If it is behind, the portal can be seen through, if in front, it can't! :) + float dist_cam = port.m_Plane.distance_to(m_pCamera->m_ptPos); + LPRINT_RUN(2, "\tPORTAL " + itos (port_num) + " (" + itos(port_id) + ") " + port.get_name()); + if (dist_cam > 0.0f) + { + LPRINT_RUN(2, "\t\tCULLED (back facing)"); + continue; + } + + /* + // 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 - m_pCamera->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; + + // while clipping to the planes we maintain a list of partial planes, so we can add them to the + // recursive next iteration of planes to check + static LVector 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; lm_Pool.Request(); + if (uiPoolMem != -1) + { + // get a vector of planes from the pool + LVector &new_planes = LMAN->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; nm_ptPos, new_planes); + + + if (pLinkedRoom) + { + Trace_Recursive(depth+1, *pLinkedRoom, new_planes, 0); + //pLinkedRoom->DetermineVisibility_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 + LMAN->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 + +} + +void LTrace::DetectFirstTouch(LRoom &room) +{ + // mark if not reached yet on this trace + if (!m_pBF_Rooms->GetBit(room.m_RoomID)) + { + m_pBF_Rooms->SetBit(room.m_RoomID, true); + + if (m_TraceFlags & MAKE_ROOM_VISIBLE) + { + // keep track of which rooms are shown this trace + m_pVisible_Rooms->push_back(room.m_RoomID); + } + + // camera and light traces + if (m_TraceFlags & TOUCH_ROOMS) + { + if (room.m_uiFrameTouched < LMAN->m_uiFrameCounter) + FirstTouch(room); + } + } + +} + + +void LTrace::FirstTouch(LRoom &room) +{ + // set the frame counter + room.m_uiFrameTouched = LMAN->m_uiFrameCounter; + + // show this room and add to visible list of rooms + room.Room_MakeVisible(true); + +// m_pBF_Rooms->SetBit(room.m_RoomID, true); + + // keep track of which rooms are shown this frame +// m_pVisible_Rooms->push_back(room.m_RoomID); + + // hide all dobs + for (int n=0; n &visible_SOBs, LVector &visible_Rooms); +// void Trace_Prepare(LRoomManager &manager, const LCamera &cam, Lawn::LBitField_Dynamic &BF_SOBs, Lawn::LBitField_Dynamic &BF_DOBs, Lawn::LBitField_Dynamic &BF_Rooms, LVector &visible_SOBs, LVector &visible_DOBs, LVector &visible_Rooms); + + void Trace_SetFlags(unsigned int flags) {m_TraceFlags = flags;} + void Trace_Begin(LRoom &room, LVector &planes); + + // simpler method of doing a trace for lights, no need to call prepare and begin + bool Trace_Light(LRoomManager &manager, const LLight &light, eLightRun eRun); + +private: + void AddSpotlightPlanes(LVector &planes) const; + void Trace_Recursive(int depth, LRoom &room, const LVector &planes, int first_portal_plane); + + void CullSOBs(LRoom &room, const LVector &planes); + void CullDOBs(LRoom &room, const LVector &planes); + void FirstTouch(LRoom &room); + void DetectFirstTouch(LRoom &room); + + + LRoomManager * m_pManager; + const LSource * m_pCamera; + + Lawn::LBitField_Dynamic * m_pBF_SOBs; + //Lawn::LBitField_Dynamic * m_pBF_DOBs; + Lawn::LBitField_Dynamic * m_pBF_Rooms; + + LVector * m_pVisible_SOBs; + //LVector * m_pVisible_DOBs; + LVector * m_pVisible_Rooms; + + unsigned int m_TraceFlags; +}; diff --git a/lvector.h b/lvector.h index 530779c..de08f9b 100644 --- a/lvector.h +++ b/lvector.h @@ -163,6 +163,23 @@ public: } } + void insert(int i, const T &val) + { + m_Vec.insert(m_Vec.begin() + i, val); + m_iSize++; + } + + int find(const T &val) + { + for (int n=0; n