285 lines · 11.5 KB · C++
RawDownload
////  AvatarVoxelSystem.cpp//  interface////  Created by Andrzej Kapolka on 5/31/13.//  Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include <cstring> #include <QNetworkReply> #include <GeometryUtil.h> #include "Application.h"#include "Avatar.h"#include "AvatarVoxelSystem.h"#include "renderer/ProgramObject.h" const float AVATAR_TREE_SCALE = 1.0f;const int MAX_VOXELS_PER_AVATAR = 2000;const int BONE_ELEMENTS_PER_VOXEL = BONE_ELEMENTS_PER_VERTEX * VERTICES_PER_VOXEL; AvatarVoxelSystem::AvatarVoxelSystem(Avatar* avatar) :    VoxelSystem(AVATAR_TREE_SCALE, MAX_VOXELS_PER_AVATAR),    _avatar(avatar), _voxelReply(0) {     // we may have been created in the network thread, but we live in the main thread    moveToThread(Application::getInstance()->thread());} AvatarVoxelSystem::~AvatarVoxelSystem() {       delete[] _readBoneIndicesArray;    delete[] _readBoneWeightsArray;    delete[] _writeBoneIndicesArray;    delete[] _writeBoneWeightsArray;} ProgramObject* AvatarVoxelSystem::_skinProgram = 0;int AvatarVoxelSystem::_boneMatricesLocation;int AvatarVoxelSystem::_boneIndicesLocation;int AvatarVoxelSystem::_boneWeightsLocation; void AvatarVoxelSystem::init() {    VoxelSystem::init();        // prep the data structures for incoming voxel data    _writeBoneIndicesArray = new GLubyte[BONE_ELEMENTS_PER_VOXEL * _maxVoxels];    _readBoneIndicesArray = new GLubyte[BONE_ELEMENTS_PER_VOXEL * _maxVoxels];     _writeBoneWeightsArray = new GLfloat[BONE_ELEMENTS_PER_VOXEL * _maxVoxels];    _readBoneWeightsArray = new GLfloat[BONE_ELEMENTS_PER_VOXEL * _maxVoxels];        // VBO for the boneIndicesArray    glGenBuffers(1, &_vboBoneIndicesID);    glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID);    glBufferData(GL_ARRAY_BUFFER, BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte) * _maxVoxels, NULL, GL_DYNAMIC_DRAW);        // VBO for the boneWeightsArray    glGenBuffers(1, &_vboBoneWeightsID);    glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID);    glBufferData(GL_ARRAY_BUFFER, BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels, NULL, GL_DYNAMIC_DRAW);        // load our skin program if this is the first avatar system to initialize    if (_skinProgram != 0) {        return;    }    _skinProgram = new ProgramObject();    _skinProgram->addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/skin_voxels.vert");    _skinProgram->link();        _boneMatricesLocation = _skinProgram->uniformLocation("boneMatrices");    _boneIndicesLocation = _skinProgram->attributeLocation("boneIndices");    _boneWeightsLocation = _skinProgram->attributeLocation("boneWeights");} void AvatarVoxelSystem::removeOutOfView() {    // no-op for now} void AvatarVoxelSystem::setVoxelURL(const QUrl& url) {    // don't restart the download if it's the same URL    if (_voxelURL == url) {        return;    }     // cancel any current download    if (_voxelReply != 0) {        delete _voxelReply;        _voxelReply = 0;    }        killLocalVoxels();        // remember the URL    _voxelURL = url;        // handle "file://" urls...    if (url.isLocalFile()) {        QString pathString = url.path();        QByteArray pathAsAscii = pathString.toAscii();        const char* path = pathAsAscii.data();        readFromSVOFile(path);        return;            }        // load the URL data asynchronously    if (!url.isValid()) {        return;    }    _voxelReply = Application::getInstance()->getNetworkAccessManager()->get(QNetworkRequest(url));    connect(_voxelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleVoxelDownloadProgress(qint64,qint64)));    connect(_voxelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleVoxelReplyError()));} void AvatarVoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,                                           float voxelScale, const nodeColor& color) {    VoxelSystem::updateNodeInArrays(nodeIndex, startVertex, voxelScale, color);        GLubyte* writeBoneIndicesAt = _writeBoneIndicesArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL);    GLfloat* writeBoneWeightsAt = _writeBoneWeightsArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL);      for (int i = 0; i < VERTICES_PER_VOXEL; i++) {        BoneIndices boneIndices;        glm::vec4 boneWeights;        computeBoneIndicesAndWeights(computeVoxelVertex(startVertex, voxelScale, i), boneIndices, boneWeights);        for (int j = 0; j < BONE_ELEMENTS_PER_VERTEX; j++) {            *(writeBoneIndicesAt + i * BONE_ELEMENTS_PER_VERTEX + j) = boneIndices[j];            *(writeBoneWeightsAt + i * BONE_ELEMENTS_PER_VERTEX + j) = boneWeights[j];        }    }} void AvatarVoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd) {    VoxelSystem::copyWrittenDataSegmentToReadArrays(segmentStart, segmentEnd);        int segmentLength = (segmentEnd - segmentStart) + 1;    GLintptr   segmentStartAt   = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte);    GLsizeiptr segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte);    GLubyte* readBoneIndicesAt     = _readBoneIndicesArray  + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    GLubyte* writeBoneIndicesAt    = _writeBoneIndicesArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    memcpy(readBoneIndicesAt, writeBoneIndicesAt, segmentSizeBytes);     segmentStartAt          = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat);    segmentSizeBytes        = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat);    GLfloat* readBoneWeightsAt   = _readBoneWeightsArray   + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    GLfloat* writeBoneWeightsAt  = _writeBoneWeightsArray  + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    memcpy(readBoneWeightsAt, writeBoneWeightsAt, segmentSizeBytes);} void AvatarVoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {    VoxelSystem::updateVBOSegment(segmentStart, segmentEnd);        int segmentLength = (segmentEnd - segmentStart) + 1;    GLintptr   segmentStartAt   = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte);    GLsizeiptr segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte);    GLubyte* readBoneIndicesFrom   = _readBoneIndicesArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID);    glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readBoneIndicesFrom);        segmentStartAt   = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat);    segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat);    GLfloat* readBoneWeightsFrom   = _readBoneWeightsArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL);    glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID);    glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readBoneWeightsFrom);  } void AvatarVoxelSystem::applyScaleAndBindProgram(bool texture) {    _skinProgram->bind();        // the base matrix includes centering and scale    QMatrix4x4 baseMatrix;    baseMatrix.scale(_treeScale);    baseMatrix.translate(-0.5f, -0.5f, -0.5f);        // bone matrices include joint transforms    QMatrix4x4 boneMatrices[NUM_AVATAR_JOINTS];    for (int i = 0; i < NUM_AVATAR_JOINTS; i++) {        glm::vec3 position;        glm::quat orientation;        _avatar->getBodyBallTransform((AvatarJointID)i, position, orientation);        boneMatrices[i].translate(position.x, position.y, position.z);          orientation = orientation * glm::inverse(_avatar->getSkeleton().joint[i].absoluteBindPoseRotation);        boneMatrices[i].rotate(QQuaternion(orientation.w, orientation.x, orientation.y, orientation.z));        const glm::vec3& bindPosition = _avatar->getSkeleton().joint[i].absoluteBindPosePosition;        boneMatrices[i].translate(-bindPosition.x, -bindPosition.y, -bindPosition.z);        boneMatrices[i] *= baseMatrix;    }     _skinProgram->setUniformValueArray(_boneMatricesLocation, boneMatrices, NUM_AVATAR_JOINTS);        glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID);    glVertexAttribPointer(_boneIndicesLocation, BONE_ELEMENTS_PER_VERTEX, GL_UNSIGNED_BYTE, false, 0, 0);    _skinProgram->enableAttributeArray(_boneIndicesLocation);        glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID);    _skinProgram->setAttributeBuffer(_boneWeightsLocation, GL_FLOAT, 0, BONE_ELEMENTS_PER_VERTEX);    _skinProgram->enableAttributeArray(_boneWeightsLocation);} void AvatarVoxelSystem::removeScaleAndReleaseProgram(bool texture) {    _skinProgram->release();    _skinProgram->disableAttributeArray(_boneIndicesLocation);    _skinProgram->disableAttributeArray(_boneWeightsLocation);} void AvatarVoxelSystem::handleVoxelDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {    // for now, just wait until we have the full business    if (bytesReceived < bytesTotal) {        return;    }     QByteArray entirety = _voxelReply->readAll();    _voxelReply->disconnect(this);    _voxelReply->deleteLater();    _voxelReply = 0;        _tree->readBitstreamToTree((unsigned char*)entirety.data(), entirety.size(), WANT_COLOR, NO_EXISTS_BITS);    setupNewVoxelsForDrawing();} void AvatarVoxelSystem::handleVoxelReplyError() {    printLog("%s\n", _voxelReply->errorString().toAscii().constData());        _voxelReply->disconnect(this);    _voxelReply->deleteLater();    _voxelReply = 0;} class IndexDistance {public:    IndexDistance(GLubyte index = AVATAR_JOINT_PELVIS, float distance = FLT_MAX) : index(index), distance(distance) { }     GLubyte index;    float distance;}; void AvatarVoxelSystem::computeBoneIndicesAndWeights(const glm::vec3& vertex, BoneIndices& indices, glm::vec4& weights) const {    // transform into joint space    glm::vec3 jointVertex = (vertex - glm::vec3(0.5f, 0.5f, 0.5f)) * AVATAR_TREE_SCALE;        // find the nearest four joints (TODO: use a better data structure for the pose positions to speed this up)    IndexDistance nearest[BONE_ELEMENTS_PER_VERTEX];    const Skeleton& skeleton = _avatar->getSkeleton();    for (int i = 0; i < NUM_AVATAR_JOINTS; i++) {        AvatarJointID parent = skeleton.joint[i].parent;        float distance = glm::length(computeVectorFromPointToSegment(jointVertex,            skeleton.joint[parent == AVATAR_JOINT_NULL ? i : parent].absoluteBindPosePosition,            skeleton.joint[i].absoluteBindPosePosition));        if (distance > skeleton.joint[i].bindRadius) {            continue;        }        for (int j = 0; j < BONE_ELEMENTS_PER_VERTEX; j++) {            if (distance < nearest[j].distance) {                // move the rest of the indices down                for (int k = BONE_ELEMENTS_PER_VERTEX - 1; k > j; k--) {                    nearest[k] = nearest[k - 1];                }                       nearest[j] = IndexDistance(i, distance);                break;            }        }    }         // compute the weights based on inverse distance    float totalWeight = 0.0f;    for (int i = 0; i < BONE_ELEMENTS_PER_VERTEX; i++) {        indices[i] = nearest[i].index;        if (nearest[i].distance != FLT_MAX) {            weights[i] = 1.0f / glm::max(nearest[i].distance, EPSILON);            totalWeight += weights[i];                } else {            weights[i] = 0.0f;        }    }        // if it's not attached to anything, consider it attached to the hip    if (totalWeight == 0.0f) {        weights[0] = 1.0f;        return;    }        // ortherwise, normalize the weights    for (int i = 0; i < BONE_ELEMENTS_PER_VERTEX; i++) {        weights[i] /= totalWeight;    }}