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
This commit is contained in:
rdb 2020-12-09 15:51:36 +01:00
parent 9525ddbfef
commit 8e2c0dff4a
5 changed files with 146 additions and 5 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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<PandaNode *> 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<PandaNode *>::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

View File

@ -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,