// 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, }, };