From 8e2c0dff4afceb6cb77303cb3e1463f3baddd1fb Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 9 Dec 2020 15:51:36 +0100 Subject: [PATCH] pgraph: Hack fix for copying model with LightAttrib on root When a LightAttrib is set on the root of a model and the light is contained within that model, creating a copy (like the one Loader makes automatically) causes the LightAttrib to still point to the original light, not to the newly copied light. This works around it only for the case when the LightAttrib is set on the root, and only for the case of NodePath::copy_to() (it turns out there are edge cases when doing it in PandaNode::copy_subgraph() that would be hard to explain). See Moguri/blend2bam#44 --- panda/src/pgraph/lightAttrib.cxx | 60 ++++++++++++++++++++++++ panda/src/pgraph/lightAttrib.h | 2 + panda/src/pgraph/loader.cxx | 6 +-- panda/src/pgraph/nodePath.cxx | 79 +++++++++++++++++++++++++++++++- panda/src/pgraph/nodePath.h | 4 ++ 5 files changed, 146 insertions(+), 5 deletions(-) diff --git a/panda/src/pgraph/lightAttrib.cxx b/panda/src/pgraph/lightAttrib.cxx index 9bc99674f8..9820f1bcfa 100644 --- a/panda/src/pgraph/lightAttrib.cxx +++ b/panda/src/pgraph/lightAttrib.cxx @@ -443,6 +443,36 @@ remove_on_light(const NodePath &light) const { return return_new(attrib); } +/** + * Returns a new LightAttrib, just like this one, but with the indicated light + * replaced with the given other light. + */ +CPT(RenderAttrib) LightAttrib:: +replace_on_light(const NodePath &source, const NodePath &dest) const { + if (source == dest) { + return this; + } + + nassertr(!source.is_empty(), this); + Light *slobj = source.node()->as_light(); + nassertr(slobj != nullptr, this); + + nassertr(!dest.is_empty(), this); + Light *dlobj = dest.node()->as_light(); + nassertr(dlobj != nullptr, this); + + LightAttrib *attrib = new LightAttrib(*this); + + auto it = attrib->_on_lights.find(source); + if (it != attrib->_on_lights.end()) { + dlobj->attrib_ref(); + slobj->attrib_unref(); + + *it = dest; + } + return return_new(attrib); +} + /** * Returns a new LightAttrib, just like this one, but with the indicated light * added to the list of lights turned off by this attrib. @@ -475,6 +505,36 @@ remove_off_light(const NodePath &light) const { return return_new(attrib); } +/** + * Returns a new LightAttrib, just like this one, but with the indicated light + * replaced with the given other light. + */ +CPT(RenderAttrib) LightAttrib:: +replace_off_light(const NodePath &source, const NodePath &dest) const { + if (source == dest) { + return this; + } + + nassertr(!source.is_empty(), this); + Light *slobj = source.node()->as_light(); + nassertr(slobj != nullptr, this); + + nassertr(!dest.is_empty(), this); + Light *dlobj = dest.node()->as_light(); + nassertr(dlobj != nullptr, this); + + LightAttrib *attrib = new LightAttrib(*this); + + auto it = attrib->_off_lights.find(source); + if (it != attrib->_off_lights.end()) { + dlobj->attrib_ref(); + slobj->attrib_unref(); + + *it = dest; + } + return return_new(attrib); +} + /** * Returns the most important light (that is, the light with the highest * priority) in the LightAttrib, excluding any ambient lights. Returns an diff --git a/panda/src/pgraph/lightAttrib.h b/panda/src/pgraph/lightAttrib.h index 4872ebc623..361a4a6c1e 100644 --- a/panda/src/pgraph/lightAttrib.h +++ b/panda/src/pgraph/lightAttrib.h @@ -85,8 +85,10 @@ PUBLISHED: CPT(RenderAttrib) add_on_light(const NodePath &light) const; CPT(RenderAttrib) remove_on_light(const NodePath &light) const; + CPT(RenderAttrib) replace_on_light(const NodePath &source, const NodePath &dest) const; CPT(RenderAttrib) add_off_light(const NodePath &light) const; CPT(RenderAttrib) remove_off_light(const NodePath &light) const; + CPT(RenderAttrib) replace_off_light(const NodePath &source, const NodePath &dest) const; NodePath get_most_important_light() const; LColor get_ambient_contribution() const; diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index ab4db6ea2f..15b130592d 100644 --- a/panda/src/pgraph/loader.cxx +++ b/panda/src/pgraph/loader.cxx @@ -292,7 +292,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, << "Model " << pathname << " found in ModelPool.\n"; } // But return a deep copy of the shared model. - node = node->copy_subgraph(); + node = NodePath(node).copy_to(NodePath()).node(); } return node; } @@ -329,7 +329,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, // from the RAM cached version. ModelPool::add_model(pathname, model_root); if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { - return model_root->copy_subgraph(); + return NodePath(model_root).copy_to(NodePath()).node(); } } } @@ -398,7 +398,7 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, // cached version. ModelPool::add_model(pathname, DCAST(ModelRoot, result.p())); if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { - result = result->copy_subgraph(); + result = NodePath(result).copy_to(NodePath()).node(); } } diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx index 1748942679..f955b9c380 100644 --- a/panda/src/pgraph/nodePath.cxx +++ b/panda/src/pgraph/nodePath.cxx @@ -539,12 +539,45 @@ copy_to(const NodePath &other, int sort, Thread *current_thread) const { nassertr(other._error_type == ET_ok, fail()); PandaNode *source_node = node(); - PT(PandaNode) copy_node = source_node->copy_subgraph(current_thread); + PandaNode::InstanceMap inst_map; + PT(PandaNode) copy_node = source_node->r_copy_subgraph(inst_map, current_thread); nassertr(copy_node != nullptr, fail()); copy_node->reset_prev_transform(current_thread); - return other.attach_new_node(copy_node, sort, current_thread); + NodePath result = other.attach_new_node(copy_node, sort, current_thread); + + // Temporary hack fix: if this root NodePath had lights applied that are + // located inside this subgraph, we need to fix them. + const RenderState *state = source_node->get_state(); + const LightAttrib *lattr; + if (state->get_attrib(lattr)) { + CPT(LightAttrib) new_lattr = lattr; + + for (size_t i = 0; i < lattr->get_num_off_lights(); ++i) { + NodePath light = lattr->get_off_light(i); + NodePath light2 = light; + + if (light2.replace_copied_nodes(*this, result, inst_map, current_thread)) { + new_lattr = DCAST(LightAttrib, new_lattr->replace_off_light(light, light2)); + } + } + + for (size_t i = 0; i < lattr->get_num_on_lights(); ++i) { + NodePath light = lattr->get_on_light(i); + NodePath light2 = light; + + if (light2.replace_copied_nodes(*this, result, inst_map, current_thread)) { + new_lattr = DCAST(LightAttrib, new_lattr->replace_on_light(light, light2)); + } + } + + if (new_lattr != lattr) { + result.set_state(state->set_attrib(std::move(new_lattr))); + } + } + + return result; } /** @@ -5803,6 +5836,48 @@ decode_from_bam_stream(vector_uchar data, BamReader *reader) { return result; } +/** + * If the given root node is an ancestor of this NodePath, replaces all + * components below it using the given instance map. + * + * This is a helper method used by copy_to(). + */ +bool NodePath:: +replace_copied_nodes(const NodePath &source, const NodePath &dest, + const PandaNode::InstanceMap &inst_map, + Thread *current_thread) { + nassertr(!dest.is_empty(), false); + + int pipeline_stage = current_thread->get_pipeline_stage(); + + pvector nodes; + + NodePathComponent *comp = _head; + while (comp != nullptr && comp != source._head) { + nodes.push_back(comp->get_node()); + + comp = comp->get_next(pipeline_stage, current_thread); + } + + if (comp == nullptr) { + // The given source NodePath isn't an ancestor of this NodePath. + return false; + } + + // Start at the dest NodePath and compose the new NodePath. + PT(NodePathComponent) new_comp = dest._head; + pvector::reverse_iterator it; + for (it = nodes.rbegin(); it != nodes.rend(); ++it) { + PandaNode::InstanceMap::const_iterator iit = inst_map.find(*it); + nassertr_always(iit != inst_map.end(), false); + new_comp = PandaNode::get_component(new_comp, iit->second, pipeline_stage, current_thread); + } + + nassertr(new_comp != nullptr, false); + _head = std::move(new_comp); + return true; +} + /** * Walks up from both NodePaths to find the first node that both have in * common, if any. Fills a_count and b_count with the number of nodes below diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h index eaec935a29..187129d30c 100644 --- a/panda/src/pgraph/nodePath.h +++ b/panda/src/pgraph/nodePath.h @@ -952,6 +952,10 @@ PUBLISHED: static NodePath decode_from_bam_stream(vector_uchar data, BamReader *reader = nullptr); private: + bool replace_copied_nodes(const NodePath &source, const NodePath &dest, + const PandaNode::InstanceMap &inst_map, + Thread *current_thread); + static NodePathComponent * find_common_ancestor(const NodePath &a, const NodePath &b, int &a_count, int &b_count,