// // Copyright 2019 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // #include "hdPrman/instancer.h" #include "pxr/imaging/hd/sceneDelegate.h" #include "pxr/imaging/hd/tokens.h" #include "pxr/base/gf/vec2f.h" #include "pxr/base/gf/vec3f.h" #include "pxr/base/gf/vec4f.h" #include "pxr/base/gf/matrix4d.h" #include "pxr/base/gf/rotation.h" #include "pxr/base/gf/quatf.h" #include "pxr/base/gf/quath.h" #include "pxr/base/gf/quaternion.h" #include "pxr/base/tf/staticTokens.h" #include "RtParamList.h" PXR_NAMESPACE_OPEN_SCOPE HdPrmanInstancer::HdPrmanInstancer(HdSceneDelegate* delegate, SdfPath const& id, SdfPath const &parentId) : HdInstancer(delegate, id, parentId) { } HdPrmanInstancer::~HdPrmanInstancer() { } void HdPrmanInstancer::SyncPrimvars() { HD_TRACE_FUNCTION(); HF_MALLOC_TAG_FUNCTION(); HdSceneDelegate *delegate = GetDelegate(); HdChangeTracker &changeTracker = delegate->GetRenderIndex().GetChangeTracker(); SdfPath const& id = GetId(); // Use the double-checked locking pattern to check if this instancer's // primvars are dirty. int dirtyBits = changeTracker.GetInstancerDirtyBits(id); if (HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, id)) { std::lock_guard lock(_instanceLock); dirtyBits = changeTracker.GetInstancerDirtyBits(id); if (HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, id)) { // If this instancer has dirty primvars, get the list of // primvar names and then cache each one. for (HdPrimvarDescriptor const& primvar: delegate->GetPrimvarDescriptors(id, HdInterpolationInstance)) { // Skip primvars that have special handling elsewhere. // The transform primvars are all handled in // SampleInstanceTransform. if (primvar.name == HdInstancerTokens->instanceTransform || primvar.name == HdInstancerTokens->rotate || primvar.name == HdInstancerTokens->scale || primvar.name == HdInstancerTokens->translate) { continue; } if (HdChangeTracker::IsPrimvarDirty(dirtyBits, id, primvar.name)) { VtValue value = delegate->Get(id, primvar.name); if (!value.IsEmpty()) { _PrimvarValue &entry = _primvarMap[primvar.name]; entry.desc = primvar; std::swap(entry.value, value); } } } // Mark the instancer as clean changeTracker.MarkInstancerClean(id); } } } // Helper to accumulate sample times from the largest set of // samples seen, up to maxNumSamples. template static void _AccumulateSampleTimes(HdTimeSampleArray const& in, HdTimeSampleArray *out) { if (in.count > out->count) { out->Resize(in.count); out->times = in.times; } } void HdPrmanInstancer::SampleInstanceTransforms( SdfPath const& prototypeId, VtIntArray const& instanceIndices, HdTimeSampleArray *sa) { HD_TRACE_FUNCTION(); HF_MALLOC_TAG_FUNCTION(); HdSceneDelegate *delegate = GetDelegate(); const SdfPath &instancerId = GetId(); // Sample the inputs HdTimeSampleArray instancerXform; HdTimeSampleArray boxedInstanceXforms; HdTimeSampleArray boxedTranslates; HdTimeSampleArray boxedRotates; HdTimeSampleArray boxedScales; delegate->SampleInstancerTransform(instancerId, &instancerXform); delegate->SamplePrimvar(instancerId, HdInstancerTokens->instanceTransform, &boxedInstanceXforms); delegate->SamplePrimvar(instancerId, HdInstancerTokens->translate, &boxedTranslates); delegate->SamplePrimvar(instancerId, HdInstancerTokens->scale, &boxedScales); delegate->SamplePrimvar(instancerId, HdInstancerTokens->rotate, &boxedRotates); // Unbox samples held as VtValues HdTimeSampleArray instanceXforms; HdTimeSampleArray translates; HdTimeSampleArray rotates; HdTimeSampleArray scales; instanceXforms.UnboxFrom(boxedInstanceXforms); translates.UnboxFrom(boxedTranslates); rotates.UnboxFrom(boxedRotates); scales.UnboxFrom(boxedScales); // As a simple resampling strategy, find the input with the max # // of samples and use its sample placement. In practice we expect // them to all be the same, i.e. to not require resampling. sa->Resize(0); _AccumulateSampleTimes(instancerXform, sa); _AccumulateSampleTimes(instanceXforms, sa); _AccumulateSampleTimes(translates, sa); _AccumulateSampleTimes(scales, sa); _AccumulateSampleTimes(rotates, sa); // Resample inputs and concatenate transformations. // // XXX:PERFORMANCE: This currently samples the transform arrays for // all indices. We should only do this work for the instances // indicated in the instanceIndices array. // for (size_t i=0; i < sa->count; ++i) { const float t = sa->times[i]; GfMatrix4d xf(1); if (instancerXform.count > 0) { xf = instancerXform.Resample(t); } VtMatrix4dArray ixf; if (instanceXforms.count > 0) { ixf = instanceXforms.Resample(t); } VtVec3fArray trans; if (translates.count > 0) { trans = translates.Resample(t); } VtQuathArray rot; if (rotates.count > 0) { rot = rotates.Resample(t); } VtVec3fArray scale; if (scales.count > 0) { scale = scales.Resample(t); } // Concatenate transformations and filter to just the instanceIndices. VtMatrix4dArray &ma = sa->values[i]; ma.resize(instanceIndices.size()); for (size_t j=0; j < instanceIndices.size(); ++j) { ma[j] = xf; size_t instanceIndex = instanceIndices[j]; if (trans.size() > instanceIndex) { GfMatrix4d t(1); t.SetTranslate(GfVec3d(trans[instanceIndex])); ma[j] = t * ma[j]; } if (rot.size() > instanceIndex) { GfMatrix4d r(1); r.SetRotate(GfRotation(rot[instanceIndex])); ma[j] = r * ma[j]; } if (scale.size() > instanceIndex) { GfMatrix4d s(1); s.SetScale(GfVec3d(scale[instanceIndex])); ma[j] = s * ma[j]; } if (ixf.size() > instanceIndex) { ma[j] = ixf[instanceIndex] * ma[j]; } } } // If there is a parent instancer, continue to unroll // the child instances across the parent; otherwise we're done. if (GetParentId().IsEmpty()) { return; } HdInstancer *parentInstancer = GetDelegate()->GetRenderIndex().GetInstancer(GetParentId()); if (!TF_VERIFY(parentInstancer)) { return; } HdPrmanInstancer *hdPrmanParentInstancer = static_cast(parentInstancer); // Multiply the instance samples against the parent instancer samples. // The transforms taking nesting into account are computed by: // parentTransforms = parentInstancer->ComputeInstanceTransforms(GetId()) // foreach (parentXf : parentTransforms, xf : transforms) { // parentXf * xf // } HdTimeSampleArray parentXf; VtIntArray instanceIndicesParent = GetDelegate()->GetInstanceIndices(GetParentId(), GetId()); hdPrmanParentInstancer-> SampleInstanceTransforms(GetId(), instanceIndicesParent, &parentXf); if (parentXf.count == 0 || parentXf.values[0].empty()) { // No samples for parent instancer. return; } // Move aside previously computed child xform samples to childXf. HdTimeSampleArray childXf(*sa); // Merge sample times, taking the densest sampling. _AccumulateSampleTimes(parentXf, sa); // Apply parent xforms to the children. for (size_t i=0; i < sa->count; ++i) { const float t = sa->times[i]; // Resample transforms at the same time. VtMatrix4dArray curParentXf = parentXf.Resample(t); VtMatrix4dArray curChildXf = childXf.Resample(t); // Multiply out each combination. VtMatrix4dArray &result = sa->values[i]; result.resize(curParentXf.size() * curChildXf.size()); for (size_t j = 0; j < curParentXf.size(); ++j) { for (size_t k = 0; k < curChildXf.size(); ++k) { result[j * curChildXf.size() + k] = curChildXf[k] * curParentXf[j]; } } } } void HdPrmanInstancer::GetInstancePrimvars( SdfPath const& prototypeId, size_t instanceIndex, RtParamList& attrs) { for (auto entry: _primvarMap) { HdPrimvarDescriptor const& primvar = entry.second.desc; // Skip non-instance-rate primvars. if (primvar.interpolation != HdInterpolationInstance) { continue; } // Confirm that instance-rate primvars are array-valued // and have sufficient dimensions. VtValue const& val = entry.second.value; if (instanceIndex >= val.GetArraySize()) { TF_WARN("HdPrman: Instance-rate primvar has array size %zu; " "cannot provide a value for instance index %zu\n", val.GetArraySize(), instanceIndex); continue; } // Instance primvars with the "ri:attributes:" prefix correspond to // renderman-namespace attributes and have that prefix stripped. // All other primvars are in the "user:" namespace, so if they don't // have that prefix we need to add it. RtUString name; static const char *userPrefix = "user:"; static const char *riAttrPrefix = "ri:attributes:"; if (!strncmp(entry.first.GetText(), userPrefix, strlen(userPrefix))) { name = RtUString(entry.first.GetText()); } else if (!strncmp(entry.first.GetText(), riAttrPrefix, strlen(riAttrPrefix))) { const char *strippedName = entry.first.GetText(); strippedName += strlen(riAttrPrefix); name = RtUString(strippedName); } else { std::string mangled = TfStringPrintf("user:%s", entry.first.GetText()); name = RtUString(mangled.c_str()); } if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetFloat(name, v[instanceIndex]); } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetInteger(name, v[instanceIndex]); } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetFloatArray( name, reinterpret_cast(v.cdata()), 2); } else if (val.IsHolding>()) { const GfVec3f& v = val.UncheckedGet>()[instanceIndex]; if (primvar.role == HdPrimvarRoleTokens->color) { attrs.SetColor(name, RtColorRGB(v[0], v[1], v[2])); } else if (primvar.role == HdPrimvarRoleTokens->point) { attrs.SetPoint(name, RtPoint3(v[0], v[1], v[2])); } else if (primvar.role == HdPrimvarRoleTokens->normal) { attrs.SetPoint(name, RtNormal3(v[0], v[1], v[2])); } else { attrs.SetVector(name, RtVector3(v[0], v[1], v[2])); } } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetFloatArray( name, reinterpret_cast(v.cdata()), 4); } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetMatrix(name, HdPrman_GfMatrixToRtMatrix(v[instanceIndex])); } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetString(name, RtUString(v[instanceIndex].c_str())); } else if (val.IsHolding>()) { const VtArray& v = val.UncheckedGet>(); attrs.SetString(name, RtUString(v[instanceIndex].GetText())); } } } PXR_NAMESPACE_CLOSE_SCOPE