PXR_NAMESPACE_OPEN_SCOPE PxrUsdKatanaUsdInPrivateData::PxrUsdKatanaUsdInPrivateData( const UsdPrim& prim, PxrUsdKatanaUsdInArgsRefPtr usdInArgs, const PxrUsdKatanaUsdInPrivateData* parentData) : _prim(prim), _usdInArgs(usdInArgs), _extGb(0) { // XXX: manually track instance and master path for possible // relationship re-retargeting. This approach does not yet // support nested instances -- which is expected to be handled // via the forthcoming GetMasterWithContext. // if (prim.IsInstance()) { if (prim.IsInMaster() && parentData) { SdfPath descendentPrimPath = prim.GetPath().ReplacePrefix( prim.GetPath().GetPrefixes()[0], SdfPath::ReflexiveRelativePath()); _instancePath = parentData->GetInstancePath().AppendPath( descendentPrimPath); } else { _instancePath = prim.GetPath(); } const UsdPrim& masterPrim = prim.GetMaster(); if (masterPrim) { _masterPath = masterPrim.GetPath(); } } else if (parentData) { // Pass along instance and master paths to children. // if (!parentData->GetInstancePath().IsEmpty()) { _instancePath = parentData->GetInstancePath(); } if (!parentData->GetMasterPath().IsEmpty()) { _masterPath = parentData->GetMasterPath(); } } // // Apply session overrides for motion. // const std::string primPath = prim.GetPrimPath().GetString(); const std::string isolatePath = usdInArgs->GetIsolatePath(); const std::string sessionPath = usdInArgs->GetSessionLocationPath(); FnKat::GroupAttribute sessionAttr = usdInArgs->GetSessionAttr(); // XXX: If an isolatePath has been specified, it means the PxrUsdIn is // probably loading USD contents below the USD root. This can prevent // overrides from trickling down the hierarchy, e.g. the overrides for /A/B // won't get applied to children if the isolatePath is /A/B/C/D. // // So, if the usdInArgs suggest that an isolatePath has been specified and // we don't have any parentData, we'll need to check if there are overrides // for the prim and any of its parents. // std::vector<std::string> pathsToCheck; if (!parentData and !isolatePath.empty() and pystring::startswith(primPath, isolatePath+"/")) { std::vector<std::string> parentLocs; Foundry::Katana::Util::Path::GetLocationStack(parentLocs, primPath); std::reverse(std::begin(parentLocs), std::end(parentLocs)); for (size_t i = 0; i < parentLocs.size(); ++i) { pathsToCheck.push_back(FnKat::DelimiterEncode( sessionPath + parentLocs[i])); } } else { pathsToCheck.push_back(FnKat::DelimiterEncode(sessionPath + primPath)); } // // If a session override is specified, use its value. If no override exists, // try asking the parent data for its value. Otherwise, fall back on the // usdInArgs value. // bool overrideFound; // Current time. // overrideFound = false; for (size_t i = 0; i < pathsToCheck.size(); ++i) { FnKat::FloatAttribute currentTimeAttr = sessionAttr.getChildByName( "overrides."+pathsToCheck[i]+".currentTime"); if (currentTimeAttr.isValid()) { _currentTime = currentTimeAttr.getValue(); overrideFound = true; break; } } if (!overrideFound) { if (parentData) { _currentTime = parentData->GetCurrentTime(); } else { _currentTime = usdInArgs->GetCurrentTime(); } } // Shutter open. // overrideFound = false; for (size_t i = 0; i < pathsToCheck.size(); ++i) { FnKat::FloatAttribute shutterOpenAttr = sessionAttr.getChildByName( "overrides."+pathsToCheck[i]+".shutterOpen"); if (shutterOpenAttr.isValid()) { _shutterOpen = shutterOpenAttr.getValue(); overrideFound = true; break; } } if (!overrideFound) { if (parentData) { _shutterOpen = parentData->GetShutterOpen(); } else { _shutterOpen = usdInArgs->GetShutterOpen(); } } // Shutter close. // overrideFound = false; for (size_t i = 0; i < pathsToCheck.size(); ++i) { FnKat::FloatAttribute shutterCloseAttr = sessionAttr.getChildByName( "overrides."+pathsToCheck[i]+".shutterClose"); if (shutterCloseAttr.isValid()) { _shutterClose = shutterCloseAttr.getValue(); overrideFound = true; break; } } if (!overrideFound) { if (parentData) { _shutterClose = parentData->GetShutterClose(); } else { _shutterClose = usdInArgs->GetShutterClose(); } } // Motion sample times. // // Fallback logic is a little more complicated for motion sample times, as // they can vary per attribute, so store both the overridden and the // fallback motion sample times for use inside GetMotionSampleTimes. // for (size_t i = 0; i < pathsToCheck.size(); ++i) { FnKat::Attribute motionSampleTimesAttr = sessionAttr.getChildByName( "overrides."+pathsToCheck[i]+".motionSampleTimes"); if (motionSampleTimesAttr.isValid()) { // Interpret an IntAttribute as "use usdInArgs defaults" // if (motionSampleTimesAttr.getType() == kFnKatAttributeTypeInt) { _motionSampleTimesOverride = usdInArgs->GetMotionSampleTimes(); break; } // Interpret a FloatAttribute as an explicit value override // if (motionSampleTimesAttr.getType() == kFnKatAttributeTypeFloat) { const auto& sampleTimes = FnKat::FloatAttribute( motionSampleTimesAttr).getNearestSample(0); if (!sampleTimes.empty()) { for (float sampleTime : sampleTimes) _motionSampleTimesOverride.push_back( (double)sampleTime); break; } } } } if (parentData) { _motionSampleTimesFallback = parentData->GetMotionSampleTimes(); } else { _motionSampleTimesFallback = usdInArgs->GetMotionSampleTimes(); } }
void PxrUsdKatanaReadPointInstancer( const UsdGeomPointInstancer& instancer, const PxrUsdKatanaUsdInPrivateData& data, PxrUsdKatanaAttrMap& instancerAttrMap, PxrUsdKatanaAttrMap& sourcesAttrMap, PxrUsdKatanaAttrMap& instancesAttrMap, PxrUsdKatanaAttrMap& inputAttrMap) { const double currentTime = data.GetCurrentTime(); PxrUsdKatanaReadXformable(instancer, data, instancerAttrMap); // Get primvars for setting later. Unfortunatley, the only way to get them // out of the attr map is to build it, which will cause its contents to be // cleared. We'll need to restore its contents before continuing. // FnKat::GroupAttribute instancerAttrs = instancerAttrMap.build(); FnKat::GroupAttribute primvarAttrs = instancerAttrs.getChildByName("geometry.arbitrary"); for (int64_t i = 0; i < instancerAttrs.getNumberOfChildren(); ++i) { instancerAttrMap.set(instancerAttrs.getChildName(i), instancerAttrs.getChildByIndex(i)); } instancerAttrMap.set("type", FnKat::StringAttribute("usd point instancer")); const std::string fileName = data.GetUsdInArgs()->GetFileName(); instancerAttrMap.set("info.usd.fileName", FnKat::StringAttribute(fileName)); FnKat::GroupAttribute inputAttrs = inputAttrMap.build(); const std::string katOutputPath = FnKat::StringAttribute( inputAttrs.getChildByName("outputLocationPath")).getValue("", false); if (katOutputPath.empty()) { _LogAndSetError(instancerAttrMap, "No output location path specified"); return; } // // Validate instancer data. // const std::string instancerPath = instancer.GetPath().GetString(); UsdStageWeakPtr stage = instancer.GetPrim().GetStage(); // Prototypes (required) // SdfPathVector protoPaths; instancer.GetPrototypesRel().GetTargets(&protoPaths); if (protoPaths.empty()) { _LogAndSetError(instancerAttrMap, "Instancer has no prototypes"); return; } _PathToPrimMap primCache; for (auto protoPath : protoPaths) { const UsdPrim &protoPrim = stage->GetPrimAtPath(protoPath); primCache[protoPath] = protoPrim; } // Indices (required) // VtIntArray protoIndices; if (!instancer.GetProtoIndicesAttr().Get(&protoIndices, currentTime)) { _LogAndSetError(instancerAttrMap, "Instancer has no prototype indices"); return; } const size_t numInstances = protoIndices.size(); if (numInstances == 0) { _LogAndSetError(instancerAttrMap, "Instancer has no prototype indices"); return; } for (auto protoIndex : protoIndices) { if (protoIndex < 0 || static_cast<size_t>(protoIndex) >= protoPaths.size()) { _LogAndSetError(instancerAttrMap, TfStringPrintf( "Out of range prototype index %d", protoIndex)); return; } } // Mask (optional) // std::vector<bool> pruneMaskValues = instancer.ComputeMaskAtTime(currentTime); if (!pruneMaskValues.empty() and pruneMaskValues.size() != numInstances) { _LogAndSetError(instancerAttrMap, "Mismatch in length of indices and mask"); return; } // Positions (required) // UsdAttribute positionsAttr = instancer.GetPositionsAttr(); if (!positionsAttr.HasValue()) { _LogAndSetError(instancerAttrMap, "Instancer has no positions"); return; } // // Compute instance transform matrices. // const double timeCodesPerSecond = stage->GetTimeCodesPerSecond(); // Gather frame-relative sample times and add them to the current time to // generate absolute sample times. // const std::vector<double> &motionSampleTimes = data.GetMotionSampleTimes(positionsAttr); const size_t sampleCount = motionSampleTimes.size(); std::vector<UsdTimeCode> sampleTimes(sampleCount); for (size_t a = 0; a < sampleCount; ++a) { sampleTimes[a] = UsdTimeCode(currentTime + motionSampleTimes[a]); } // Get velocityScale from the opArgs. // float velocityScale = FnKat::FloatAttribute( inputAttrs.getChildByName("opArgs.velocityScale")).getValue(1.0f, false); // XXX Replace with UsdGeomPointInstancer::ComputeInstanceTransformsAtTime. // std::vector<std::vector<GfMatrix4d>> xformSamples(sampleCount); const size_t numXformSamples = _ComputeInstanceTransformsAtTime(xformSamples, instancer, sampleTimes, UsdTimeCode(currentTime), timeCodesPerSecond, numInstances, positionsAttr, velocityScale); if (numXformSamples == 0) { _LogAndSetError(instancerAttrMap, "Could not compute " "sample/topology-invarying instance " "transform matrix"); return; } // // Compute prototype bounds. // bool aggregateBoundsValid = false; std::vector<double> aggregateBounds; // XXX Replace with UsdGeomPointInstancer::ComputeExtentAtTime. // VtVec3fArray aggregateExtent; if (_ComputeExtentAtTime( aggregateExtent, data.GetUsdInArgs(), xformSamples, motionSampleTimes, protoIndices, protoPaths, primCache, pruneMaskValues)) { aggregateBoundsValid = true; aggregateBounds.resize(6); aggregateBounds[0] = aggregateExtent[0][0]; // min x aggregateBounds[1] = aggregateExtent[1][0]; // max x aggregateBounds[2] = aggregateExtent[0][1]; // min y aggregateBounds[3] = aggregateExtent[1][1]; // max y aggregateBounds[4] = aggregateExtent[0][2]; // min z aggregateBounds[5] = aggregateExtent[1][2]; // max z } // // Build sources. Keep track of which instances use them. // FnGeolibServices::StaticSceneCreateOpArgsBuilder sourcesBldr(false); std::vector<int> instanceIndices; instanceIndices.reserve(numInstances); std::vector<std::string> instanceSources; instanceSources.reserve(protoPaths.size()); std::map<std::string, int> instanceSourceIndexMap; std::vector<int> omitList; omitList.reserve(numInstances); std::map<SdfPath, std::string> protoPathsToKatPaths; for (size_t i = 0; i < numInstances; ++i) { int index = protoIndices[i]; // Check to see if we are pruned. // bool isPruned = (!pruneMaskValues.empty() and pruneMaskValues[i] == false); if (isPruned) { omitList.push_back(i); } const SdfPath &protoPath = protoPaths[index]; // Compute the full (Katana) path to this prototype. // std::string fullProtoPath; std::map<SdfPath, std::string>::const_iterator pptkpIt = protoPathsToKatPaths.find(protoPath); if (pptkpIt != protoPathsToKatPaths.end()) { fullProtoPath = pptkpIt->second; } else { _PathToPrimMap::const_iterator pcIt = primCache.find(protoPath); const UsdPrim &protoPrim = pcIt->second; if (!protoPrim) { continue; } // Determine where (what path) to start building the prototype prim // such that its material bindings will be preserved. This could be // the prototype path itself or an ancestor path. // SdfPathVector commonPrefixes; UsdRelationship materialBindingsRel = UsdShadeMaterial::GetBindingRel(protoPrim); auto assetAPI = UsdModelAPI(protoPrim); std::string assetName; bool isReferencedModelPrim = assetAPI.IsModel() and assetAPI.GetAssetName(&assetName); if (!materialBindingsRel or isReferencedModelPrim) { // The prim has no material bindings or is a referenced model // prim (meaning that materials are defined below it); start // building at the prototype path. // commonPrefixes.push_back(protoPath); } else { SdfPathVector materialPaths; materialBindingsRel.GetForwardedTargets(&materialPaths); for (auto materialPath : materialPaths) { const SdfPath &commonPrefix = protoPath.GetCommonPrefix(materialPath); if (commonPrefix.GetString() == "/") { // XXX Unhandled case. // The prototype prim and its material are not under the // same parent; start building at the prototype path // (although it is likely that bindings will be broken). // commonPrefixes.push_back(protoPath); } else { // Start building at the common ancestor between the // prototype prim and its material. // commonPrefixes.push_back(commonPrefix); } } } // XXX Unhandled case. // We'll use the first common ancestor even if there is more than // one (which shouldn't appen if the prototype prim and its bindings // are under the same parent). // SdfPath::RemoveDescendentPaths(&commonPrefixes); const std::string buildPath = commonPrefixes[0].GetString(); // See if the path is a child of the point instancer. If so, we'll // match its hierarchy. If not, we'll put it under a 'prototypes' // group. // std::string relBuildPath; if (pystring::startswith(buildPath, instancerPath + "/")) { relBuildPath = pystring::replace( buildPath, instancerPath + "/", ""); } else { relBuildPath = "prototypes/" + FnGeolibUtil::Path::GetLeafName(buildPath); } // Start generating the full path to the prototype. // fullProtoPath = katOutputPath + "/" + relBuildPath; // Make the common ancestor our instance source. // sourcesBldr.setAttrAtLocation(relBuildPath, "type", FnKat::StringAttribute("instance source")); // Author a tracking attr. // sourcesBldr.setAttrAtLocation(relBuildPath, "info.usd.sourceUsdPath", FnKat::StringAttribute(buildPath)); // Tell the BuildIntermediate op to start building at the common // ancestor. // sourcesBldr.setAttrAtLocation(relBuildPath, "usdPrimPath", FnKat::StringAttribute(buildPath)); sourcesBldr.setAttrAtLocation(relBuildPath, "usdPrimName", FnKat::StringAttribute("geo")); if (protoPath.GetString() != buildPath) { // Finish generating the full path to the prototype. // fullProtoPath = fullProtoPath + "/geo" + pystring::replace( protoPath.GetString(), buildPath, ""); } // Create a mapping that will link the instance's index to its // prototype's full path. // instanceSourceIndexMap[fullProtoPath] = instanceSources.size(); instanceSources.push_back(fullProtoPath); // Finally, store the full path in the map so we won't have to do // this work again. // protoPathsToKatPaths[protoPath] = fullProtoPath; } instanceIndices.push_back(instanceSourceIndexMap[fullProtoPath]); } // // Build instances. // FnGeolibServices::StaticSceneCreateOpArgsBuilder instancesBldr(false); instancesBldr.createEmptyLocation("instances", "instance array"); instancesBldr.setAttrAtLocation("instances", "geometry.instanceSource", FnKat::StringAttribute(instanceSources, 1)); instancesBldr.setAttrAtLocation("instances", "geometry.instanceIndex", FnKat::IntAttribute(&instanceIndices[0], instanceIndices.size(), 1)); FnKat::DoubleBuilder instanceMatrixBldr(16); for (size_t a = 0; a < numXformSamples; ++a) { double relSampleTime = motionSampleTimes[a]; // Shove samples into the builder at the frame-relative sample time. If // motion is backwards, make sure to reverse time samples. std::vector<double> &matVec = instanceMatrixBldr.get( data.IsMotionBackward() ? PxrUsdKatanaUtils::ReverseTimeSample(relSampleTime) : relSampleTime); matVec.reserve(16 * numInstances); for (size_t i = 0; i < numInstances; ++i) { GfMatrix4d instanceXform = xformSamples[a][i]; const double *matArray = instanceXform.GetArray(); for (int j = 0; j < 16; ++j) { matVec.push_back(matArray[j]); } } } instancesBldr.setAttrAtLocation("instances", "geometry.instanceMatrix", instanceMatrixBldr.build()); if (!omitList.empty()) { instancesBldr.setAttrAtLocation("instances", "geometry.omitList", FnKat::IntAttribute(&omitList[0], omitList.size(), 1)); } instancesBldr.setAttrAtLocation("instances", "geometry.pointInstancerId", FnKat::StringAttribute(katOutputPath)); // // Transfer primvars. // FnKat::GroupBuilder instancerPrimvarsBldr; FnKat::GroupBuilder instancesPrimvarsBldr; for (int64_t i = 0; i < primvarAttrs.getNumberOfChildren(); ++i) { const std::string primvarName = primvarAttrs.getChildName(i); // Use "point" scope for the instancer. instancerPrimvarsBldr.set(primvarName, primvarAttrs.getChildByIndex(i)); instancerPrimvarsBldr.set(primvarName + ".scope", FnKat::StringAttribute("point")); // User "primitive" scope for the instances. instancesPrimvarsBldr.set(primvarName, primvarAttrs.getChildByIndex(i)); instancesPrimvarsBldr.set(primvarName + ".scope", FnKat::StringAttribute("primitive")); } instancerAttrMap.set("geometry.arbitrary", instancerPrimvarsBldr.build()); instancesBldr.setAttrAtLocation("instances", "geometry.arbitrary", instancesPrimvarsBldr.build()); // // Set the final aggregate bounds. // if (aggregateBoundsValid) { instancerAttrMap.set("bound", FnKat::DoubleAttribute(&aggregateBounds[0], 6, 2)); } // // Set proxy attrs. // instancerAttrMap.set("proxies", PxrUsdKatanaUtils::GetViewerProxyAttr(data)); // // Transfer builder results to our attr maps. // FnKat::GroupAttribute sourcesAttrs = sourcesBldr.build(); for (int64_t i = 0; i < sourcesAttrs.getNumberOfChildren(); ++i) { sourcesAttrMap.set( sourcesAttrs.getChildName(i), sourcesAttrs.getChildByIndex(i)); } FnKat::GroupAttribute instancesAttrs = instancesBldr.build(); for (int64_t i = 0; i < instancesAttrs.getNumberOfChildren(); ++i) { instancesAttrMap.set( instancesAttrs.getChildName(i), instancesAttrs.getChildByIndex(i)); } }
int ImagineRender::queueDataUpdates(FnKat::GroupAttribute updateAttribute) { // fprintf(stderr, "Queue updates...\n"); m_liveRenderState.lock(); if (!m_pRaytracer) { m_liveRenderState.unlock(); return 0; } m_liveRenderState.unlock(); unsigned int numItems = updateAttribute.getNumberOfChildren(); for (unsigned int i = 0; i < numItems; i++) { FnKat::GroupAttribute dataUpdateItemAttribute = updateAttribute.getChildByIndex(i); if (!dataUpdateItemAttribute.isValid()) continue; // fprintf(stderr, "\n\n%s\n\n", dataUpdateItemAttribute.getXML().c_str()); FnKat::StringAttribute typeAttribute = dataUpdateItemAttribute.getChildByName("type"); if (!typeAttribute.isValid()) continue; FnKat::StringAttribute locationAttribute = dataUpdateItemAttribute.getChildByName("location"); FnKat::GroupAttribute attributesAttribute = dataUpdateItemAttribute.getChildByName("attributes"); // fprintf(stderr, "\n\n%s\n\n", attributesAttribute.getXML().c_str()); bool partialUpdate = false; FnKat::StringAttribute partialUpdateAttribute = attributesAttribute.getChildByName("partialUpdate"); if (partialUpdateAttribute.isValid() && partialUpdateAttribute.getValue("", false) == "True") { partialUpdate = true; } std::string type = typeAttribute.getValue("", false); std::string location = locationAttribute.getValue("", false); if (type == "camera" && location == m_renderCameraLocation) { KatanaUpdateItem newUpdate(KatanaUpdateItem::eTypeCamera, KatanaUpdateItem::eLocCamera, location); FnKat::GroupAttribute xformAttribute = attributesAttribute.getChildByName("xform"); if (xformAttribute.isValid()) { LiveRenderHelpers::setUpdateXFormFromAttribute(xformAttribute, newUpdate); } FnKat::GroupAttribute geometryAttribute = attributesAttribute.getChildByName("geometry"); if (geometryAttribute.isValid()) { FnKat::DoubleAttribute fovAttribute = geometryAttribute.getChildByName("fov"); if (fovAttribute.isValid()) { double fovValue = fovAttribute.getValue(70.0, false); newUpdate.extra.add("fov", (float)fovValue); } FnKat::DoubleAttribute nearClipAttribute = geometryAttribute.getChildByName("near"); if (nearClipAttribute.isValid()) { double nearClipValue = nearClipAttribute.getValue(0.1, false); newUpdate.extra.add("nearClip", (float)nearClipValue); } } m_liveRenderState.addUpdate(newUpdate); } else if (type == "geoMaterial") { FnKat::GroupAttribute materialAttribute = attributesAttribute.getChildByName("material"); if (!materialAttribute.isValid()) continue; FnKat::StringAttribute shaderAttribute = materialAttribute.getChildByName("imagineSurfaceShader"); if (shaderAttribute.isValid()) { // we have a none-network material FnKat::GroupAttribute shaderParams = materialAttribute.getChildByName("imagineSurfaceParams"); // if all shader params are default, there won't be a group, so shaderParams will be invalid, but we can pass // this down anyway. So we don't need to check for the validity of shaderParams, as its possible non-existance // is okay. // extremely hacky for the moment... KatanaUpdateItem newUpdate(KatanaUpdateItem::eTypeObjectMaterial, KatanaUpdateItem::eLocObject, location); std::string shaderType = shaderAttribute.getValue("", false); Material* pNewMaterial = MaterialHelper::createNewMaterialStandAlone(shaderType, shaderParams); if (pNewMaterial) { newUpdate.pMaterial = pNewMaterial; m_liveRenderState.addUpdate(newUpdate); } continue; } FnKat::GroupAttribute nodesAttribute = materialAttribute.getChildByName("nodes"); if (nodesAttribute.isValid()) { MaterialHelper materialHelper(m_logger); // we have a network material Material* pNewMaterial = materialHelper.createNetworkMaterial(materialAttribute, false); if (pNewMaterial) { // extremely hacky for the moment... KatanaUpdateItem newUpdate(KatanaUpdateItem::eTypeObjectMaterial, KatanaUpdateItem::eLocObject, location); newUpdate.pMaterial = pNewMaterial; m_liveRenderState.addUpdate(newUpdate); } } } else if (type == "geo") { KatanaUpdateItem newUpdate(KatanaUpdateItem::eTypeObject, KatanaUpdateItem::eLocObject, location); bool changed = false; FnKat::GroupAttribute xformAttribute = attributesAttribute.getChildByName("xform"); if (xformAttribute.isValid()) { LiveRenderHelpers::setUpdateXFormFromAttribute(xformAttribute, newUpdate); changed = true; } FnKat::IntAttribute deletedAttribute = attributesAttribute.getChildByName("deleted"); if (deletedAttribute.isValid()) { int deletedValue = deletedAttribute.getValue(0, false); if (deletedValue == 1) { newUpdate.extra.add("deleted", true); changed = true; } } if (changed) { m_liveRenderState.addUpdate(newUpdate); } } else if (type == "light") { // fprintf(stderr, "\n\n%s\n\n", attributesAttribute.getXML().c_str()); KatanaUpdateItem newUpdate(KatanaUpdateItem::eTypeLight, KatanaUpdateItem::eLocLight, location); FnKat::GroupAttribute xformAttribute = attributesAttribute.getChildByName("xform"); if (xformAttribute.isValid()) { LiveRenderHelpers::setUpdateXFormFromAttribute(xformAttribute, newUpdate); } FnKat::GroupAttribute materialAttribute = attributesAttribute.getChildByName("material"); if (materialAttribute.isValid()) { FnKat::GroupAttribute shaderParamsAttribute = materialAttribute.getChildByName("imagineLightParams"); if (shaderParamsAttribute.isValid()) { KatanaAttributeHelper helper(shaderParamsAttribute); float intensity = helper.getFloatParam("intensity", 1.0f); newUpdate.extra.add("intensity", intensity); float exposure = helper.getFloatParam("exposure", 1.0f); newUpdate.extra.add("exposure", exposure); } } FnKat::IntAttribute muteAttribute = attributesAttribute.getChildByName("mute"); if (muteAttribute.isValid()) { int muteValue = muteAttribute.getValue(0, false); newUpdate.extra.add("muted", (bool)muteValue); } m_liveRenderState.addUpdate(newUpdate); } } return 0; }