/* -*-c++-*- * Copyright (C) 2009 Cedric Pinson * Copyright (C) 2017 Julien Valentin * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include #include #include #include using namespace osgAnimation; #define DEFAULT_FIRST_VERTATTRIB_TARGETTED 11 RigTransformHardware::RigTransformHardware(): _bonesPerVertex (0), _nbVertices (0), _needInit (true), _minAttribIndex(DEFAULT_FIRST_VERTATTRIB_TARGETTED) {} RigTransformHardware::RigTransformHardware(const RigTransformHardware& rth, const osg::CopyOp& copyop): RigTransform(rth, copyop), _bonesPerVertex(rth._bonesPerVertex), _nbVertices(rth._nbVertices), _bonePalette(rth._bonePalette), _boneNameToPalette(rth._boneNameToPalette), _boneWeightAttribArrays(rth._boneWeightAttribArrays), _uniformMatrixPalette(rth._uniformMatrixPalette), _shader(rth._shader), _needInit(rth._needInit), _minAttribIndex(rth._minAttribIndex) { } osg::Vec4Array* RigTransformHardware::getVertexAttrib(unsigned int index) { if (index >= _boneWeightAttribArrays.size()) return 0; return _boneWeightAttribArrays[index].get(); } void RigTransformHardware::computeMatrixPaletteUniform(const osg::Matrix& transformFromSkeletonToGeometry, const osg::Matrix& invTransformFromSkeletonToGeometry) { for (unsigned int i = 0; i < _bonePalette.size(); ++i) { osg::ref_ptr bone = _bonePalette[i].get(); const osg::Matrixf& invBindMatrix = bone->getInvBindMatrixInSkeletonSpace(); const osg::Matrixf& boneMatrix = bone->getMatrixInSkeletonSpace(); osg::Matrixf resultBoneMatrix = invBindMatrix * boneMatrix; osg::Matrixf result = transformFromSkeletonToGeometry * resultBoneMatrix * invTransformFromSkeletonToGeometry; if (!_uniformMatrixPalette->setElement(i, result)) OSG_WARN << "RigTransformHardware::computeUniformMatrixPalette can't set uniform at " << i << " elements" << std::endl; } } // // create vertex attribute by 2 bones // vec4(boneIndex0, weight0, boneIndex1, weight1) // if more bones are needed then other attributes are created // vec4(boneIndex2, weight2, boneIndex3, weight3) // the idea is to use this format to have a granularity smaller // than the 4 bones using two vertex attributes // typedef std::vector > PerVertexInfList; ///create normalized a set of Vertex Attribs given a PerVertexInfList and return the max num bone per vertex unsigned int createVertexAttribList(const PerVertexInfList & perVertexInfluences, RigTransformHardware::BoneWeightAttribList& boneWeightAttribArrays){ short boneIndexInVec4; unsigned int vertid = 0, boneIndexInList; IndexWeightList::size_type maxBonePerVertex = 0; ///build vertex attrib arrays //get maxBonePerVertex for(PerVertexInfList::const_iterator vertinfit = perVertexInfluences.begin(); vertinfit != perVertexInfluences.end(); ++vertinfit) maxBonePerVertex = osg::maximum(maxBonePerVertex, vertinfit->size()); OSG_INFO << "RigTransformHardware::createVertexAttribList maximum number of bone per vertex is " << maxBonePerVertex << std::endl; unsigned int nbArray = static_cast(ceilf( ((float)maxBonePerVertex) * 0.5f)); if (!nbArray) return 0; ///create vertex attrib arrays boneWeightAttribArrays.resize(nbArray); for(unsigned int j=0; j< nbArray; ++j) { boneWeightAttribArrays[j] = new osg::Vec4Array(osg::Array::BIND_PER_VERTEX); boneWeightAttribArrays[j]->resize(perVertexInfluences.size()); } ///populate vertex attrib arrays for(PerVertexInfList::const_iterator vertinfit=perVertexInfluences.begin(); vertinfit != perVertexInfluences.end(); ++vertinfit,++vertid) { //sum for normalization float sum=0; for(IndexWeightList::const_iterator iwit = vertinfit->begin(); iwit != vertinfit->end(); ++iwit) sum+=iwit->second; if(sum< 1e-4) { OSG_WARN << "RigTransformHardware::buildPalette Warning: vertex with zero sum weights: " <((*vertinfit)[boneIndexInList].first); ///normalization here float boneWeight = (*vertinfit)[boneIndexInList].second*sum; dest[0 + boneIndexInVec4] = boneIndex; dest[1 + boneIndexInVec4] = boneWeight; } else { dest[0 + boneIndexInVec4] = 0; dest[1 + boneIndexInVec4] = 0; } } } } } return maxBonePerVertex; } bool RigTransformHardware::prepareData(RigGeometry& rig) { if(!rig.getSkeleton() && !rig.getParents().empty()) { RigGeometry::FindNearestParentSkeleton finder; if(rig.getParents().size() > 1) osg::notify(osg::WARN) << "A RigGeometry should not have multi parent ( " << rig.getName() << " )" << std::endl; rig.getParents()[0]->accept(finder); if(!finder._root.valid()) { osg::notify(osg::WARN) << "A RigGeometry did not find a parent skeleton for RigGeometry ( " << rig.getName() << " )" << std::endl; return false; } rig.setSkeleton(finder._root.get()); } if(!rig.getSkeleton()) return false; BoneMapVisitor mapVisitor; rig.getSkeleton()->accept(mapVisitor); BoneMap boneMap = mapVisitor.getBoneMap(); if (!buildPalette(boneMap,rig) ) return false; osg::Geometry& source = *rig.getSourceGeometry(); osg::Vec3Array* positionSrc = dynamic_cast(source.getVertexArray()); if (!positionSrc) { OSG_WARN << "RigTransformHardware no vertex array in the geometry " << rig.getName() << std::endl; return false; } // copy shallow from source geometry to rig rig.copyFrom(source); return true; } bool RigTransformHardware::buildPalette(const BoneMap&boneMap,const RigGeometry&rig) { typedef std::map BoneNameCountMap; _nbVertices = rig.getVertexArray()->getNumElements(); _boneWeightAttribArrays.resize(0); _bonePalette.clear(); _boneNameToPalette.clear(); IndexWeightList::size_type maxBonePerVertex=0; BoneNameCountMap boneNameCountMap; const VertexInfluenceMap &vertexInfluenceMap = *rig.getInfluenceMap(); BoneNamePaletteIndex::iterator boneName2PaletteIndex; // init temp vertex attribute data std::vector perVertexInfluences; perVertexInfluences.resize(_nbVertices); unsigned int paletteindex; for (VertexInfluenceMap::const_iterator boneinflistit = vertexInfluenceMap.begin(); boneinflistit != vertexInfluenceMap.end(); ++boneinflistit) { const IndexWeightList& boneinflist = boneinflistit->second; const std::string& bonename = boneinflistit->first; BoneMap::const_iterator bonebyname; if ((bonebyname = boneMap.find(bonename)) == boneMap.end()) { OSG_WARN << "RigTransformHardware::buildPalette can't find bone " << bonename << "in skeleton bonemap: skip this influence" << std::endl; continue; } if ((boneName2PaletteIndex= _boneNameToPalette.find(bonename)) != _boneNameToPalette.end()) { boneNameCountMap[bonename]++; paletteindex= boneName2PaletteIndex->second ; } else { boneNameCountMap[bonename] = 1; // for stats _boneNameToPalette[bonename] = _bonePalette.size() ; paletteindex= _bonePalette.size() ; _bonePalette.push_back(bonebyname->second); } for(IndexWeightList::const_iterator infit = boneinflist.begin(); infit!=boneinflist.end(); ++infit) { const VertexIndexWeight& iw = *infit; const unsigned int &index = iw.first; const float &weight = iw.second; IndexWeightList & iwlist = perVertexInfluences[index]; if(fabs(weight) > 1e-4) // don't use bone with weight too small { iwlist.push_back(VertexIndexWeight(paletteindex,weight)); } else { OSG_WARN << "RigTransformHardware::buildPalette Bone " << bonename << " has a weight " << weight << " for vertex " << index << " this bone will not be in the palette" << std::endl; } } OSG_INFO << "RigTransformHardware::buildPalette matrix palette has " << boneNameCountMap.size() << " entries" << std::endl; for (BoneNameCountMap::iterator it = boneNameCountMap.begin(); it != boneNameCountMap.end(); ++it) { OSG_INFO << "RigTransformHardware::buildPalette Bone " << it->first << " is used " << it->second << " times" << std::endl; } OSG_INFO << "RigTransformHardware::buildPalette will use " << boneNameCountMap.size() * 4 << " uniforms" << std::endl; } if( (_bonesPerVertex = createVertexAttribList(perVertexInfluences, _boneWeightAttribArrays) ) < 1 ) return false; _uniformMatrixPalette = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "matrixPalette", _bonePalette.size()); _needInit = true; return true; } bool RigTransformHardware::init(RigGeometry& rig) { //if animdata seams prepared if(_uniformMatrixPalette.valid()) { osg::ref_ptr program ; osg::ref_ptr vertexshader; osg::ref_ptr stateset = rig.getOrCreateStateSet(); //grab geom source program and vertex shader if _shader is not setted if(!_shader.valid() && (program = (osg::Program*)stateset->getAttribute(osg::StateAttribute::PROGRAM))) { for(unsigned int i=0; igetNumShaders(); ++i) if(program->getShader(i)->getType()==osg::Shader::VERTEX) { vertexshader=program->getShader(i); program->removeShader(vertexshader); } } else { program = new osg::Program; program->setName("HardwareSkinning"); } //set default source if _shader is not user setted if (!vertexshader.valid()) { if (!_shader.valid()) vertexshader = osg::Shader::readShaderFile(osg::Shader::VERTEX,"skinning.vert"); else vertexshader=_shader; } if (!vertexshader.valid()) { OSG_WARN << "RigTransformHardware can't load VertexShader" << std::endl; return false; } // replace max matrix by the value from uniform { std::string str = vertexshader->getShaderSource(); std::string toreplace = std::string("MAX_MATRIX"); std::size_t start = str.find(toreplace); if (std::string::npos != start) { std::stringstream ss; ss << getMatrixPaletteUniform()->getNumElements(); str.replace(start, toreplace.size(), ss.str()); vertexshader->setShaderSource(str); } else { OSG_WARN<< "MAX_MATRIX not found in Shader! " << str << std::endl; } OSG_INFO << "Shader " << str << std::endl; } unsigned int nbAttribs = getNumVertexAttrib(); for (unsigned int i = 0; i < nbAttribs; i++) { std::stringstream ss; ss << "boneWeight" << i; program->addBindAttribLocation(ss.str(), _minAttribIndex + i); rig.setVertexAttribArray(_minAttribIndex + i, getVertexAttrib(i)); OSG_INFO << "set vertex attrib " << ss.str() << std::endl; } program->addShader(vertexshader.get()); stateset->removeUniform("nbBonesPerVertex"); stateset->addUniform(new osg::Uniform("nbBonesPerVertex",_bonesPerVertex)); stateset->removeUniform("matrixPalette"); stateset->addUniform(_uniformMatrixPalette); stateset->setAttribute(program.get()); _needInit = false; return true; } else prepareData(rig); return false; } void RigTransformHardware::operator()(RigGeometry& geom) { if (_needInit) if (!init(geom)) return; computeMatrixPaletteUniform(geom.getMatrixFromSkeletonToGeometry(), geom.getInvMatrixFromSkeletonToGeometry()); }