// // Model.cpp // interface // // Created by Andrzej Kapolka on 10/18/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #include #include #include #include #include #include #include "Application.h" #include "Model.h" #include #include #include using namespace std; static int modelPointerTypeId = qRegisterMetaType >(); static int weakNetworkGeometryPointerTypeId = qRegisterMetaType >(); static int vec3VectorTypeId = qRegisterMetaType >(); Model::Model(QObject* parent) : QObject(parent), _scale(1.0f, 1.0f, 1.0f), _shapesAreDirty(true), _boundingRadius(0.f), _boundingShape(), _boundingShapeLocalOffset(0.f), _lodDistance(0.0f), _pupilDilation(0.0f) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } Model::~Model() { deleteGeometry(); } ProgramObject Model::_program; ProgramObject Model::_normalMapProgram; ProgramObject Model::_shadowProgram; ProgramObject Model::_skinProgram; ProgramObject Model::_skinNormalMapProgram; ProgramObject Model::_skinShadowProgram; int Model::_normalMapTangentLocation; Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { glm::vec3 deltaScale = _scale - scale; if (glm::length2(deltaScale) > EPSILON) { _scale = scale; rebuildShapes(); } } void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); locations.clusterIndices = program.attributeLocation("clusterIndices"); locations.clusterWeights = program.attributeLocation("clusterWeights"); locations.tangent = program.attributeLocation("tangent"); program.setUniformValue("diffuseMap", 0); program.setUniformValue("normalMap", 1); program.release(); } QVector Model::createJointStates(const FBXGeometry& geometry) { QVector jointStates; foreach (const FBXJoint& joint, geometry.joints) { JointState state; state.translation = joint.translation; state.rotation = joint.rotation; jointStates.append(state); } // compute transforms // Unfortunately, the joints are not neccessarily in order from parents to children, // so we must iterate over the list multiple times until all are set correctly. QVector jointIsSet; int numJoints = jointStates.size(); jointIsSet.fill(false, numJoints); int numJointsSet = 0; int lastNumJointsSet = -1; while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { lastNumJointsSet = numJointsSet; for (int i = 0; i < numJoints; ++i) { if (jointIsSet[i]) { continue; } JointState& state = jointStates[i]; const FBXJoint& joint = geometry.joints[i]; int parentIndex = joint.parentIndex; if (parentIndex == -1) { glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = _rotation * combinedRotation; ++numJointsSet; jointIsSet[i] = true; } else if (jointIsSet[parentIndex]) { const JointState& parentState = jointStates.at(parentIndex); glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = parentState.combinedRotation * combinedRotation; ++numJointsSet; jointIsSet[i] = true; } } } return jointStates; } void Model::init() { if (!_program.isLinked()) { _program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model.vert"); _program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag"); _program.link(); _program.bind(); _program.setUniformValue("texture", 0); _program.release(); _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_normal_map.vert"); _normalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_normal_map.frag"); _normalMapProgram.link(); _normalMapProgram.bind(); _normalMapProgram.setUniformValue("diffuseMap", 0); _normalMapProgram.setUniformValue("normalMap", 1); _normalMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); _normalMapProgram.release(); _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert"); _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_shadow.frag"); _shadowProgram.link(); _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model.vert"); _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag"); _skinProgram.link(); initSkinProgram(_skinProgram, _skinLocations); _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_normal_map.frag"); _skinNormalMapProgram.link(); initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations); _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model_shadow.vert"); _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_shadow.frag"); _skinShadowProgram.link(); initSkinProgram(_skinShadowProgram, _skinShadowLocations); } } void Model::reset() { if (_jointStates.isEmpty()) { return; } foreach (Model* attachment, _attachments) { attachment->reset(); } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].rotation = geometry.joints.at(i).rotation; } } bool Model::updateGeometry() { // NOTE: this is a recursive call that walks all attachments, and their attachments bool needFullUpdate = false; for (int i = 0; i < _attachments.size(); i++) { Model* model = _attachments.at(i); if (model->updateGeometry()) { needFullUpdate = true; } } bool needToRebuild = false; if (_nextGeometry) { _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); _nextGeometry->setLoadPriority(this, -_lodDistance); _nextGeometry->ensureLoading(); if (_nextGeometry->isLoaded()) { applyNextGeometry(); needToRebuild = true; } } if (!_geometry) { // geometry is not ready return false; } QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); if (_geometry != geometry) { // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. const FBXGeometry& newGeometry = geometry->getFBXGeometry(); QVector newJointStates = createJointStates(newGeometry); if (! _jointStates.isEmpty()) { // copy the existing joint states const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); it != oldGeometry.jointIndices.constEnd(); it++) { int oldIndex = it.value() - 1; int newIndex = newGeometry.getJointIndex(it.key()); if (newIndex != -1) { newJointStates[newIndex] = _jointStates.at(oldIndex); } } } deleteGeometry(); _dilatedTextures.clear(); _geometry = geometry; _jointStates = newJointStates; needToRebuild = true; } else if (_jointStates.isEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { _jointStates = createJointStates(fbxGeometry); needToRebuild = true; } } _geometry->setLoadPriority(this, -_lodDistance); _geometry->ensureLoading(); if (needToRebuild) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.append(state); QOpenGLBuffer buffer; if (!mesh.blendshapes.isEmpty()) { buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); buffer.create(); buffer.bind(); buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3)); buffer.release(); } _blendedVertexBuffers.append(buffer); } foreach (const FBXAttachment& attachment, fbxGeometry.attachments) { Model* model = new Model(this); model->init(); model->setURL(attachment.url); _attachments.append(model); } rebuildShapes(); needFullUpdate = true; } return needFullUpdate; } bool Model::render(float alpha, RenderMode mode) { // render the attachments foreach (Model* attachment, _attachments) { attachment->render(alpha, mode); } if (_meshStates.isEmpty()) { return false; } // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (_dilatedTextures.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { QVector > dilated; dilated.resize(mesh.parts.size()); _dilatedTextures.append(dilated); } } glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glDisable(GL_COLOR_MATERIAL); if (mode == DIFFUSE_RENDER_MODE || mode == NORMAL_RENDER_MODE) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); } // render opaque meshes with alpha testing glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); renderMeshes(alpha, mode, false); glDisable(GL_ALPHA_TEST); // render translucent meshes afterwards renderMeshes(alpha, mode, true); glDisable(GL_CULL_FACE); // deactivate vertex arrays after drawing glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); // bind with 0 to switch back to normal operation glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); // restore all the default material settings Application::getInstance()->setupWorldLight(); return true; } Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); } const Extents& bindExtents = _geometry->getFBXGeometry().bindExtents; Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale }; return scaledExtents; } bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; } rotation = _jointStates.at(index).rotation; const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation; return glm::abs(rotation.x - defaultRotation.x) >= EPSILON || glm::abs(rotation.y - defaultRotation.y) >= EPSILON || glm::abs(rotation.z - defaultRotation.z) >= EPSILON || glm::abs(rotation.w - defaultRotation.w) >= EPSILON; } void Model::setJointState(int index, bool valid, const glm::quat& rotation) { if (index != -1 && index < _jointStates.size()) { _jointStates[index].rotation = valid ? rotation : _geometry->getFBXGeometry().joints.at(index).rotation; } } int Model::getParentJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).parentIndex : -1; } int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } bool Model::getHeadPosition(glm::vec3& headPosition) const { return isActive() && getJointPosition(_geometry->getFBXGeometry().headJointIndex, headPosition); } bool Model::getNeckPosition(glm::vec3& neckPosition) const { return isActive() && getJointPosition(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } bool Model::getNeckRotation(glm::quat& neckRotation) const { return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation); } bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); return getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && getJointPosition(geometry.rightEyeJointIndex, secondEyePosition); } bool Model::getLeftHandPosition(glm::vec3& position) const { return getJointPosition(getLeftHandJointIndex(), position); } bool Model::getLeftHandRotation(glm::quat& rotation) const { return getJointRotation(getLeftHandJointIndex(), rotation); } bool Model::getRightHandPosition(glm::vec3& position) const { return getJointPosition(getRightHandJointIndex(), position); } bool Model::getRightHandRotation(glm::quat& rotation) const { return getJointRotation(getRightHandJointIndex(), rotation); } bool Model::restoreLeftHandPosition(float percent) { return restoreJointPosition(getLeftHandJointIndex(), percent); } bool Model::getLeftShoulderPosition(glm::vec3& position) const { return getJointPosition(getLastFreeJointIndex(getLeftHandJointIndex()), position); } float Model::getLeftArmLength() const { return getLimbLength(getLeftHandJointIndex()); } bool Model::restoreRightHandPosition(float percent) { return restoreJointPosition(getRightHandJointIndex(), percent); } bool Model::getRightShoulderPosition(glm::vec3& position) const { return getJointPosition(getLastFreeJointIndex(getRightHandJointIndex()), position); } float Model::getRightArmLength() const { return getLimbLength(getRightHandJointIndex()); } void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) { // don't recreate the geometry if it's the same URL if (_url == url) { return; } _url = url; // if so instructed, keep the current geometry until the new one is loaded _nextBaseGeometry = _nextGeometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback, delayLoad); _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; if (!retainCurrent || !isActive() || _nextGeometry->isLoaded()) { applyNextGeometry(); } } void Model::clearShapes() { for (int i = 0; i < _jointShapes.size(); ++i) { delete _jointShapes[i]; } _jointShapes.clear(); } void Model::rebuildShapes() { clearShapes(); if (_jointStates.isEmpty()) { return; } // make sure all the joints are updated correctly before we try to create their shapes for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } const FBXGeometry& geometry = _geometry->getFBXGeometry(); float uniformScale = extractUniformScale(_scale); glm::quat inverseRotation = glm::inverse(_rotation); glm::vec3 rootPosition(0.f); // joint shapes Extents totalExtents; totalExtents.reset(); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; Extents shapeExtents; shapeExtents.reset(); if (joint.parentIndex == -1) { rootPosition = worldPosition; } float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) { CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); capsule->setPosition(worldPosition); capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); _jointShapes.push_back(capsule); glm::vec3 endPoint; capsule->getEndPoint(endPoint); glm::vec3 startPoint; capsule->getStartPoint(startPoint); glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); shapeExtents.addPoint(worldPosition + axis); shapeExtents.addPoint(worldPosition - axis); } else { SphereShape* sphere = new SphereShape(radius, worldPosition); _jointShapes.push_back(sphere); glm::vec3 axis = glm::vec3(radius); shapeExtents.addPoint(worldPosition + axis); shapeExtents.addPoint(worldPosition - axis); } totalExtents.addExtents(shapeExtents); } // bounding shape // NOTE: we assume that the longest side of totalExtents is the yAxis glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum; float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); } void Model::updateShapePositions() { if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { glm::vec3 rootPosition(0.f); _boundingRadius = 0.f; float uniformScale = extractUniformScale(_scale); const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; // shape position and rotation need to be in world-frame glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; _jointShapes[i]->setPosition(worldPosition); _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); float distance2 = glm::distance2(worldPosition, _translation); if (distance2 > _boundingRadius) { _boundingRadius = distance2; } if (joint.parentIndex == -1) { rootPosition = worldPosition; } } _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); } } bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { const glm::vec3 relativeOrigin = origin - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); float minDistance = FLT_MAX; float radiusScale = extractUniformScale(_scale); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; glm::vec3 end = extractTranslation(_jointStates[i].transform); float endRadius = joint.boneRadius * radiusScale; glm::vec3 start = end; float startRadius = joint.boneRadius * radiusScale; if (joint.parentIndex != -1) { start = extractTranslation(_jointStates[joint.parentIndex].transform); startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale; } // for now, use average of start and end radii float capsuleDistance; if (findRayCapsuleIntersection(relativeOrigin, direction, start, end, (startRadius + endRadius) / 2.0f, capsuleDistance)) { minDistance = qMin(minDistance, capsuleDistance); } } if (minDistance < FLT_MAX) { distance = minDistance; return true; } return false; } bool Model::findCollisions(const QVector shapes, CollisionList& collisions) { bool collided = false; for (int i = 0; i < shapes.size(); ++i) { const Shape* theirShape = shapes[i]; for (int j = 0; j < _jointShapes.size(); ++j) { const Shape* ourShape = _jointShapes[j]; if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) { collided = true; } } } return collided; } bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions, int skipIndex) { bool collided = false; SphereShape sphere(sphereRadius, sphereCenter); const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointShapes.size(); i++) { const FBXJoint& joint = geometry.joints[i]; if (joint.parentIndex != -1) { if (skipIndex != -1) { int ancestorIndex = joint.parentIndex; do { if (ancestorIndex == skipIndex) { goto outerContinue; } ancestorIndex = geometry.joints[ancestorIndex].parentIndex; } while (ancestorIndex != -1); } } if (ShapeCollider::shapeShape(&sphere, _jointShapes[i], collisions)) { CollisionInfo* collision = collisions.getLastCollision(); collision->_type = MODEL_COLLISION; collision->_data = (void*)(this); collision->_flags = i; collided = true; } outerContinue: ; } return collided; } class Blender : public QRunnable { public: Blender(Model* model, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients); virtual void run(); private: QPointer _model; QWeakPointer _geometry; QVector _meshes; QVector _blendshapeCoefficients; }; Blender::Blender(Model* model, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients) : _model(model), _geometry(geometry), _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { // make sure the model/geometry still exists if (_model.isNull() || _geometry.isNull()) { return; } QVector vertices, normals; int offset = 0; foreach (const FBXMesh& mesh, _meshes) { if (mesh.blendshapes.isEmpty()) { continue; } vertices += mesh.vertices; normals += mesh.normals; glm::vec3* meshVertices = vertices.data() + offset; glm::vec3* meshNormals = normals.data() + offset; offset += mesh.vertices.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); if (vertexCoefficient < EPSILON) { continue; } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; const FBXBlendshape& blendshape = mesh.blendshapes.at(i); for (int j = 0; j < blendshape.indices.size(); j++) { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; } } } // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices", Q_ARG(const QPointer&, _model), Q_ARG(const QWeakPointer&, _geometry), Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } void Model::simulate(float deltaTime, bool fullUpdate) { fullUpdate = updateGeometry() || fullUpdate; if (isActive() && fullUpdate) { simulateInternal(deltaTime); } } void Model::simulateInternal(float deltaTime) { // NOTE: this is a recursive call that walks all attachments, and their attachments // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } _shapesAreDirty = true; const FBXGeometry& geometry = _geometry->getFBXGeometry(); // update the attachment transforms and simulate them for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); Model* model = _attachments.at(i); glm::vec3 jointTranslation = _translation; glm::quat jointRotation = _rotation; getJointPosition(attachment.jointIndex, jointTranslation); getJointRotation(attachment.jointIndex, jointRotation); model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); model->setScale(_scale * attachment.scale); if (model->isActive()) { model->simulateInternal(deltaTime); } } for (int i = 0; i < _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix; } } // post the blender if (geometry.hasBlendedMeshes()) { QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients)); } } void Model::updateJointState(int index) { JointState& state = _jointStates[index]; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXJoint& joint = geometry.joints.at(index); if (joint.parentIndex == -1) { glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = _rotation * combinedRotation; } else { const JointState& parentState = _jointStates.at(joint.parentIndex); if (index == geometry.leanJointIndex) { maybeUpdateLeanRotation(parentState, joint, state); } else if (index == geometry.neckJointIndex) { maybeUpdateNeckRotation(parentState, joint, state); } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { maybeUpdateEyeRotation(parentState, joint, state); } glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = parentState.combinedRotation * combinedRotation; } } void Model::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // nothing by default } void Model::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // nothing by default } void Model::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // nothing by default } bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } position = _translation + extractTranslation(_jointStates[jointIndex].transform); return true; } bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } rotation = _jointStates[jointIndex].combinedRotation * (fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation : _geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation); return true; } bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } glm::vec3 relativePosition = position - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; if (freeLineage.isEmpty()) { return false; } if (lastFreeIndex == -1) { lastFreeIndex = freeLineage.last(); } // this is a cyclic coordinate descent algorithm: see // http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d const int ITERATION_COUNT = 1; glm::vec3 worldAlignment = _rotation * alignment; for (int i = 0; i < ITERATION_COUNT; i++) { // first, we go from the joint upwards, rotating the end as close as possible to the target glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].transform); for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) { int index = freeLineage.at(j); const FBXJoint& joint = geometry.joints.at(index); if (!(joint.isFree || allIntermediatesFree)) { continue; } JointState& state = _jointStates[index]; glm::vec3 jointPosition = extractTranslation(state.transform); glm::vec3 jointVector = endPosition - jointPosition; glm::quat oldCombinedRotation = state.combinedRotation; applyRotationDelta(index, rotationBetween(jointVector, relativePosition - jointPosition)); endPosition = state.combinedRotation * glm::inverse(oldCombinedRotation) * jointVector + jointPosition; if (alignment != glm::vec3() && j > 1) { jointVector = endPosition - jointPosition; glm::vec3 positionSum; for (int k = j - 1; k > 0; k--) { int index = freeLineage.at(k); updateJointState(index); positionSum += extractTranslation(_jointStates.at(index).transform); } glm::vec3 projectedCenterOfMass = glm::cross(jointVector, glm::cross(positionSum / (j - 1.0f) - jointPosition, jointVector)); glm::vec3 projectedAlignment = glm::cross(jointVector, glm::cross(worldAlignment, jointVector)); const float LENGTH_EPSILON = 0.001f; if (glm::length(projectedCenterOfMass) > LENGTH_EPSILON && glm::length(projectedAlignment) > LENGTH_EPSILON) { applyRotationDelta(index, rotationBetween(projectedCenterOfMass, projectedAlignment)); } } } } // now update the joint states from the top for (int j = freeLineage.size() - 1; j >= 0; j--) { updateJointState(freeLineage.at(j)); } _shapesAreDirty = true; return true; } bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } JointState& state = _jointStates[jointIndex]; state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); return true; } void Model::setJointTranslation(int jointIndex, const glm::vec3& translation) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXJoint& joint = geometry.joints.at(jointIndex); glm::mat4 parentTransform; if (joint.parentIndex == -1) { parentTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; } else { parentTransform = _jointStates.at(joint.parentIndex).transform; } JointState& state = _jointStates[jointIndex]; glm::vec3 preTranslation = extractTranslation(joint.preTransform * glm::mat4_cast(joint.preRotation * state.rotation * joint.postRotation) * joint.postTransform); state.translation = glm::vec3(glm::inverse(parentTransform) * glm::vec4(translation, 1.0f)) - preTranslation; } bool Model::restoreJointPosition(int jointIndex, float percent) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; foreach (int index, freeLineage) { JointState& state = _jointStates[index]; const FBXJoint& joint = geometry.joints.at(index); state.rotation = safeMix(state.rotation, joint.rotation, percent); state.translation = glm::mix(state.translation, joint.translation, percent); } return true; } float Model::getLimbLength(int jointIndex) const { if (jointIndex == -1 || _jointStates.isEmpty()) { return 0.0f; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; float length = 0.0f; float lengthScale = (_scale.x + _scale.y + _scale.z) / 3.0f; for (int i = freeLineage.size() - 2; i >= 0; i--) { length += geometry.joints.at(freeLineage.at(i)).distanceToParent * lengthScale; } return length; } void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain) { JointState& state = _jointStates[jointIndex]; const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (!constrain || (joint.rotationMin == glm::vec3(-PI, -PI, -PI) && joint.rotationMax == glm::vec3(PI, PI, PI))) { // no constraints state.rotation = state.rotation * glm::inverse(state.combinedRotation) * delta * state.combinedRotation; state.combinedRotation = delta * state.combinedRotation; return; } glm::quat newRotation = glm::quat(glm::clamp(safeEulerAngles(state.rotation * glm::inverse(state.combinedRotation) * delta * state.combinedRotation), joint.rotationMin, joint.rotationMax)); state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; state.rotation = newRotation; } const int BALL_SUBDIVISIONS = 10; void Model::renderJointCollisionShapes(float alpha) { glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); for (int i = 0; i < _jointShapes.size(); i++) { glPushMatrix(); Shape* shape = _jointShapes[i]; if (shape->getType() == Shape::SPHERE_SHAPE) { // shapes are stored in world-frame, so we have to transform into model frame glm::vec3 position = shape->getPosition() - _translation; glTranslatef(position.x, position.y, position.z); const glm::quat& rotation = shape->getRotation(); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); // draw a grey sphere at shape position glColor4f(0.75f, 0.75f, 0.75f, alpha); glutSolidSphere(shape->getBoundingRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); } else if (shape->getType() == Shape::CAPSULE_SHAPE) { CapsuleShape* capsule = static_cast(shape); // draw a blue sphere at the capsule endpoint glm::vec3 endPoint; capsule->getEndPoint(endPoint); endPoint = endPoint - _translation; glTranslatef(endPoint.x, endPoint.y, endPoint.z); glColor4f(0.6f, 0.6f, 0.8f, alpha); glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a yellow sphere at the capsule startpoint glm::vec3 startPoint; capsule->getStartPoint(startPoint); startPoint = startPoint - _translation; glm::vec3 axis = endPoint - startPoint; glTranslatef(-axis.x, -axis.y, -axis.z); glColor4f(0.8f, 0.8f, 0.6f, alpha); glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a green cylinder between the two points glm::vec3 origin(0.f); glColor4f(0.6f, 0.8f, 0.6f, alpha); Avatar::renderJointConnectingCone( origin, axis, capsule->getRadius(), capsule->getRadius()); } glPopMatrix(); } glPopMatrix(); } void Model::renderBoundingCollisionShapes(float alpha) { glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); // draw a blue sphere at the capsule endpoint glm::vec3 endPoint; _boundingShape.getEndPoint(endPoint); endPoint = endPoint - _translation; glTranslatef(endPoint.x, endPoint.y, endPoint.z); glColor4f(0.6f, 0.6f, 0.8f, alpha); glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a yellow sphere at the capsule startpoint glm::vec3 startPoint; _boundingShape.getStartPoint(startPoint); startPoint = startPoint - _translation; glm::vec3 axis = endPoint - startPoint; glTranslatef(-axis.x, -axis.y, -axis.z); glColor4f(0.8f, 0.8f, 0.6f, alpha); glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a green cylinder between the two points glm::vec3 origin(0.f); glColor4f(0.6f, 0.8f, 0.6f, alpha); Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius()); glPopMatrix(); } bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const { if (collision._type == MODEL_COLLISION) { // the joint is pokable by a collision if it exists and is free to move const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags]; if (joint.parentIndex == -1 || _jointStates.isEmpty()) { return false; } // an empty freeLineage means the joint can't move const FBXGeometry& geometry = _geometry->getFBXGeometry(); int jointIndex = collision._flags; const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; return !freeLineage.isEmpty(); } return false; } void Model::applyCollision(CollisionInfo& collision) { if (collision._type != MODEL_COLLISION) { return; } glm::vec3 jointPosition(0.f); int jointIndex = collision._flags; if (getJointPosition(jointIndex, jointPosition)) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (joint.parentIndex != -1) { // compute the approximate distance (travel) that the joint needs to move glm::vec3 start; getJointPosition(joint.parentIndex, start); glm::vec3 contactPoint = collision._contactPoint - start; glm::vec3 penetrationEnd = contactPoint + collision._penetration; glm::vec3 axis = glm::cross(contactPoint, penetrationEnd); float travel = glm::length(axis); const float MIN_TRAVEL = 1.0e-8f; if (travel > MIN_TRAVEL) { // compute the new position of the joint float angle = asinf(travel / (glm::length(contactPoint) * glm::length(penetrationEnd))); axis = glm::normalize(axis); glm::vec3 end; getJointPosition(jointIndex, end); glm::vec3 newEnd = start + glm::angleAxis(angle, axis) * (end - start); // try to move it setJointPosition(jointIndex, newEnd, -1, true); } } } } void Model::setBlendedVertices(const QVector& vertices, const QVector& normals) { if (_blendedVertexBuffers.isEmpty()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); int index = 0; for (int i = 0; i < geometry.meshes.size(); i++) { const FBXMesh& mesh = geometry.meshes.at(i); if (mesh.blendshapes.isEmpty()) { continue; } QOpenGLBuffer& buffer = _blendedVertexBuffers[i]; buffer.bind(); buffer.write(0, vertices.constData() + index, mesh.vertices.size() * sizeof(glm::vec3)); buffer.write(mesh.vertices.size() * sizeof(glm::vec3), normals.constData() + index, mesh.normals.size() * sizeof(glm::vec3)); buffer.release(); index += mesh.vertices.size(); } } void Model::applyNextGeometry() { // delete our local geometry and custom textures deleteGeometry(); _dilatedTextures.clear(); _lodHysteresis = _nextLODHysteresis; // we retain a reference to the base geometry so that its reference count doesn't fall to zero _baseGeometry = _nextBaseGeometry; _geometry = _nextGeometry; _nextBaseGeometry.reset(); _nextGeometry.reset(); } void Model::deleteGeometry() { foreach (Model* attachment, _attachments) { delete attachment; } _attachments.clear(); _blendedVertexBuffers.clear(); _jointStates.clear(); _meshStates.clear(); clearShapes(); if (_geometry) { _geometry->clearLoadPriority(this); } } void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); for (int i = 0; i < networkMeshes.size(); i++) { // exit early if the translucency doesn't match what we're drawing const NetworkMesh& networkMesh = networkMeshes.at(i); if (translucent ? (networkMesh.getTranslucentPartCount() == 0) : (networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) { continue; } const_cast(networkMesh.indexBuffer).bind(); const FBXMesh& mesh = geometry.meshes.at(i); int vertexCount = mesh.vertices.size(); if (vertexCount == 0) { // sanity check continue; } const_cast(networkMesh.vertexBuffer).bind(); ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; if (mode == SHADOW_RENDER_MODE) { program = &_shadowProgram; skinProgram = &_skinShadowProgram; skinLocations = &_skinShadowLocations; } else if (!mesh.tangents.isEmpty()) { program = &_normalMapProgram; skinProgram = &_skinNormalMapProgram; skinLocations = &_skinNormalMapLocations; } const MeshState& state = _meshStates.at(i); ProgramObject* activeProgram = program; int tangentLocation = _normalMapTangentLocation; glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); if (state.clusterMatrices.size() > 1) { skinProgram->bind(); glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false, (const float*)state.clusterMatrices.constData()); int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) + mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0); skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4); skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT, offset + vertexCount * sizeof(glm::vec4), 4); skinProgram->enableAttributeArray(skinLocations->clusterIndices); skinProgram->enableAttributeArray(skinLocations->clusterWeights); activeProgram = skinProgram; tangentLocation = skinLocations->tangent; } else { glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]); program->bind(); } if (mesh.blendshapes.isEmpty()) { if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); activeProgram->enableAttributeArray(tangentLocation); } glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) + mesh.tangents.size() * sizeof(glm::vec3))); glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) + (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3))); } else { if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3); activeProgram->enableAttributeArray(tangentLocation); } glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3))); glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3))); _blendedVertexBuffers[i].bind(); } glVertexPointer(3, GL_FLOAT, 0, 0); glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3))); if (!mesh.colors.isEmpty()) { glEnableClientState(GL_COLOR_ARRAY); } else { glColor4f(1.0f, 1.0f, 1.0f, alpha); } if (!mesh.texCoords.isEmpty()) { glEnableClientState(GL_TEXTURE_COORD_ARRAY); } qint64 offset = 0; for (int j = 0; j < networkMesh.parts.size(); j++) { const NetworkMeshPart& networkPart = networkMesh.parts.at(j); const FBXMeshPart& part = mesh.parts.at(j); if (networkPart.isTranslucent() != translucent) { offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int); continue; } // apply material properties if (mode == SHADOW_RENDER_MODE) { glBindTexture(GL_TEXTURE_2D, 0); } else { glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha); glm::vec4 specular = glm::vec4(part.specularColor, alpha); glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular); glMaterialf(GL_FRONT, GL_SHININESS, part.shininess); Texture* diffuseMap = networkPart.diffuseTexture.data(); if (mesh.isEye && diffuseMap) { diffuseMap = (_dilatedTextures[i][j] = static_cast(diffuseMap)->getDilatedTexture(_pupilDilation)).data(); } glBindTexture(GL_TEXTURE_2D, !diffuseMap ? Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); if (!mesh.tangents.isEmpty()) { glActiveTexture(GL_TEXTURE1); Texture* normalMap = networkPart.normalTexture.data(); glBindTexture(GL_TEXTURE_2D, !normalMap ? Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID()); glActiveTexture(GL_TEXTURE0); } } glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset); offset += part.quadIndices.size() * sizeof(int); glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(), GL_UNSIGNED_INT, (void*)offset); offset += part.triangleIndices.size() * sizeof(int); } if (!mesh.colors.isEmpty()) { glDisableClientState(GL_COLOR_ARRAY); } if (!mesh.texCoords.isEmpty()) { glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); activeProgram->disableAttributeArray(tangentLocation); } if (state.clusterMatrices.size() > 1) { skinProgram->disableAttributeArray(skinLocations->clusterIndices); skinProgram->disableAttributeArray(skinLocations->clusterWeights); } glPopMatrix(); activeProgram->release(); } }