From 0c10ed45d037591ac8c67b5f248ceb44061ef9be Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Tue, 22 Oct 2019 20:44:17 +0100 Subject: [PATCH] Working with box rooms demo --- ldebug.h | 14 +- ldob.cpp | 7 + ldob.h | 2 + lhelper.cpp | 1126 +++++++++++++++++++++++++++++++++++++++++++ lhelper.h | 83 ++++ lportal_all.cpp | 2 + lroom.cpp | 51 +- lroom.h | 7 +- lroom_converter.cpp | 183 ++++--- lroom_converter.h | 12 +- lroom_manager.cpp | 479 +++++++++++++++--- lroom_manager.h | 49 +- lscene_saver.cpp | 33 ++ lscene_saver.h | 10 + 14 files changed, 1876 insertions(+), 182 deletions(-) create mode 100644 lhelper.cpp create mode 100644 lhelper.h create mode 100644 lscene_saver.cpp create mode 100644 lscene_saver.h diff --git a/ldebug.h b/ldebug.h index 8f5cd09..4a7fdf9 100644 --- a/ldebug.h +++ b/ldebug.h @@ -1,11 +1,15 @@ #pragma once +// 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= Lawn::LDebug::m_iLoggingLevel)\ diff --git a/ldob.cpp b/ldob.cpp index f3ce3bb..01c84c6 100644 --- a/ldob.cpp +++ b/ldob.cpp @@ -111,6 +111,13 @@ bool LSob::IsShadowCaster() const } +GeometryInstance * LSob::GetGI() const +{ + Object * pObj = ObjectDB::get_instance(m_ID); + GeometryInstance * pGI = Object::cast_to(pObj); + return pGI; +} + VisualInstance * LSob::GetVI() const { Object * pObj = ObjectDB::get_instance(m_ID); diff --git a/ldob.h b/ldob.h index fd767c5..a2cd876 100644 --- a/ldob.h +++ b/ldob.h @@ -28,6 +28,7 @@ #include "scene/3d/spatial.h" class VisualInstance; +class GeometryInstance; class Light; class LHidable @@ -50,6 +51,7 @@ class LSob : public LHidable public: Spatial * GetSpatial() const; VisualInstance * GetVI() const; + GeometryInstance * GetGI() const; //void Show(bool bShow); bool IsShadowCaster() const; diff --git a/lhelper.cpp b/lhelper.cpp new file mode 100644 index 0000000..f9168ed --- /dev/null +++ b/lhelper.cpp @@ -0,0 +1,1126 @@ +#include "lhelper.h" +#include "ldebug.h" +#include "scene/3d/baked_lightmap.h" +#include "scene/3d/mesh_instance.h" +#include "thirdparty/xatlas/xatlas.h" + +// for ::free +#include + + +String LHelper::LFace::ToString() const +{ + String sz; + + for (int c=0; c<3; c++) + { + sz += String(Variant(m_Pos[c])); + sz += ", "; + } + + sz += " norm : "; + for (int c=0; c<3; c++) + { + sz += String(Variant(m_Norm[c])); + sz += ", "; + } + + return sz; +} + + +bool LHelper::LVert::ApproxEqual(const LVert &o) const +{ + if (m_Pos != o.m_Pos) + return false; + if (m_Norm != o.m_Norm) + return false; + if (m_UV != o.m_UV) + return false; + if (m_UV2 != o.m_UV2) + return false; + + return true; +} + + +////////////////////////////////////////// +/* +bool LHelper::UnMergeSOBs(LRoomManager &manager, const PoolVector &uv2s) +{ + int uv_count = 0; + + // go through each sob mesh + for (int n=0; n(pGI); + if (!pMI) + continue; + +// if (UnMerge_SOB(*pMI, uv2s, uv_count) == false) +// return false; + } + + return true; +} +*/ + +// main function for getting merged uv2 back to sobs +bool LHelper::TransferUV2(const MeshInstance &mi_from, MeshInstance &mi_to) +{ + LMerged merged; + if (!FillMergedFromMesh(merged, mi_from)) + return false; + + return UnMerge_SOB(mi_to, merged); +} + + +bool LHelper::FillMergedFromMesh(LMerged &merged, const MeshInstance &mesh) +{ + Ref rmesh = mesh.get_mesh(); + Array arrays = rmesh->surface_get_arrays(0); + + + merged.m_Verts = arrays[VS::ARRAY_VERTEX]; + merged.m_Norms = arrays[VS::ARRAY_NORMAL]; + merged.m_UV2s = arrays[VS::ARRAY_TEX_UV2]; + merged.m_Inds = arrays[VS::ARRAY_INDEX]; + // PoolVector p_UV1s = arrays[VS::ARRAY_TEX_UV]; + + merged.m_nFaces = merged.m_Inds.size() / 3; + + if (merged.m_UV2s.size() == 0) + { + LWARN(5, "Merged mesh has no secondary UVs"); + return false; + } + + int miCount = 0; + for (int mf=0; mf(pGI); + if (!pMI) + continue; + + if (UnMerge_SOB(*pMI, merged) == false) + return false; + } + + + return true; +} + +int LHelper::DebugCountUVs(MeshInstance &mi) +{ + Ref rmesh = mi.get_mesh(); + Array arrays = rmesh->surface_get_arrays(0); + PoolVector verts = arrays[VS::ARRAY_VERTEX]; + PoolVector uv1s = arrays[VS::ARRAY_TEX_UV]; + PoolVector uv2s = arrays[VS::ARRAY_TEX_UV2]; + + LPRINT(5, "Lightmap num verts is " + itos (verts.size()) + "\tUV1s is " + itos (uv1s.size()) + "\tUV2s is " + itos(uv2s.size())); + + return uv2s.size(); +} + + +unsigned int LHelper::FindMatchingVertex(const PoolVector &uvs, const Vector2 &uv1) const +{ + // very slow and inefficient .. thanks xatlas! + for (int n=0; n 0.2f) + return false; + + + Vector3 pos_diff = b_pos - a_pos; + if (pos_diff.length_squared() > 0.1f) + return false; + + // make sure both are normalized + Vector3 na = a_norm;//.normalized(); + Vector3 nb = b_norm;//.normalized(); + + float norm_dot = na.dot(nb); + if (norm_dot < 0.95f) + return false; + + return true; +} + + +// -1 for no match, or 0 for 0 offset match, 1 for +1, 2 for +2 offset match... +int LHelper::DoFacesMatch(const LFace& sob_f, const LFace &m_face) const +{ + // match one + int offset = 0; + bool bMatch = false; + for (offset = 0; offset < 3; offset++) + { + if (DoFaceVertsApproxMatch(sob_f, m_face, 0, offset)) + { + bMatch = true; + break; + } + } + + // none found that match, most common scenario + if (!bMatch) + return -1; + + // debug +// String sz = "\t\tposs match sob : "; +// sz += sob_f.ToString(); +// sz += " merged : "; +// sz += m_face.ToString(); +// LPRINT(2, sz); + + + // does 2nd and third match? + int offset1 = (offset + 1) % 3; + if (!DoFaceVertsApproxMatch(sob_f, m_face, 1, offset1)) + return -1; + + int offset2 = (offset + 2) % 3; + if (!DoFaceVertsApproxMatch(sob_f, m_face, 2, offset2)) + return -1; + + return offset; +} + + +int LHelper::FindOrAddVert(LVector &uni_verts, const LVert &vert) const +{ + for (int n=0; n merged_verts, const PoolVector merged_norms, const PoolVector &merged_uv2s, const PoolVector &merged_inds, int &vert_count) +bool LHelper::UnMerge_SOB(MeshInstance &mi, LMerged &merged) +{ + //LPRINT(2, "UnMerge_SOB " + mi.get_name()); + + Ref rmesh = mi.get_mesh(); + Array arrays = rmesh->surface_get_arrays(0); + PoolVector verts = arrays[VS::ARRAY_VERTEX]; + PoolVector norms = arrays[VS::ARRAY_NORMAL]; + PoolVector uv1s = arrays[VS::ARRAY_TEX_UV]; + PoolVector inds = arrays[VS::ARRAY_INDEX]; + + // we need to get the vert positions and normals from local space to world space to match up with the + // world space coords in the merged mesh + PoolVector world_verts; + PoolVector world_norms; + Transform trans = mi.get_global_transform(); + Transform_Verts(verts, world_verts, trans); + Transform_Norms(norms, world_norms, trans); + + + // these are the uvs to be filled in the sob + PoolVector uv2s; + uv2s.resize(verts.size()); + + // for each face in the SOB, attempt to find matching face in the merged mesh + int nFaces = inds.size() / 3; + int iCount = 0; + + int nMergedFaces = merged.m_nFaces; + + + iCount = 0; + + // the number of unique verts in the UV2 mapped mesh may be HIGHER + // than the original mesh, because verts with same pos / norm / uv may now have + // different UV2. So we will be recreating the entire + // mesh data with a new set of unique verts + LVector UniqueVerts; + PoolVector UniqueIndices; + + for (int f=0; f merged_uv2s.size()) +// { +// LWARN(5, "Lightmap and SOBs out of sync, num verts in mesh " + itos(verts.size()) + " total uvs " + itos(merged_uv2s.size())); +// return false; +// } + + // uv2 sub array +// PoolVector uv2s; +// for (int n=0; n unique_poss; + PoolVector unique_norms; + PoolVector unique_uv1s; + PoolVector unique_uv2s; + + for (int n=0; n norms = arrays[VS::ARRAY_NORMAL]; +// PoolVector uv1s = arrays[VS::ARRAY_TEX_UV]; +// PoolVector inds = arrays[VS::ARRAY_INDEX]; + + + Ref mat = mi.get_surface_material(0); + + + Ref am_new; + am_new.instance(); + Array arr; + arr.resize(Mesh::ARRAY_MAX); +// arr[Mesh::ARRAY_VERTEX] = verts; +// arr[Mesh::ARRAY_NORMAL] = norms; +// arr[Mesh::ARRAY_INDEX] = inds; +// arr[Mesh::ARRAY_TEX_UV] = uv1s; +// arr[Mesh::ARRAY_TEX_UV2] = uv2s; + arr[Mesh::ARRAY_VERTEX] = unique_poss; + arr[Mesh::ARRAY_NORMAL] = unique_norms; + arr[Mesh::ARRAY_INDEX] = UniqueIndices; + arr[Mesh::ARRAY_TEX_UV] = unique_uv1s; + arr[Mesh::ARRAY_TEX_UV2] = unique_uv2s; + + am_new->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr); + +// am_new->surface_set_material(0, mat); + + // hopefully the old mesh will be ref count freed? ??? + mi.set_mesh(am_new); + + mi.set_surface_material(0, mat); + + return true; +} + + +//bool LHelper::CreateLightmapProxy(LRoomManager &manager, BakedLightmap &baked_lightmap) +MeshInstance * LHelper::CreateLightmapProxy(LRoomManager &manager) +{ + // create a temporary mesh instance to merge to, lightmap etc + MeshInstance * pMerged = memnew(MeshInstance); + pMerged->set_name("lightmap_proxy"); + +// baked_lightmap.add_child(pMerged); + + manager.get_parent()->add_child(pMerged); + + bool res = true; + + // create uv2s is done in merge + if (!MergeSOBs(manager, pMerged)) + { + res = false; + goto finish; + } + + /* + if (!BakeLightmap(baked_lightmap, pMerged)) + { + res = false; + goto finish; + } + */ + + // unmerge the mesh back to the sobs + if (!UnMergeSOBs(manager, pMerged)) + { + res = false; + goto finish; + } + +finish: + // finished .. remove the merged child and delete +// pMerged->queue_delete(); + + if (res) + return pMerged; + + // failed + pMerged->queue_delete(); + return 0; +// return res; +} + + +bool LHelper::MergeSOBs(LRoomManager &manager, MeshInstance * pMerged) +{ + PoolVector verts; + PoolVector normals; + PoolVector inds; +// PoolVector uv2s; + + + // go through each sob mesh + for (int n=0; n(pGI); + if (!pMI) + continue; + + // to get the transform, the node has to be in the tree, so temporarily show if hidden + bool bShowing = sob.m_bShow; + sob.Show(true); + + Merge_MI(*pMI, verts, normals, inds); + + sob.Show(bShowing); + } + + + assert (pMerged); + //MeshInstance * pMI = memnew(MeshInstance); + //pMI->set_name("Merged"); + //add_child(pMI); + //Ref rmesh = pMI->get_mesh(); + + LPRINT(5, "Merging, num verts is " + itos(verts.size())); + + // lightmap unwrap + //LightmapUnwrap(verts, normals, inds, uv2s); + + // unmerge the original meshes (write the uvs2 back) + + + Ref am; + am.instance(); + Array arr; + arr.resize(Mesh::ARRAY_MAX); + arr[Mesh::ARRAY_VERTEX] = verts; + arr[Mesh::ARRAY_NORMAL] = normals; + arr[Mesh::ARRAY_INDEX] = inds; +// arr[Mesh::ARRAY_TEX_UV2] = uv2s; + + // bug fix. The lightmapping code removes duplicate vertices, which we DONT want + // as it makes the merged mesh get out of sync with the original meshes. + // To prevent this we will create a dummy set of UV1s that are unique. +// PoolVector dummy_uvs; +// for (int n=0; nadd_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT); + + LightmapUnwrap(am, pMerged->get_global_transform()); + + // duplicate the UV2 to uv1 just in case they are needed + arr[Mesh::ARRAY_TEX_UV] = arr[Mesh::ARRAY_TEX_UV2]; + + pMerged->set_mesh(am); + + //DebugCountUVs(*pMerged); + + + // check the num of uvs match the number of verts + //int numUVs = DebugCountUVs(*pMerged); + + // set mesh to use in baked lighting + pMerged->set_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT, true); + + return true; + + // bake +// Node * pBLN = pMerged->get_parent()->find_node("BakedLightmap"); +// BakedLightmap * pBL = Object::cast_to(pBLN); + +// if (!pBL) +// { +// LWARN(5, "BakedLightmap not found"); +// return false; +// } + +// { +// BakedLightmap::BakeError err; +// err = pBL->bake(pMerged); + +// switch (err) +// { +// case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: +// LWARN(5, "Can't determine a save path for lightmap images.\nSave your scene (for images to be saved in the same dir), or pick a save path from the BakedLightmap properties."); +// break; +// case BakedLightmap::BAKE_ERROR_NO_MESHES: +// LWARN(5, "No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."); +// break; +// case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE: +// LWARN(5, "Failed creating lightmap images, make sure path is writable."); +// break; +// default: { +// } +// } +// } + +// return true; +} + +bool LHelper::LightmapUnwrap(Ref am, const Transform &trans) +{ +// ArrayMesh +// Ref rmesh = pMesh->get_mesh(); + + // we can add the UV2 coords from here + Error err = am->lightmap_unwrap(trans); + if (err != OK) { + LWARN(5, "UV Unwrap failed, mesh may not be manifold?"); + return false; + } + + return true; +} + + +void LHelper::SetOwnerRecursive(Node * pNode, Node * pOwner) +{ + pNode->set_owner(pOwner); + + for (int n=0; nget_child_count(); n++) + { + SetOwnerRecursive(pNode->get_child(n), pOwner); + } +} + + +bool LHelper::BakeLightmap(BakedLightmap &baked_lightmap, MeshInstance * pMerged) +{ + // bake + + BakedLightmap::BakeError err; +// err = baked_lightmap.bake(pMerged); + Node * pStartNode = baked_lightmap.get_parent(); + + // baked lightmap only picks up meshes and traverses if the owner is set.... (!) + SetOwnerRecursive(pStartNode, pStartNode); + + err = baked_lightmap.bake(pStartNode); + + switch (err) + { + case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: + { + LWARN(5, "Can't determine a save path for lightmap images.\nSave your scene (for images to be saved in the same dir), or pick a save path from the BakedLightmap properties."); + return false; + } + break; + case BakedLightmap::BAKE_ERROR_NO_MESHES: + { + LWARN(5, "No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."); + return false; + } + break; + case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE: + { + LWARN(5, "Failed creating lightmap images, make sure path is writable."); + return false; + } + break; + default: { + } + } + + return true; +} + + +void LHelper::Transform_Verts(const PoolVector &ptsLocal, PoolVector &ptsWorld, const Transform &tr) const +{ + for (int n=0; n &normsLocal, PoolVector &normsWorld, const Transform &tr) const +{ + for (int n=0; n &verts, PoolVector &norms, PoolVector &inds) +{ + // some godot jiggery pokery to get the mesh verts in local space + Ref rmesh = mi.get_mesh(); + Array arrays = rmesh->surface_get_arrays(0); + PoolVector p_vertices = arrays[VS::ARRAY_VERTEX]; + PoolVector p_normals = arrays[VS::ARRAY_NORMAL]; + PoolVector p_indices = arrays[VS::ARRAY_INDEX]; + //PoolVector::Read ir = mesh_indices.read(); + + // the first index of this mesh is offset from the verts we already have stored in the merged mesh + int first_index = verts.size(); + +// LPRINT(2, "Merge MI : " + mi.get_name() + "\tFirstVert : " + itos(first_index) + "\tNumUVs : " + itos(p_vertices.size())); + + // transform verts to world space + Transform trans = mi.get_global_transform(); + + for (int n=0; n &p_verts, const PoolVector &p_normals, const PoolVector &p_inds, PoolVector &r_uvs) +{ + ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); + + assert (p_verts.size() == p_normals.size()); + + Vector verts; + for (int n=0; n normals; + for (int n=0; n inds; + for (int n=0; n face_mats; + for (int n=0; nwidth; +// *r_size_hint_y = atlas->height; + +// float w = *r_size_hint_x; +// float h = *r_size_hint_y; + +// if (w == 0 || h == 0) { +// return false; //could not bake because there is no area +// } + +// const xatlas::Mesh &output = atlas->meshes[0]; + +// *r_vertex = (int *)malloc(sizeof(int) * output.vertexCount); +// *r_verts = (float *)malloc(sizeof(xatlas::Vertex) * output.vertexCount); +// *r_index = (int *)malloc(sizeof(int) * output.indexCount); + +// float max_x = 0; +// float max_y = 0; +// for (uint32_t i = 0; i < output.vertexCount; i++) { +// (*r_vertex)[i] = output.vertexArray[i].xref; +// (*r_uv)[i * 2 + 0] = output.vertexArray[i].uv[0] / w; +// (*r_uv)[i * 2 + 1] = output.vertexArray[i].uv[1] / h; +// max_x = MAX(max_x, output.vertexArray[i].uv[0]); +// max_y = MAX(max_y, output.vertexArray[i].uv[1]); +// } + +// printf("Final texture size: %f,%f - max %f,%f\n", w, h, max_x, max_y); +// *r_vertex_count = output.vertexCount; + +// for (uint32_t i = 0; i < output.indexCount; i++) { +// (*r_index)[i] = output.indexArray[i]; +// } + +// *r_index_count = output.indexCount; + +// xatlas::Destroy(atlas); +// printf("Done\n"); +// return true; +//} + + + +/* +//Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texel_size) { +Error LHelper::lightmap_unwrap(ArrayMesh &am, const Transform &p_base_transform, float p_texel_size) +{ + + ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(am.blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); + + Vector vertices; + Vector normals; + Vector indices; + Vector face_materials; + Vector uv; + Vector > uv_index; + + Vector surfaces; + for (int i = 0; i < get_surface_count(); i++) { + ArrayMeshLightmapSurface s; + s.primitive = surface_get_primitive_type(i); + + ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); + s.format = surface_get_format(i); + ERR_FAIL_COND_V_MSG(!(s.format & ARRAY_FORMAT_NORMAL), ERR_UNAVAILABLE, "Normals are required for lightmap unwrap."); + + Array arrays = surface_get_arrays(i); + s.material = surface_get_material(i); + s.vertices = SurfaceTool::create_vertex_array_from_triangle_arrays(arrays); + + PoolVector rvertices = arrays[Mesh::ARRAY_VERTEX]; + int vc = rvertices.size(); + PoolVector::Read r = rvertices.read(); + + PoolVector rnormals = arrays[Mesh::ARRAY_NORMAL]; + PoolVector::Read rn = rnormals.read(); + + int vertex_ofs = vertices.size() / 3; + + vertices.resize((vertex_ofs + vc) * 3); + normals.resize((vertex_ofs + vc) * 3); + uv_index.resize(vertex_ofs + vc); + + for (int j = 0; j < vc; j++) { + + Vector3 v = p_base_transform.xform(r[j]); + Vector3 n = p_base_transform.basis.xform(rn[j]).normalized(); + + vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; + vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; + vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; + normals.write[(j + vertex_ofs) * 3 + 0] = n.x; + normals.write[(j + vertex_ofs) * 3 + 1] = n.y; + normals.write[(j + vertex_ofs) * 3 + 2] = n.z; + uv_index.write[j + vertex_ofs] = Pair(i, j); + } + + PoolVector rindices = arrays[Mesh::ARRAY_INDEX]; + int ic = rindices.size(); + + if (ic == 0) { + + for (int j = 0; j < vc / 3; j++) { + if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) + continue; + + indices.push_back(vertex_ofs + j * 3 + 0); + indices.push_back(vertex_ofs + j * 3 + 1); + indices.push_back(vertex_ofs + j * 3 + 2); + face_materials.push_back(i); + } + + } else { + PoolVector::Read ri = rindices.read(); + + for (int j = 0; j < ic / 3; j++) { + if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) + continue; + indices.push_back(vertex_ofs + ri[j * 3 + 0]); + indices.push_back(vertex_ofs + ri[j * 3 + 1]); + indices.push_back(vertex_ofs + ri[j * 3 + 2]); + face_materials.push_back(i); + } + } + + surfaces.push_back(s); + } + + //unwrap + + float *gen_uvs; + int *gen_vertices; + int *gen_indices; + int gen_vertex_count; + int gen_index_count; + int size_x; + int size_y; + + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), face_materials.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + + if (!ok) { + return ERR_CANT_CREATE; + } + + //remove surfaces + while (get_surface_count()) { + surface_remove(0); + } + + //create surfacetools for each surface.. + Vector > surfaces_tools; + + for (int i = 0; i < surfaces.size(); i++) { + Ref st; + st.instance(); + st->begin(Mesh::PRIMITIVE_TRIANGLES); + st->set_material(surfaces[i].material); + surfaces_tools.push_back(st); //stay there + } + + print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); + //go through all indices + for (int i = 0; i < gen_index_count; i += 3) { + + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_index.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_index.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_index.size(), ERR_BUG); + + ERR_FAIL_COND_V(uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 1]]].first || uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); + + int surface = uv_index[gen_vertices[gen_indices[i + 0]]].first; + + for (int j = 0; j < 3; j++) { + + SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; + + if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { + surfaces_tools.write[surface]->add_color(v.color); + } + if (surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { + surfaces_tools.write[surface]->add_uv(v.uv); + } + if (surfaces[surface].format & ARRAY_FORMAT_NORMAL) { + surfaces_tools.write[surface]->add_normal(v.normal); + } + if (surfaces[surface].format & ARRAY_FORMAT_TANGENT) { + Plane t; + t.normal = v.tangent; + t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; + surfaces_tools.write[surface]->add_tangent(t); + } + if (surfaces[surface].format & ARRAY_FORMAT_BONES) { + surfaces_tools.write[surface]->add_bones(v.bones); + } + if (surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { + surfaces_tools.write[surface]->add_weights(v.weights); + } + + Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); + surfaces_tools.write[surface]->add_uv2(uv2); + + surfaces_tools.write[surface]->add_vertex(v.vertex); + } + } + + //free stuff + ::free(gen_vertices); + ::free(gen_indices); + ::free(gen_uvs); + + //generate surfaces + + for (int i = 0; i < surfaces_tools.size(); i++) { + surfaces_tools.write[i]->index(); + surfaces_tools.write[i]->commit(Ref((ArrayMesh *)this), surfaces[i].format); + } + + set_lightmap_size_hint(Size2(size_x, size_y)); + + return OK; +} +*/ diff --git a/lhelper.h b/lhelper.h new file mode 100644 index 0000000..4ae7b87 --- /dev/null +++ b/lhelper.h @@ -0,0 +1,83 @@ +#pragma once + +#include "lroom_manager.h" + +class LHelper +{ +public: + struct LFace + { + Vector3 m_Pos[3]; + Vector3 m_Norm[3]; + int m_index[3]; + + String ToString() const; + }; + + // unique vert + struct LVert + { + Vector3 m_Pos; + Vector3 m_Norm; + Vector2 m_UV; + Vector2 m_UV2; + + bool ApproxEqual(const LVert &o) const; + }; + + // the merged mesh data to be passed for unmerging meshes + struct LMerged + { + PoolVector m_Verts; + PoolVector m_Norms; + PoolVector m_UV2s; + PoolVector m_Inds; + + int m_nFaces; + + // precreate LFaces + LVector m_LFaces; + }; + + // one function to do the whole internal workflow + MeshInstance * CreateLightmapProxy(LRoomManager &manager); + + // for lightmapping + bool MergeSOBs(LRoomManager &manager, MeshInstance * pMerged); + + // take the UV2 coords from the merged mesh and attach these to the SOB meshes + bool UnMergeSOBs(LRoomManager &manager, MeshInstance * pMerged); + +// bool UnMergeSOBs(LRoomManager &manager, const PoolVector &uv2s); + + // main function for getting merged uv2 back to sobs + bool TransferUV2(const MeshInstance &mi_from, MeshInstance &mi_to); + +private: + void Merge_MI(const MeshInstance &mi, PoolVector &verts, PoolVector &norms, PoolVector &inds); + + bool UnMerge_SOB(MeshInstance &mi, LMerged &merged); + + unsigned int FindMatchingVertex(const PoolVector &uvs, const Vector2 &uv1) const; + + bool BakeLightmap(BakedLightmap &baked_lightmap, MeshInstance * pMerged); + + bool LightmapUnwrap(Ref am, const Transform &trans); +// bool LightmapUnwrap(const PoolVector &p_verts, const PoolVector &p_normals, const PoolVector &p_inds, PoolVector &r_uvs); + //Error lightmap_unwrap(ArrayMesh &am, const Transform &p_base_transform, float p_texel_size); + int DebugCountUVs(MeshInstance &mi); + + void Transform_Verts(const PoolVector &ptsLocal, PoolVector &ptsWorld, const Transform &tr) const; + void Transform_Norms(const PoolVector &normsLocal, PoolVector &normsWorld, const Transform &tr) const; + + int DoFacesMatch(const LFace& sob_f, const LFace &m_face) const; + bool DoFaceVertsApproxMatch(const LFace& sob_f, const LFace &m_face, int c0, int c1) const; + bool DoPosNormsApproxMatch(const Vector3 &a_pos, const Vector3 &a_norm, const Vector3 &b_pos, const Vector3 &b_norm) const; + + int FindOrAddVert(LVector &uni_verts, const LVert &vert) const; + + void SetOwnerRecursive(Node * pNode, Node * pOwner); + + bool FillMergedFromMesh(LMerged &merged, const MeshInstance &mesh); +// bool xatlas_mesh_lightmap_unwrap(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); +}; diff --git a/lportal_all.cpp b/lportal_all.cpp index fe22c7a..273b95a 100644 --- a/lportal_all.cpp +++ b/lportal_all.cpp @@ -9,3 +9,5 @@ #include "ldob.cpp" #include "lbound.cpp" #include "lbitfield_dynamic.cpp" +#include "lhelper.cpp" +#include "lscene_saver.cpp" diff --git a/lroom.cpp b/lroom.cpp index c1ab50a..f847e04 100644 --- a/lroom.cpp +++ b/lroom.cpp @@ -58,9 +58,6 @@ Spatial * LRoom::GetGodotRoom() const void LRoom::DOB_Add(const LDob &dob) { -// LDob dob; -// dob.m_ID = pDOB->get_instance_id(); - m_DOBs.push_back(dob); } @@ -314,6 +311,23 @@ void LRoom::FinalizeVisibility(LRoomManager &manager) } } +// call when releasing a level, this should unregister all dobs within all rooms +void LRoom::Release(LRoomManager &manager) +{ + for (int n=0; n &planes, int portalID_from) +void LRoom::DetermineVisibility_Recursive(LRoomManager &manager, int depth, const LCamera &cam, const LVector &planes, int first_portal_plane) { // prevent too much depth if (depth > 8) @@ -567,12 +581,19 @@ void LRoom::DetermineVisibility_Recursive(LRoomManager &manager, int depth, cons // 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 - - for (int l=1; l &new_planes = manager.m_Pool.Get(uiPoolMem); + new_planes.clear(); - // copy the existing planes - new_planes.copy_from(planes); + // NEW!! if portal is totally inside the planes, don't copy the old planes + if (overall_res != LPortal::eClipResult::CLIP_INSIDE) + { + // copy the existing planes + //new_planes.copy_from(planes); + + // new .. only copy the partial planes that the portal cuts through + for (int n=0; nDetermineVisibility_Recursive(manager, depth + 1, cam, new_planes, port_id); + pLinkedRoom->DetermineVisibility_Recursive(manager, depth + 1, cam, new_planes, 0); // for debugging need to reset tab depth Lawn::LDebug::m_iTabDepth = depth; } diff --git a/lroom.h b/lroom.h index bb64b42..abb8e31 100644 --- a/lroom.h +++ b/lroom.h @@ -100,16 +100,15 @@ 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 portalID_from = -1); + void DetermineVisibility_Recursive(LRoomManager &manager, int depth, const LCamera &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 void Room_MakeVisible(bool bVisible); - // hide godot room and all linked dobs - // USED AT RUNTIME -// void Hide_All(); + // call when releasing a level, this should unregister all dobs within all rooms + void Release(LRoomManager &manager); // show godot room and all linked dobs and all sobs void Debug_ShowAll(bool bActive); diff --git a/lroom_converter.cpp b/lroom_converter.cpp index 716380c..66770d3 100644 --- a/lroom_converter.cpp +++ b/lroom_converter.cpp @@ -29,17 +29,47 @@ // save typing, I am lazy #define LMAN m_pManager +#define LROOMLIST m_pRoomList -void LRoomConverter::Convert(LRoomManager &manager) +void LRoomConverter::Convert(LRoomManager &manager, bool bVerbose, bool bPreparationRun, bool bDeleteLights) { + m_bFinalRun = (bPreparationRun == false); + + m_bDeleteLights = bDeleteLights; + // This just is simply used to set how much debugging output .. more during conversion, less during running // except when requested by explicitly clearing this flag. - Lawn::LDebug::m_bRunning = false; - LPRINT(5, "running convert"); + 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; nShowAll(false); // temp rooms no longer needed m_TempRooms.clear(true); @@ -97,9 +127,9 @@ void LRoomConverter::Convert_Rooms() // first find all room empties and convert to LRooms int count = 0; - for (int n=0; nget_child_count(); n++) + for (int n=0; nget_child_count(); n++) { - Node * pChild = LMAN->get_child(n); + Node * pChild = LROOMLIST->get_child(n); if (!Node_IsRoom(pChild)) continue; @@ -188,7 +218,10 @@ void LRoomConverter::Convert_Room_FindObjects_Recursive(Node * pParent, LRoom &l LRoom_PushBackSOB(lroom, sob); // take away layer 0 from the sob, so it can be culled effectively - pVI->set_layer_mask(0); + if (m_bFinalRun) + { + pVI->set_layer_mask(0); + } } else { @@ -339,23 +372,23 @@ bool LRoomConverter::Convert_Bound(LRoom &lroom, MeshInstance * pMI) } // hide all in preparation for first frame -void LRoomConverter::Convert_HideAll() -{ - for (int n=0; nm_SOBs.size(); n++) - { - LSob &sob = LMAN->m_SOBs[n]; - sob.Show(false); - } +//void LRoomConverter::Convert_HideAll() +//{ +// for (int n=0; nm_SOBs.size(); n++) +// { +// LSob &sob = LMAN->m_SOBs[n]; +// sob.Show(false); +// } - // hide all lights that are non global - for (int n=0; nm_Lights.size(); n++) - { - LLight &light = LMAN->m_Lights[n]; - if (!light.IsGlobal()) - light.Show(false); - } +// // hide all lights that are non global +// for (int n=0; nm_Lights.size(); n++) +// { +// LLight &light = LMAN->m_Lights[n]; +// if (!light.IsGlobal()) +// light.Show(false); +// } -} +//} void LRoomConverter::Convert_Lights() { @@ -641,6 +674,7 @@ void LRoomConverter::Convert_Bounds() // delete the mesh pGRoom->remove_child(pChild); pChild->queue_delete(); + break; } } @@ -681,12 +715,12 @@ void LRoomConverter::Convert_Portals() int LRoomConverter::CountRooms() { - int nChildren = LMAN->get_child_count(); + int nChildren = LROOMLIST->get_child_count(); int count = 0; for (int n=0; nget_child(n))) + if (Node_IsRoom(LROOMLIST->get_child(n))) count++; } @@ -989,32 +1023,36 @@ void LRoomConverter::LRoom_DetectPortalMeshes(LRoom &lroom, LTempRoom &troom) } } - // we need an enclosing while loop because we might be deleting children and mucking up the iterator - bool bDetectedOne = true; - - while (bDetectedOne) + // delete portal meshes + if (m_bFinalRun) +// if (true) { - bDetectedOne = false; + // we need an enclosing while loop because we might be deleting children and mucking up the iterator + bool bDetectedOne = true; - for (int n=0; nget_child_count(); n++) + while (bDetectedOne) { - Node * pChild = pGRoom->get_child(n); + bDetectedOne = false; - if (Node_IsPortal(pChild)) + for (int n=0; nget_child_count(); n++) { - // delete the original child, as it is no longer needed at runtime (except maybe for debugging .. NYI?) - // pMeshInstance->hide(); - pChild->get_parent()->remove_child(pChild); - pChild->queue_delete(); + Node * pChild = pGRoom->get_child(n); - bDetectedOne = true; - } + if (Node_IsPortal(pChild)) + { + // delete the original child, as it is no longer needed at runtime (except maybe for debugging .. NYI?) + // pMeshInstance->hide(); + pChild->get_parent()->remove_child(pChild); + pChild->queue_delete(); + bDetectedOne = true; + } - if (bDetectedOne) - break; - } // for loop + if (bDetectedOne) + break; + } // for loop - } // while + } // while + } // if we want to delete portal meshes } @@ -1057,63 +1095,17 @@ void LRoomConverter::LRoom_DetectedLight(LRoom &lroom, Node * pNode) Light * pLight = Object::cast_to(pNode); assert (pLight); - LMAN->LightCreate(pLight, lroom.m_RoomID); - /* - // create new light - LLight l; - l.SetDefaults(); - l.m_GodotID = pLight->get_instance_id(); - // 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_fMaxDist = pLight->get_param(Light::PARAM_SHADOW_MAX_DISTANCE); - - // source room ID - l.m_RoomID = lroom.m_RoomID; - - bool bOK = false; - - // what kind of light? - SpotLight * pSL = Object::cast_to(pNode); - if (pSL) + if (m_bDeleteLights) { - LPRINT(2, "\tSPOTLIGHT detected " + pNode->get_name()); - l.m_eType = LLight::LT_SPOTLIGHT; - l.m_fSpread = pSL->get_param(Light::PARAM_SPOT_ANGLE); - - bOK = true; + LPRINT(2, "Deleting Light : " + pLight->get_name()); + // delete light now we are using lightmaps for test + pLight->queue_delete(); } - - OmniLight * pOL = Object::cast_to(pNode); - if (pOL) + else { - LPRINT(2, "\tOMNILIGHT detected " + pNode->get_name()); - l.m_eType = LLight::LT_OMNI; - bOK = true; + LPRINT(2, "Detected Light : " + pLight->get_name()); + LMAN->LightCreate(pLight, lroom.m_RoomID); } - - DirectionalLight * pDL = Object::cast_to(pNode); - if (pDL) - { - LPRINT(2, "\tDIRECTIONALLIGHT detected " + pNode->get_name()); - l.m_eType = LLight::LT_DIRECTIONAL; - bOK = true; - } - - // don't add if not recognised - if (!bOK) - { - LPRINT(2, "\tLIGHT type unrecognised " + pNode->get_name()); - return; - } - - - // turn the local light off to start with - pLight->hide(); - - LMAN->m_Lights.push_back(l); - */ } // found a portal mesh! create a matching LPortal @@ -1135,6 +1127,7 @@ void LRoomConverter::LRoom_DetectedPortalMesh(LRoom &lroom, LTempRoom &troom, Me Array arrays = rmesh->surface_get_arrays(0); PoolVector p_vertices = arrays[VS::ARRAY_VERTEX]; + // create a new LPortal to fill with this wonderful info LPortal &lport = *troom.m_Portals.request(); lport.m_szName = szLinkRoom; diff --git a/lroom_converter.h b/lroom_converter.h index 977d0ce..07fa207 100644 --- a/lroom_converter.h +++ b/lroom_converter.h @@ -81,7 +81,7 @@ public: }; // this function calls everything else in the converter - void Convert(LRoomManager &manager); + void Convert(LRoomManager &manager, bool bVerbose, bool bPreparationRun, bool bDeleteLights); private: int CountRooms(); @@ -96,7 +96,7 @@ private: bool Convert_Bound(LRoom &lroom, MeshInstance * pMI); void Convert_ShadowCasters(); void Convert_Lights(); - void Convert_HideAll(); +// void Convert_HideAll(); void LRoom_DetectPortalMeshes(LRoom &lroom, LTempRoom &troom); @@ -133,9 +133,17 @@ private: + // set up on entry LRoomManager * m_pManager; + Spatial * m_pRoomList; // room list pointed to by the manager nodepath + LVector m_TempRooms; bool Bound_AddPlaneIfUnique(LVector &planes, const Plane &p); + + // whether we are preparing the level, or doing a final run, + // in which case we should delete lights and set vis flags + bool m_bFinalRun; + bool m_bDeleteLights; }; diff --git a/lroom_manager.cpp b/lroom_manager.cpp index d0d6e74..78eaa5d 100644 --- a/lroom_manager.cpp +++ b/lroom_manager.cpp @@ -26,12 +26,27 @@ #include "ldebug.h" #include "scene/3d/immediate_geometry.h" #include "scene/3d/light.h" +#include "scene/3d/baked_lightmap.h" #include "lroom.h" +#include "lhelper.h" +#include "lscene_saver.h" + +#define LROOMLIST m_pRoomList +#define CHECK_ROOM_LIST if (!CheckRoomList())\ +{\ +WARN_PRINT_ONCE("rooms is unset");\ +return false;\ +} + LRoomManager::LRoomManager() { m_ID_camera = 0; m_ID_DebugPlanes = 0; + m_ID_DebugBounds = 0; + m_ID_DebugLights = 0; + m_ID_RoomList = 0; + m_uiFrameCounter = 0; m_iLoggingLevel = 2; m_bActive = true; @@ -44,6 +59,13 @@ LRoomManager::LRoomManager() m_bDebugPlanes = false; m_bDebugBounds = false; m_bDebugLights = false; + + m_pRoomList = 0; + + if (!Engine::get_singleton()->is_editor_hint()) + { + CreateDebug(); + } } int LRoomManager::FindClosestRoom(const Vector3 &pt) const @@ -146,9 +168,17 @@ int LRoomManager::Obj_GetRoomNum(Node * pNode) const LRoom * LRoomManager::GetRoomFromDOB(Node * pNode) { int iRoom = Obj_GetRoomNum(pNode); - if (iRoom == -1) + if (iRoom < 0) { - WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : metadata is empty"); + if (iRoom == -1) + { + WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : metadata is empty"); + } + + if (iRoom == -2) + { + WARN_PRINT_ONCE("LRoomManager::GetRoomFromDOB : are you updating an unregistered DOB?"); + } return 0; } @@ -164,6 +194,8 @@ LRoom * LRoomManager::GetRoomFromDOB(Node * pNode) // register but let LPortal know which room the dob should start in bool LRoomManager::dob_register_hint(Node * pDOB, float radius, Node * pRoom) { + CHECK_ROOM_LIST + if (!pDOB) { WARN_PRINT_ONCE("dob_register_hint : pDOB is NULL"); @@ -198,6 +230,7 @@ void LRoomManager::CreateDebug() ImmediateGeometry * p = memnew(ImmediateGeometry); p->set_name("debug_planes"); add_child(p); + move_child(p, get_child_count()-1); m_ID_DebugPlanes = p->get_instance_id(); @@ -217,6 +250,7 @@ void LRoomManager::CreateDebug() ImmediateGeometry * b = memnew(ImmediateGeometry); b->set_name("debug_bounds"); add_child(b); + move_child(b, get_child_count()-1); m_ID_DebugBounds = b->get_instance_id(); m_mat_Debug_Bounds = Ref(memnew(SpatialMaterial)); //m_mat_Debug_Bounds->set_flag(SpatialMaterial::FLAG_UNSHADED, true); @@ -230,6 +264,7 @@ void LRoomManager::CreateDebug() ImmediateGeometry * b = memnew(ImmediateGeometry); b->set_name("debug_lights"); add_child(b); + move_child(b, get_child_count()-1); m_ID_DebugLights = b->get_instance_id(); b->set_material_override(m_mat_Debug_Bounds); //b->hide(); @@ -297,6 +332,8 @@ bool LRoomManager::DobRegister(Spatial * pDOB, float radius, int iRoom) bool LRoomManager::dob_register(Node * pDOB, float radius) { + CHECK_ROOM_LIST + if (!pDOB) { WARN_PRINT_ONCE("dob_register : pDOB is NULL"); @@ -341,6 +378,11 @@ int LRoomManager::dob_update(Node * pDOB) // get dob data to move to new room unsigned int dob_id = pRoom->DOB_Find(pDOB); +// if (dob_id == -1) +// { +// WARN_PRINT_ONCE("DOB is not found in room"); +// return -1; +// } assert (dob_id != -1); // copy across data before removing @@ -366,6 +408,8 @@ int LRoomManager::dob_update(Node * pDOB) bool LRoomManager::dob_teleport_hint(Node * pDOB, Node * pRoom) { + CHECK_ROOM_LIST + if (!pDOB) { WARN_PRINT_ONCE("dob_teleport_hint : pDOB is NULL"); @@ -439,6 +483,8 @@ bool LRoomManager::DobTeleport(Spatial * pDOB, int iNewRoomID) // not tested... bool LRoomManager::dob_teleport(Node * pDOB) { + CHECK_ROOM_LIST + Spatial * pSpat = Object::cast_to(pDOB); if (!pSpat) return false; @@ -458,8 +504,13 @@ bool LRoomManager::dob_teleport(Node * pDOB) bool LRoomManager::dob_unregister(Node * pDOB) { + CHECK_ROOM_LIST + LRoom * pRoom = GetRoomFromDOB(pDOB); + // change the meta data on the DOB .. this will catch trying to update an unregistered DOB + Obj_SetRoomNum(pDOB, -2); + if (pRoom) { unsigned int dob_id = pRoom->DOB_Find(pDOB); @@ -549,6 +600,8 @@ bool LRoomManager::LightCreate(Light * pLight, int roomID) bool LRoomManager::light_register(Node * pLightNode) { + CHECK_ROOM_LIST + if (!pLightNode) { WARN_PRINT_ONCE("light_register : pLightNode is NULL"); @@ -565,26 +618,6 @@ bool LRoomManager::light_register(Node * pLightNode) } return LightCreate(pLight, -1); - -// // set culling flag for light -// // 1 is for lighting objects outside the room system -// pLight->set_cull_mask(1 | LRoom::LAYER_MASK_LIGHT); - -// // create new light -// LLight l; -// l.SetDefaults(); -// l.m_GodotID = pLight->get_instance_id(); - -// // 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 = -1; -// l.m_eType = LLight::LT_DIRECTIONAL; - -// m_Lights.push_back(l); - -// return true; } @@ -616,6 +649,35 @@ int LRoomManager::dob_get_room_id(Node * pDOB) return Obj_GetRoomNum(pDOB); } +// helpers to enable the client to manage switching on and off physics and AI +int LRoomManager::rooms_get_num_rooms() const +{ + return m_Rooms.size(); +} + +bool LRoomManager::rooms_is_room_visible(int room_id) const +{ + if (room_id >= m_Rooms.size()) + { + LWARN(5, "LRoomManager::rooms_is_room_visible : room id higher than number of rooms"); + return false; + } + + return m_BF_visible_rooms.GetBit(room_id) != 0; +} + +Array LRoomManager::rooms_get_visible_rooms() const +{ + Array rooms; + for (int n=0; nsize(); n++) + { + rooms.push_back((*m_pCurr_VisibleRoomList)[n]); + } + + return rooms; +} + + Node * LRoomManager::rooms_get_room(int room_id) { const LRoom * pRoom = GetRoom(room_id); @@ -673,11 +735,36 @@ void LRoomManager::rooms_set_debug_planes(bool bActive) } +// move the initial hiding to where the camera is set, so we can save the scene etc +void LRoomManager::ShowAll(bool bShow) +{ + for (int n=0; nshow(); - else - pS->hide(); + sob.Show(!bActive); +// Spatial * pS = sob.GetSpatial(); +// if (pS) +// if (!bActive) +// pS->show(); +// else +// pS->hide(); VisualInstance * pVI = sob.GetVI(); if (pVI) @@ -734,18 +822,24 @@ void LRoomManager::rooms_log_frame() } -void LRoomManager::rooms_set_camera(Node * pCam) +bool LRoomManager::rooms_set_camera(Node * pCam) { + CHECK_ROOM_LIST + + // is it the first setting of the camera? if so hide all + if (m_ID_camera == 0) + ShowAll(false); + m_ID_camera = 0; if (!pCam) - return; + return false; Camera * pCamera = Object::cast_to(pCam); if (!pCamera) { WARN_PRINT("Not a camera"); - return; + return false; } m_ID_camera = pCam->get_instance_id(); @@ -756,19 +850,137 @@ void LRoomManager::rooms_set_camera(Node * pCam) // use this temporarily to force debug // rooms_log_frame(); + + return true; } + + // convert empties and meshes to rooms and portals -void LRoomManager::rooms_convert() +bool LRoomManager::rooms_convert(bool bVerbose, bool bDeleteLights) { + ResolveRoomListPath(); + CHECK_ROOM_LIST + LRoomConverter conv; - conv.Convert(*this); + conv.Convert(*this, bVerbose, false, bDeleteLights); + return true; } +bool LRoomManager::rooms_transfer_uv2s(Node * pMeshInstance_From, Node * pMeshInstance_To) +{ + CheckRoomList(); + MeshInstance * pMI_from = Object::cast_to(pMeshInstance_From); + MeshInstance * pMI_to = Object::cast_to(pMeshInstance_To); + + if (!pMI_from) + return false; + if (!pMI_to) + return false; + + LHelper helper; + Lawn::LDebug::m_bRunning = false; + bool res = helper.TransferUV2(*pMI_from, *pMI_to); + Lawn::LDebug::m_bRunning = true; + return res; +} + + +bool LRoomManager::rooms_unmerge_sobs(Node * pMergeMeshInstance) +{ + MeshInstance * pMI = Object::cast_to(pMergeMeshInstance); + + LHelper helper; + Lawn::LDebug::m_bRunning = false; + bool res = helper.UnMergeSOBs(*this, pMI); + Lawn::LDebug::m_bRunning = true; + return res; +} + +bool LRoomManager::rooms_save_scene(Node * pNode, String szFilename) +{ + LSceneSaver saver; + return saver.SaveScene(pNode, szFilename); +} + + +MeshInstance * LRoomManager::rooms_convert_lightmap_internal(String szProxyFilename, String szLevelFilename) +{ + ResolveRoomListPath(); + if (!CheckRoomList()) + { + WARN_PRINT_ONCE("rooms_convert_lightmap_internal : rooms not set"); + return 0; + } + + LRoomConverter conv; + conv.Convert(*this, true, true, false); + + LHelper helper; + Lawn::LDebug::m_bRunning = false; + MeshInstance * pMI = helper.CreateLightmapProxy(*this); + + // if there is a save filename for the proxy, save + if ((szProxyFilename != "") && pMI) + { + LSceneSaver saver; + saver.SaveScene(pMI, szProxyFilename); + } + + Lawn::LDebug::m_bRunning = true; + + // save the UV2 mapped level + if (szLevelFilename != "") + rooms_save_scene(LROOMLIST, szLevelFilename); + + return pMI; +} + + +bool LRoomManager::rooms_merge_sobs(Node * pMergeMeshInstance) +{ + MeshInstance * pMI = Object::cast_to(pMergeMeshInstance); + + LHelper helper; + Lawn::LDebug::m_bRunning = false; + bool res = helper.MergeSOBs(*this, pMI); + Lawn::LDebug::m_bRunning = true; + return res; +} + + // free memory for current set of rooms, prepare for converting a new game level void LRoomManager::rooms_release() { + CheckRoomList(); + + // unhide all the objects and reattach to scene graph + rooms_set_active(false); + + // unregister all the dobs + for (int n=0; nis_editor_hint()) { WARN_PRINT_ONCE("LRoomManager::FrameUpdate should not be called in editor"); - return; + return false; } // could turn off internal processing? not that important if (!m_bActive) - return; + return false; + + CHECK_ROOM_LIST if (m_bFrustumOnly) { // debugging emulate view frustum FrameUpdate_FrustumOnly(); - return; + return true; } // we keep a frame counter to prevent visiting things multiple times on the same frame in recursive functions @@ -872,11 +1086,11 @@ void LRoomManager::FrameUpdate() } else // camera not set .. do nothing - return; + return false; // camera not a camera?? shouldn't happen but we'll check if (!pCamera) - return; + return false; // Which room is the camera currently in? LRoom * pRoom = GetRoomFromDOB(pCamera); @@ -884,7 +1098,7 @@ void LRoomManager::FrameUpdate() if (!pRoom) { WARN_PRINT_ONCE("LRoomManager::FrameUpdate : Camera is not in an LRoom"); - return; + return false; } // lcamera contains the info needed for culling @@ -938,6 +1152,8 @@ void LRoomManager::FrameUpdate() // when running, emit less debugging output so as not to choke the IDE Lawn::LDebug::m_bRunning = true; + + return true; } void LRoomManager::FrameUpdate_FinalizeRooms() @@ -1112,13 +1328,6 @@ void LRoomManager::FrameUpdate_FinalizeVisibility_WithinRooms() // show / hide is relatively expensive because of propagating messages between nodes ... // should be minimized sob.Show(true); - - // see how expensive show is -// for (int t=0; t<10000; t++) -// { -// sob.Show(false); -// sob.Show(true); -// } } } @@ -1214,7 +1423,7 @@ void LRoomManager::_notification(int p_what) { if (!Engine::get_singleton()->is_editor_hint()) { set_process_internal(true); - CreateDebug(); + //CreateDebug(); } else set_process_internal(false); @@ -1232,34 +1441,176 @@ void LRoomManager::_notification(int p_what) { void LRoomManager::_bind_methods() { // main functions - ClassDB::bind_method(D_METHOD("rooms_convert"), &LRoomManager::rooms_convert); + ClassDB::bind_method(D_METHOD("rooms_convert", "verbose", "delete lights"), &LRoomManager::rooms_convert); ClassDB::bind_method(D_METHOD("rooms_release"), &LRoomManager::rooms_release); - ClassDB::bind_method(D_METHOD("rooms_set_camera"), &LRoomManager::rooms_set_camera); - ClassDB::bind_method(D_METHOD("rooms_get_room"), &LRoomManager::rooms_get_room); + ClassDB::bind_method(D_METHOD("rooms_set_camera", "camera"), &LRoomManager::rooms_set_camera); + + ClassDB::bind_method(D_METHOD("rooms_save_scene", "node", "filename"), &LRoomManager::rooms_save_scene); + + // stuff for external workflow .. works but don't expose yet +// ClassDB::bind_method(D_METHOD("rooms_merge_sobs"), &LRoomManager::rooms_merge_sobs); +// ClassDB::bind_method(D_METHOD("rooms_unmerge_sobs"), &LRoomManager::rooms_unmerge_sobs); +// ClassDB::bind_method(D_METHOD("rooms_transfer_uv2s"), &LRoomManager::rooms_transfer_uv2s); + // debugging - ClassDB::bind_method(D_METHOD("rooms_set_logging"), &LRoomManager::rooms_set_logging); + ClassDB::bind_method(D_METHOD("rooms_set_logging", "level 0 to 6"), &LRoomManager::rooms_set_logging); ClassDB::bind_method(D_METHOD("rooms_log_frame"), &LRoomManager::rooms_log_frame); - ClassDB::bind_method(D_METHOD("rooms_set_active"), &LRoomManager::rooms_set_active); - ClassDB::bind_method(D_METHOD("rooms_set_debug_planes"), &LRoomManager::rooms_set_debug_planes); - ClassDB::bind_method(D_METHOD("rooms_set_debug_bounds"), &LRoomManager::rooms_set_debug_bounds); - ClassDB::bind_method(D_METHOD("rooms_set_debug_lights"), &LRoomManager::rooms_set_debug_lights); + ClassDB::bind_method(D_METHOD("rooms_set_active", "active"), &LRoomManager::rooms_set_active); + 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); + // lightmapping + ClassDB::bind_method(D_METHOD("rooms_convert_lightmap_internal", "proxy filename", "level filename"), &LRoomManager::rooms_convert_lightmap_internal); // functions to add dynamic objects to the culling system // Note that these should not be placed directly in rooms, the system will 'soft link' to them // so they can be held, e.g. in pools elsewhere in the scene graph - ClassDB::bind_method(D_METHOD("dob_register"), &LRoomManager::dob_register); - ClassDB::bind_method(D_METHOD("dob_unregister"), &LRoomManager::dob_unregister); - ClassDB::bind_method(D_METHOD("dob_update"), &LRoomManager::dob_update); - ClassDB::bind_method(D_METHOD("dob_teleport"), &LRoomManager::dob_teleport); + ClassDB::bind_method(D_METHOD("dob_register", "dob", "radius"), &LRoomManager::dob_register); + ClassDB::bind_method(D_METHOD("dob_unregister", "dob"), &LRoomManager::dob_unregister); + ClassDB::bind_method(D_METHOD("dob_update", "dob"), &LRoomManager::dob_update); + ClassDB::bind_method(D_METHOD("dob_teleport", "dob"), &LRoomManager::dob_teleport); - ClassDB::bind_method(D_METHOD("dob_register_hint"), &LRoomManager::dob_register_hint); - ClassDB::bind_method(D_METHOD("dob_teleport_hint"), &LRoomManager::dob_teleport_hint); + ClassDB::bind_method(D_METHOD("dob_register_hint", "dob", "radius", "room"), &LRoomManager::dob_register_hint); + ClassDB::bind_method(D_METHOD("dob_teleport_hint", "dob", "room"), &LRoomManager::dob_teleport_hint); - ClassDB::bind_method(D_METHOD("dob_get_room_id"), &LRoomManager::dob_get_room_id); + ClassDB::bind_method(D_METHOD("dob_get_room_id", "dob"), &LRoomManager::dob_get_room_id); - ClassDB::bind_method(D_METHOD("light_register"), &LRoomManager::light_register); + + ClassDB::bind_method(D_METHOD("light_register", "light"), &LRoomManager::light_register); + + // helper + ClassDB::bind_method(D_METHOD("rooms_get_room", "room id"), &LRoomManager::rooms_get_room); + ClassDB::bind_method(D_METHOD("rooms_get_num_rooms"), &LRoomManager::rooms_get_num_rooms); + ClassDB::bind_method(D_METHOD("rooms_is_room_visible", "room id"), &LRoomManager::rooms_is_room_visible); + ClassDB::bind_method(D_METHOD("rooms_get_visible_rooms"), &LRoomManager::rooms_get_visible_rooms); + + + ClassDB::bind_method(D_METHOD("set_rooms", "rooms"), &LRoomManager::set_rooms); + ClassDB::bind_method(D_METHOD("set_rooms_path", "rooms"), &LRoomManager::set_rooms_path); + ClassDB::bind_method(D_METHOD("get_rooms_path"), &LRoomManager::get_rooms_path); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "rooms"), "set_rooms_path", "get_rooms_path"); +} + +////////////////////////////// + +void LRoomManager::_set_rooms(Object *p_rooms) +{ + if (p_rooms) + { + LPRINT(2, "\t_set_rooms"); + } + else + { + LPRINT(2, "\t_set_rooms NULL"); + } + + m_ID_RoomList = 0; + m_pRoomList = 0; + + if (p_rooms) + { + // check is spatial + Spatial *pSpatial = Object::cast_to(p_rooms); + + if (pSpatial) + { + m_ID_RoomList = pSpatial->get_instance_id(); + m_pRoomList = pSpatial; + LPRINT(2, "\t\tRoomlist was Godot ID " + itos(m_ID_RoomList)); + } + else + { + // should never get here? + WARN_PRINT("_set_rooms must be a Spatial"); + } + } } + + +void LRoomManager::set_rooms(const Object *p_rooms) +{ + // handle null + if (p_rooms == NULL) + { + WARN_PRINT("SCRIPT set_rooms NULL"); + remove_rooms_path(); + return; + } + LPRINT(2, "SCRIPT set_rooms"); + + // is it a spatial? + const Spatial * pSpatial = Object::cast_to(p_rooms); + + if (!pSpatial) + { + WARN_PRINT("set_rooms must be a spatial."); + remove_rooms_path(); + return; + } + + NodePath path = get_path_to(pSpatial); + set_rooms_path(path); +} + + +void LRoomManager::set_rooms_path(const NodePath &p_path) +{ + LPRINT(2, "set_rooms_path " + p_path); + m_path_RoomList = p_path; + ResolveRoomListPath(); +} + +NodePath LRoomManager::get_rooms_path() const +{ + return m_path_RoomList; +} + +void LRoomManager::remove_rooms_path() +{ + NodePath pathnull; + set_rooms_path(pathnull); +} + + +Spatial * LRoomManager::GetRoomList_Checked() +{ + if (m_ID_RoomList == 0) + return 0; + + m_pRoomList = (Spatial *) ObjectDB::get_instance(m_ID_RoomList); + + if (!m_pRoomList) + m_ID_RoomList = 0; // the node is no longer valid + + return m_pRoomList; +} + + +void LRoomManager::ResolveRoomListPath() +{ + LPRINT(2, "ResolveRoomListPath " + m_path_RoomList); + + if (has_node(m_path_RoomList)) + { + LPRINT(2, "has_node"); + Spatial * pNode = Object::cast_to(get_node(m_path_RoomList)); + if (pNode) + { + _set_rooms(pNode); + return; + } + else + { + WARN_PRINT("RoomList must be a Spatial"); + } + } + + // default to setting to null + _set_rooms(NULL); +} + diff --git a/lroom_manager.h b/lroom_manager.h index 3c1c9dc..d679edd 100644 --- a/lroom_manager.h +++ b/lroom_manager.h @@ -41,6 +41,7 @@ class LRoomManager : public Spatial { friend class LRoom; friend class LRoomConverter; + friend class LHelper; // godot ID of the camera (which should be registered as a DOB to allow moving between rooms) @@ -117,7 +118,7 @@ protected: int m_iLoggingLevel; private: // this is where we do all the culling - void FrameUpdate(); + bool FrameUpdate(); void FrameUpdate_Prepare(); void FrameUpdate_FinalizeRooms(); void FrameUpdate_AddShadowCasters(); @@ -141,6 +142,7 @@ private: void CreateDebug(); void DobChangeVisibility(Spatial * pDOB, const LRoom * pOld, const LRoom * pNew); void ReleaseResources(bool bPrepareConvert); + void ShowAll(bool bShow); // helper funcs @@ -165,6 +167,11 @@ public: 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; @@ -172,25 +179,61 @@ private: Ref m_mat_Debug_Planes; Ref m_mat_Debug_Bounds; + // unchecked + Spatial * m_pRoomList; + void ResolveRoomListPath(); + +public: + // makes sure m_pRoomList is up to date and valid + bool CheckRoomList() {return GetRoomList_Checked() != 0;} + + Spatial * GetRoomList_Checked(); + // unchecked, be sure to call checked version first which will set m_pRoomList + Spatial * GetRoomList() const {return m_pRoomList;} 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 - void rooms_convert(); + 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 - void rooms_set_camera(Node * pCam); + 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); diff --git a/lscene_saver.cpp b/lscene_saver.cpp new file mode 100644 index 0000000..fd943ef --- /dev/null +++ b/lscene_saver.cpp @@ -0,0 +1,33 @@ +#include "lscene_saver.h" +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" +#include "core/io/resource_saver.h" + +void LSceneSaver::SetOwnerRecursive(Node * pNode, Node * pOwner) +{ + pNode->set_owner(pOwner); + + for (int n=0; nget_child_count(); n++) + { + SetOwnerRecursive(pNode->get_child(n), pOwner); + } +} + + +bool LSceneSaver::SaveScene(Node * pNode, String szFilename) +{ + // godot needs owner to be set on nodes that are to be saved as part of a packed scene + SetOwnerRecursive(pNode, pNode); + + //PackedScene ps; + // reference should self delete on exiting func .. check! + Ref ps = memnew(PackedScene); + //Ref ref_ps = &ps; + + ps->pack(pNode); + + ResourceSaver rs; + rs.save(szFilename, ps); + + return true; +} diff --git a/lscene_saver.h b/lscene_saver.h new file mode 100644 index 0000000..6957d5c --- /dev/null +++ b/lscene_saver.h @@ -0,0 +1,10 @@ +#pragma once + +class LSceneSaver +{ +public: + bool SaveScene(Node * pNode, String szFilename); + +private: + void SetOwnerRecursive(Node * pNode, Node * pOwner); +};