Merge branch 'master' of https://github.com/worklist/hifi into avatardata_optimization

This commit is contained in:
ZappoMan 2013-06-04 16:21:20 -07:00
commit 6e70f3b187
15 changed files with 712 additions and 194 deletions

View file

@ -63,12 +63,12 @@ if (APPLE)
endif (APPLE)
find_package(Qt4 REQUIRED QtCore QtGui QtOpenGL)
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL)
include(${QT_USE_FILE})
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}")
# run qt moc on qt-enabled headers
qt4_wrap_cpp(INTERFACE_SRCS src/Application.h)
qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h)
# create the executable, make it a bundle on OS X
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS})

View file

@ -0,0 +1,34 @@
#version 120
//
// skin_voxels.vert
// vertex shader
//
// Created by Andrzej Kapolka on 5/31/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
const int MAX_BONES = 32;
const int INDICES_PER_VERTEX = 4;
uniform mat4 boneMatrices[MAX_BONES];
attribute vec4 boneIndices;
attribute vec4 boneWeights;
void main(void) {
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
vec4 normal = vec4(0.0, 0.0, 0.0, 0.0);
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
mat4 boneMatrix = boneMatrices[int(boneIndices[i])];
float boneWeight = boneWeights[i];
position += boneMatrix * gl_Vertex * boneWeight;
normal += boneMatrix * vec4(gl_Normal, 0.0) * boneWeight;
}
position = gl_ModelViewProjectionMatrix * position;
normal = normalize(gl_ModelViewMatrix * normal);
gl_FrontColor = gl_Color * (gl_LightModel.ambient + gl_LightSource[0].ambient +
gl_LightSource[0].diffuse * max(0.0, dot(normal, gl_LightSource[0].position)));
gl_Position = position;
}

View file

@ -21,16 +21,23 @@
#include <glm/gtx/quaternion.hpp>
#include <QActionGroup>
#include <QBoxLayout>
#include <QColorDialog>
#include <QDialogButtonBox>
#include <QDesktopWidget>
#include <QFormLayout>
#include <QGLWidget>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMainWindow>
#include <QMenuBar>
#include <QMouseEvent>
#include <QNetworkAccessManager>
#include <QWheelEvent>
#include <QSettings>
#include <QShortcut>
#include <QTimer>
#include <QUrl>
#include <QtDebug>
#include <QFileDialog>
#include <QDesktopServices>
@ -161,8 +168,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_window->setWindowTitle("Interface");
printLog("Interface Startup:\n");
_voxels.setViewFrustum(&_viewFrustum);
unsigned int listenPort = AGENT_SOCKET_LISTEN_PORT;
const char** constArgv = const_cast<const char**>(argv);
const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
@ -1143,6 +1148,32 @@ void Application::terminate() {
}
}
void Application::editPreferences() {
QDialog dialog(_glWidget);
dialog.setWindowTitle("Interface Preferences");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QLineEdit* avatarURL = new QLineEdit(_settings->value("avatarURL").toString());
avatarURL->setMinimumWidth(400);
form->addRow("Avatar URL:", avatarURL);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
if (dialog.exec() != QDialog::Accepted) {
return;
}
QUrl url(avatarURL->text());
_settings->setValue("avatarURL", url);
_myAvatar.getVoxels()->loadVoxelsFromURL(url);
}
void Application::pair() {
PairingHandler::sendPairRequest();
}
@ -1469,6 +1500,9 @@ void Application::initMenu() {
QMenu* fileMenu = menuBar->addMenu("File");
fileMenu->addAction("Quit", this, SLOT(quit()), Qt::CTRL | Qt::Key_Q);
QMenu* editMenu = menuBar->addMenu("Edit");
editMenu->addAction("Preferences...", this, SLOT(editPreferences()));
QMenu* pairMenu = menuBar->addMenu("Pair");
pairMenu->addAction("Pair", this, SLOT(pair()));
@ -1509,9 +1543,7 @@ void Application::initMenu() {
renderMenu->addAction("First Person", this, SLOT(setRenderFirstPerson(bool)), Qt::Key_P)->setCheckable(true);
QMenu* toolsMenu = menuBar->addMenu("Tools");
(_renderStatsOn = toolsMenu->addAction("Stats"))->setCheckable(true);
_renderStatsOn->setShortcut(Qt::Key_Slash);
(_logOn = toolsMenu->addAction("Log"))->setCheckable(true);
_logOn->setChecked(false);
@ -1579,6 +1611,9 @@ void Application::initMenu() {
debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true);
debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true);
debugMenu->addAction("Wants View Delta Sending", this, SLOT(setWantsDelta(bool)))->setCheckable(true);
_networkAccessManager = new QNetworkAccessManager(this);
_settings = new QSettings("High Fidelity", "Interface", this);
}
void Application::updateFrustumRenderModeAction() {
@ -1613,8 +1648,6 @@ void Application::initDisplay() {
void Application::init() {
_voxels.init();
_voxels.setViewerAvatar(&_myAvatar);
_voxels.setCamera(&_myCamera);
_environment.init();
@ -1625,11 +1658,14 @@ void Application::init() {
_stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0);
_myAvatar.init();
_myAvatar.setPosition(START_LOCATION);
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON );
_myCamera.setModeShiftRate(1.0f);
_myAvatar.setDisplayingLookatVectors(false);
_myAvatar.getVoxels()->loadVoxelsFromURL(_settings->value("avatarURL").toUrl());
QCursor::setPos(_headMouseX, _headMouseY);
OculusManager::connect();
@ -1910,7 +1946,7 @@ void Application::displayOculus(Camera& whichCamera) {
glPopMatrix();
}
void Application::displaySide(Camera& whichCamera) {
// transform by eye offset
@ -2493,7 +2529,9 @@ QAction* Application::checkedVoxelModeAction() const {
void Application::attachNewHeadToAgent(Agent* newAgent) {
if (newAgent->getLinkedData() == NULL) {
newAgent->setLinkedData(new Avatar(newAgent));
Avatar* newAvatar = new Avatar(newAgent);
newAvatar->init();
newAgent->setLinkedData(newAvatar);
}
}

View file

@ -37,6 +37,8 @@ class QGLWidget;
class QKeyEvent;
class QMainWindow;
class QMouseEvent;
class QNetworkAccessManager;
class QSettings;
class QWheelEvent;
class Agent;
@ -65,10 +67,13 @@ public:
Avatar* getAvatar() { return &_myAvatar; }
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
VoxelSystem* getVoxels() { return &_voxels; }
Environment* getEnvironment() { return &_environment; }
bool shouldEchoAudio() { return _echoAudioMode->isChecked(); }
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
/*!
@fn getSettingBool
@brief A function for getting boolean settings from the settings file.
@ -126,6 +131,8 @@ private slots:
void idle();
void terminate();
void editPreferences();
void pair();
void setHead(bool head);
@ -235,6 +242,9 @@ private:
QAction* _fullScreenMode; // whether we are in full screen mode
QAction* _frustumRenderModeAction;
QNetworkAccessManager* _networkAccessManager;
QSettings* _settings;
SerialInterface _serialPort;
bool _displayLevels;

View file

@ -55,7 +55,6 @@ const float SKIN_COLOR[] = {1.0, 0.84, 0.66};
const float DARK_SKIN_COLOR[] = {0.9, 0.78, 0.63};
const int NUM_BODY_CONE_SIDES = 9;
bool usingBigSphereCollisionTest = true;
float chatMessageScale = 0.0015;
@ -88,7 +87,8 @@ Avatar::Avatar(Agent* owningAgent) :
_mouseRayDirection(0.0f, 0.0f, 0.0f),
_interactingOther(NULL),
_cumulativeMouseYaw(0.0f),
_isMouseTurningRight(false)
_isMouseTurningRight(false),
_voxels(this)
{
// give the pointer to our head to inherited _headData variable from AvatarData
@ -264,6 +264,10 @@ Avatar::~Avatar() {
delete _balls;
}
void Avatar::init() {
_voxels.init();
}
void Avatar::reset() {
_head.reset();
}
@ -1015,20 +1019,24 @@ void Avatar::updateBodyBalls(float deltaTime) {
if (glm::length(_position - _bodyBall[BODY_BALL_PELVIS].position) > BEYOND_BODY_SPRING_RANGE) {
resetBodyBalls();
}
glm::quat orientation = getOrientation();
glm::vec3 jointDirection = orientation * JOINT_DIRECTION;
for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) {
glm::vec3 springVector;
float length = 0.0f;
if (_ballSpringsInitialized) {
// apply spring forces
glm::vec3 springVector(_bodyBall[b].position);
springVector = _bodyBall[b].position;
if (b == BODY_BALL_PELVIS) {
springVector -= _position;
} else {
springVector -= _bodyBall[_bodyBall[b].parentBall].position;
}
float length = glm::length(springVector);
length = glm::length(springVector);
if (length > 0.0f) { // to avoid divide by zero
glm::vec3 springDirection = springVector / length;
@ -1066,6 +1074,14 @@ void Avatar::updateBodyBalls(float deltaTime) {
// update position by velocity...
_bodyBall[b].position += _bodyBall[b].velocity * deltaTime;
// update rotation
const float SMALL_SPRING_LENGTH = 0.001f; // too-small springs can change direction rapidly
if (_skeleton.joint[b].parent == AVATAR_JOINT_NULL || length < SMALL_SPRING_LENGTH) {
_bodyBall[b].rotation = orientation * _skeleton.joint[_bodyBall[b].parentJoint].absoluteBindPoseRotation;
} else {
_bodyBall[b].rotation = rotationBetween(jointDirection, springVector) * orientation;
}
}
}
@ -1126,6 +1142,9 @@ void Avatar::renderBody(bool lookingInMirror) {
const float RENDER_OPAQUE_BEYOND = 1.0f; // Meters beyond which body is shown opaque
const float RENDER_TRANSLUCENT_BEYOND = 0.5f;
// Render the body's voxels
_voxels.render(false);
// Render the body as balls and cones
for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) {
float distanceToCamera = glm::length(_cameraPosition - _bodyBall[b].position);
@ -1231,6 +1250,11 @@ void Avatar::setHeadFromGyros(glm::vec3* eulerAngles, glm::vec3* angularVelocity
}
}
void Avatar::getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const {
position = _bodyBall[jointID].position;
rotation = _bodyBall[jointID].rotation;
}
void Avatar::writeAvatarDataToFile() {
Application::getInstance()->setSetting("avatarPos", _position);
Application::getInstance()->setSetting("avatarRotation", glm::vec3(_bodyYaw, _bodyPitch, _bodyRoll));

View file

@ -13,6 +13,7 @@
#include <AvatarData.h>
#include "world.h"
#include "AvatarTouch.h"
#include "AvatarVoxelSystem.h"
#include "InterfaceConfig.h"
#include "SerialInterface.h"
#include "Balls.h"
@ -80,6 +81,7 @@ public:
Avatar(Agent* owningAgent = NULL);
~Avatar();
void init();
void reset();
void simulate(float deltaTime, Transmitter* transmitter);
void updateHeadFromGyros(float frametime, SerialInterface * serialInterface);
@ -98,6 +100,7 @@ public:
void setOrientation (const glm::quat& orientation);
//getters
const Skeleton& getSkeleton () const { return _skeleton;}
float getHeadYawRate () const { return _head.yawRate;}
float getBodyYaw () const { return _bodyYaw;}
bool getIsNearInteractingOther () const { return _avatarTouch.getAbleToReachOtherAvatar();}
@ -116,6 +119,8 @@ public:
glm::quat getOrientation () const;
glm::quat getWorldAlignedOrientation() const;
AvatarVoxelSystem* getVoxels() { return &_voxels; }
// Set what driving keys are being pressed to control thrust levels
void setDriveKeys(int key, bool val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key]; };
@ -124,6 +129,9 @@ public:
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
glm::vec3 getThrust() { return _thrust; };
// Get the position/rotation of a single body ball
void getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const;
//read/write avatar data
void writeAvatarDataToFile();
void readAvatarDataFromFile();
@ -139,6 +147,7 @@ private:
glm::vec3 parentOffset; // a 3D vector in the frame of reference of the parent skeletal joint
AvatarBodyBallID parentBall; // the ball to which this ball is constrained for spring forces
glm::vec3 position; // the actual dynamic position of the ball at any given time
glm::quat rotation; // the rotation of the ball
glm::vec3 velocity; // the velocity of the ball
float springLength; // the ideal length of the spring between this ball and its parentBall
float jointTightness; // how tightly the ball position attempts to stay at its ideal position (determined by parentOffset)
@ -181,6 +190,8 @@ private:
float _cumulativeMouseYaw;
bool _isMouseTurningRight;
AvatarVoxelSystem _voxels;
// private methods...
glm::vec3 caclulateAverageEyePosition() { return _head.caclulateAverageEyePosition(); } // get the position smack-dab between the eyes (for lookat)
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;

View file

@ -0,0 +1,261 @@
//
// 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 <QUrl>
#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) {
}
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::loadVoxelsFromURL(const QUrl& url) {
// cancel any current download
if (_voxelReply != 0) {
delete _voxelReply;
}
killLocalVoxels();
// load the URL data asynchronously
if (!url.isValid()) {
return;
}
_voxelReply = Application::getInstance()->getNetworkAccessManager()->get(QNetworkRequest(url));
connect(_voxelReply, SIGNAL(readyRead()), SLOT(readVoxelDataFromReply()));
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::readVoxelDataFromReply() {
// for now, just wait until we have the full business
if (!_voxelReply->isFinished()) {
return;
}
QByteArray entirety = _voxelReply->readAll();
_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->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;
}
}

View file

@ -0,0 +1,74 @@
//
// AvatarVoxelSystem.h
// interface
//
// Created by Andrzej Kapolka on 5/31/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__AvatarVoxelSystem__
#define __interface__AvatarVoxelSystem__
#include <QObject>
#include "VoxelSystem.h"
const int BONE_ELEMENTS_PER_VERTEX = 4;
typedef GLubyte BoneIndices[BONE_ELEMENTS_PER_VERTEX];
class QNetworkReply;
class QUrl;
class Avatar;
class AvatarVoxelSystem : public QObject, public VoxelSystem {
Q_OBJECT
public:
AvatarVoxelSystem(Avatar* avatar);
virtual ~AvatarVoxelSystem();
virtual void init();
virtual void removeOutOfView();
void loadVoxelsFromURL(const QUrl& url);
protected:
virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color);
virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void applyScaleAndBindProgram(bool texture);
virtual void removeScaleAndReleaseProgram(bool texture);
private slots:
void readVoxelDataFromReply();
void handleVoxelReplyError();
private:
void computeBoneIndicesAndWeights(const glm::vec3& vertex, BoneIndices& indices, glm::vec4& weights) const;
Avatar* _avatar;
GLubyte* _readBoneIndicesArray;
GLfloat* _readBoneWeightsArray;
GLubyte* _writeBoneIndicesArray;
GLfloat* _writeBoneWeightsArray;
GLuint _vboBoneIndicesID;
GLuint _vboBoneWeightsID;
QNetworkReply* _voxelReply;
static ProgramObject* _skinProgram;
static int _boneMatricesLocation;
static int _boneIndicesLocation;
static int _boneWeightsLocation;
};
#endif /* defined(__interface__AvatarVoxelSystem__) */

View file

@ -5,6 +5,7 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
#include "Skeleton.h"
#include "Util.h"
const float BODY_SPRING_DEFAULT_TIGHTNESS = 1000.0f;
const float FLOATING_HEIGHT = 0.13f;
@ -18,8 +19,9 @@ void Skeleton::initialize() {
joint[b].parent = AVATAR_JOINT_NULL;
joint[b].position = glm::vec3(0.0, 0.0, 0.0);
joint[b].defaultPosePosition = glm::vec3(0.0, 0.0, 0.0);
joint[b].rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
joint[b].rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
joint[b].length = 0.0;
joint[b].bindRadius = 1.0f / 8;
}
// specify the parental hierarchy
@ -48,6 +50,35 @@ void Skeleton::initialize() {
joint[ AVATAR_JOINT_RIGHT_HEEL ].parent = AVATAR_JOINT_RIGHT_KNEE;
joint[ AVATAR_JOINT_RIGHT_TOES ].parent = AVATAR_JOINT_RIGHT_HEEL;
// specify the bind pose position
joint[ AVATAR_JOINT_PELVIS ].bindPosePosition = glm::vec3( 0.0, 0.0, 0.0 );
joint[ AVATAR_JOINT_TORSO ].bindPosePosition = glm::vec3( 0.0, 0.09, -0.01 );
joint[ AVATAR_JOINT_CHEST ].bindPosePosition = glm::vec3( 0.0, 0.09, -0.01 );
joint[ AVATAR_JOINT_NECK_BASE ].bindPosePosition = glm::vec3( 0.0, 0.14, 0.01 );
joint[ AVATAR_JOINT_HEAD_BASE ].bindPosePosition = glm::vec3( 0.0, 0.04, 0.00 );
joint[ AVATAR_JOINT_LEFT_COLLAR ].bindPosePosition = glm::vec3( -0.06, 0.04, 0.01 );
joint[ AVATAR_JOINT_LEFT_SHOULDER ].bindPosePosition = glm::vec3( -0.05, 0.0, 0.01 );
joint[ AVATAR_JOINT_LEFT_ELBOW ].bindPosePosition = glm::vec3( -0.16, 0.0, 0.0 );
joint[ AVATAR_JOINT_LEFT_WRIST ].bindPosePosition = glm::vec3( -0.12, 0.0, 0.0 );
joint[ AVATAR_JOINT_LEFT_FINGERTIPS ].bindPosePosition = glm::vec3( -0.1, 0.0, 0.0 );
joint[ AVATAR_JOINT_RIGHT_COLLAR ].bindPosePosition = glm::vec3( 0.06, 0.04, 0.01 );
joint[ AVATAR_JOINT_RIGHT_SHOULDER ].bindPosePosition = glm::vec3( 0.05, 0.0, 0.01 );
joint[ AVATAR_JOINT_RIGHT_ELBOW ].bindPosePosition = glm::vec3( 0.16, 0.0, 0.0 );
joint[ AVATAR_JOINT_RIGHT_WRIST ].bindPosePosition = glm::vec3( 0.12, 0.0, 0.0 );
joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].bindPosePosition = glm::vec3( 0.1, 0.0, 0.0 );
joint[ AVATAR_JOINT_LEFT_HIP ].bindPosePosition = glm::vec3( -0.05, 0.0, 0.02 );
joint[ AVATAR_JOINT_LEFT_KNEE ].bindPosePosition = glm::vec3( 0.00, -0.25, 0.00 );
joint[ AVATAR_JOINT_LEFT_HEEL ].bindPosePosition = glm::vec3( 0.00, -0.23, 0.00 );
joint[ AVATAR_JOINT_LEFT_TOES ].bindPosePosition = glm::vec3( 0.00, 0.00, -0.06 );
joint[ AVATAR_JOINT_RIGHT_HIP ].bindPosePosition = glm::vec3( 0.05, 0.0, 0.02 );
joint[ AVATAR_JOINT_RIGHT_KNEE ].bindPosePosition = glm::vec3( 0.00, -0.25, 0.00 );
joint[ AVATAR_JOINT_RIGHT_HEEL ].bindPosePosition = glm::vec3( 0.00, -0.23, 0.00 );
joint[ AVATAR_JOINT_RIGHT_TOES ].bindPosePosition = glm::vec3( 0.00, 0.00, -0.06 );
// specify the default pose position
joint[ AVATAR_JOINT_PELVIS ].defaultPosePosition = glm::vec3( 0.0, 0.0, 0.0 );
joint[ AVATAR_JOINT_TORSO ].defaultPosePosition = glm::vec3( 0.0, 0.09, -0.01 );
@ -77,9 +108,18 @@ void Skeleton::initialize() {
joint[ AVATAR_JOINT_RIGHT_HEEL ].defaultPosePosition = glm::vec3( -0.01, -0.22, 0.08 );
joint[ AVATAR_JOINT_RIGHT_TOES ].defaultPosePosition = glm::vec3( 0.00, -0.03, -0.05 );
// calculate bone length
// calculate bone length, absolute bind positions/rotations
for (int b = 0; b < NUM_AVATAR_JOINTS; b++) {
joint[b].length = glm::length(joint[b].defaultPosePosition);
if (joint[b].parent == AVATAR_JOINT_NULL) {
joint[b].absoluteBindPosePosition = joint[b].bindPosePosition;
joint[b].absoluteBindPoseRotation = glm::quat();
} else {
joint[b].absoluteBindPosePosition = joint[ joint[b].parent ].absoluteBindPosePosition +
joint[b].bindPosePosition;
joint[b].absoluteBindPoseRotation = rotationBetween(JOINT_DIRECTION, joint[b].bindPosePosition);
}
}
}

View file

@ -13,35 +13,36 @@
enum AvatarJointID
{
AVATAR_JOINT_NULL = -1,
AVATAR_JOINT_PELVIS,
AVATAR_JOINT_TORSO,
AVATAR_JOINT_CHEST,
AVATAR_JOINT_NECK_BASE,
AVATAR_JOINT_HEAD_BASE,
AVATAR_JOINT_HEAD_TOP,
AVATAR_JOINT_LEFT_COLLAR,
AVATAR_JOINT_LEFT_SHOULDER,
AVATAR_JOINT_LEFT_ELBOW,
AVATAR_JOINT_LEFT_WRIST,
AVATAR_JOINT_LEFT_FINGERTIPS,
AVATAR_JOINT_RIGHT_COLLAR,
AVATAR_JOINT_RIGHT_SHOULDER,
AVATAR_JOINT_RIGHT_ELBOW,
AVATAR_JOINT_RIGHT_WRIST,
AVATAR_JOINT_RIGHT_FINGERTIPS,
AVATAR_JOINT_LEFT_HIP,
AVATAR_JOINT_LEFT_KNEE,
AVATAR_JOINT_LEFT_HEEL,
AVATAR_JOINT_LEFT_TOES,
AVATAR_JOINT_RIGHT_HIP,
AVATAR_JOINT_RIGHT_KNEE,
AVATAR_JOINT_RIGHT_HEEL,
AVATAR_JOINT_RIGHT_TOES,
AVATAR_JOINT_NULL = -1,
AVATAR_JOINT_PELVIS,
AVATAR_JOINT_TORSO,
AVATAR_JOINT_CHEST,
AVATAR_JOINT_NECK_BASE,
AVATAR_JOINT_HEAD_BASE,
AVATAR_JOINT_HEAD_TOP,
AVATAR_JOINT_LEFT_COLLAR,
AVATAR_JOINT_LEFT_SHOULDER,
AVATAR_JOINT_LEFT_ELBOW,
AVATAR_JOINT_LEFT_WRIST,
AVATAR_JOINT_LEFT_FINGERTIPS,
AVATAR_JOINT_RIGHT_COLLAR,
AVATAR_JOINT_RIGHT_SHOULDER,
AVATAR_JOINT_RIGHT_ELBOW,
AVATAR_JOINT_RIGHT_WRIST,
AVATAR_JOINT_RIGHT_FINGERTIPS,
AVATAR_JOINT_LEFT_HIP,
AVATAR_JOINT_LEFT_KNEE,
AVATAR_JOINT_LEFT_HEEL,
AVATAR_JOINT_LEFT_TOES,
AVATAR_JOINT_RIGHT_HIP,
AVATAR_JOINT_RIGHT_KNEE,
AVATAR_JOINT_RIGHT_HEEL,
AVATAR_JOINT_RIGHT_TOES,
NUM_AVATAR_JOINTS
NUM_AVATAR_JOINTS
};
const glm::vec3 JOINT_DIRECTION = glm::vec3(1.0f, 0.0f, 0.0f);
class Skeleton {
public:
@ -59,14 +60,18 @@ public:
struct AvatarJoint
{
AvatarJointID parent; // which joint is this joint connected to?
glm::vec3 position; // the position at the "end" of the joint - in global space
glm::vec3 defaultPosePosition; // the parent relative position when the avatar is in the default pose
glm::quat rotation; // the parent-relative rotation (orientation) of the joint as a quaternion
float length; // the length of vector between the joint and its parent
AvatarJointID parent; // which joint is this joint connected to?
glm::vec3 position; // the position at the "end" of the joint - in global space
glm::vec3 defaultPosePosition; // the parent relative position when the avatar is in the default pose
glm::vec3 bindPosePosition; // the parent relative position when the avatar is in the "T-pose"
glm::vec3 absoluteBindPosePosition; // the absolute position when the avatar is in the "T-pose"
glm::quat absoluteBindPoseRotation; // the absolute rotation when the avatar is in the "T-pose"
float bindRadius; // the radius of the bone capsule that envelops the vertices to bind
glm::quat rotation; // the parent-relative rotation (orientation) of the joint as a quaternion
float length; // the length of vector connecting the joint and its parent
};
AvatarJoint joint[ NUM_AVATAR_JOINTS ];
AvatarJoint joint[ NUM_AVATAR_JOINTS ];
};
#endif

View file

@ -72,6 +72,27 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
return acos((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))) * 180.f / PI;
}
// Helper function return the rotation from the first vector onto the second
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
float angle = angleBetween(v1, v2);
if (isnan(angle) || angle < EPSILON) {
return glm::quat();
}
glm::vec3 axis = glm::cross(v1, v2);
if (angle > 179.99f) { // 180 degree rotation; must use another axis
axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f));
float axisLength = glm::length(axis);
if (axisLength < EPSILON) { // parallel to x; y will work
axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f)));
} else {
axis /= axisLength;
}
} else {
axis = glm::normalize(glm::cross(v1, v2));
}
return glm::angleAxis(angle, axis);
}
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)

View file

@ -45,6 +45,8 @@ void drawVector(glm::vec3* vector);
float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 safeEulerAngles(const glm::quat& q);
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);

View file

@ -19,6 +19,7 @@
#include <PerfStat.h>
#include <OctalCode.h>
#include <pthread.h>
#include "Application.h"
#include "Log.h"
#include "VoxelConstants.h"
#include "InterfaceConfig.h"
@ -37,14 +38,15 @@ GLfloat identityNormals[] = { 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1,
-1,0,0, +1,0,0, +1,0,0, -1,0,0,
-1,0,0, +1,0,0, +1,0,0, -1,0,0 };
GLubyte identityIndices[] = { 0,2,1, 0,3,2, // Z- .
GLubyte identityIndices[] = { 0,2,1, 0,3,2, // Z-
8,9,13, 8,13,12, // Y-
16,23,19, 16,20,23, // X-
17,18,22, 17,22,21, // X+
10,11,15, 10,15,14, // Y+
4,5,6, 4,6,7 }; // Z+ .
4,5,6, 4,6,7 }; // Z+
VoxelSystem::VoxelSystem() : AgentData(NULL) {
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) :
AgentData(NULL), _treeScale(treeScale), _maxVoxels(maxVoxels) {
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
_writeRenderFullVBO = true;
_readRenderFullVBO = true;
@ -241,10 +243,8 @@ void VoxelSystem::cleanupRemovedVoxels() {
}
void VoxelSystem::copyWrittenDataToReadArraysFullVBOs() {
int bytesOfVertices = (_voxelsInWriteArrays * VERTEX_POINTS_PER_VOXEL) * sizeof(GLfloat);
int bytesOfColors = (_voxelsInWriteArrays * VERTEX_POINTS_PER_VOXEL) * sizeof(GLubyte);
memcpy(_readVerticesArray, _writeVerticesArray, bytesOfVertices);
memcpy(_readColorsArray, _writeColorsArray, bytesOfColors );
copyWrittenDataSegmentToReadArrays(0, _voxelsInWriteArrays - 1);
_voxelsInReadArrays = _voxelsInWriteArrays;
// clear our dirty flags
@ -271,47 +271,37 @@ void VoxelSystem::copyWrittenDataToReadArraysPartialVBOs() {
if (!thisVoxelDirty) {
// If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before
// this one and so that's where the "segment" ends
segmentEnd = i - 1;
copyWrittenDataSegmentToReadArrays(segmentStart, i - 1);
inSegment = false;
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readColorsAt, writeColorsAt, segmentSizeBytes);
}
}
}
// if we got to the end of the array, and we're in an active dirty segment...
if (inSegment) {
segmentEnd = _voxelsInWriteArrays - 1;
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readColorsAt, writeColorsAt, segmentSizeBytes);
copyWrittenDataSegmentToReadArrays(segmentStart, _voxelsInWriteArrays - 1);
}
// update our length
_voxelsInReadArrays = _voxelsInWriteArrays;
}
void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
memcpy(readColorsAt, writeColorsAt, segmentSizeBytes);
}
void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
PerformanceWarning warn(_renderWarningsOn, "copyWrittenDataToReadArrays()");
if (_voxelsDirty && _voxelsUpdated) {
@ -324,12 +314,11 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
}
int VoxelSystem::newTreeToArrays(VoxelNode* node) {
assert(_viewFrustum); // you must set up _viewFrustum before calling this
int voxelsUpdated = 0;
bool shouldRender = false; // assume we don't need to render it
// if it's colored, we might need to render it!
if (node->isColored()) {
float distanceToNode = node->distanceToCamera(*_viewFrustum);
float distanceToNode = node->distanceToCamera(*Application::getInstance()->getViewFrustum());
float boundary = boundaryDistanceForRenderLevel(node->getLevel());
float childBoundary = boundaryDistanceForRenderLevel(node->getLevel() + 1);
bool inBoundary = (distanceToNode <= boundary);
@ -364,7 +353,7 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) {
int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) {
// If we've run out of room, then just bail...
if (_voxelsInWriteArrays >= MAX_VOXELS_PER_SYSTEM) {
if (_voxelsInWriteArrays >= _maxVoxels) {
return 0;
}
@ -375,12 +364,7 @@ int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) {
// populate the array with points for the 8 vertices
// and RGB color for each added vertex
for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) {
GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
*(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale);
*(writeColorsAt +j) = node->getColor()[j % 3];
}
updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor());
node->setBufferIndex(nodeIndex);
_writeVoxelDirtyArray[nodeIndex] = true; // just in case we switch to Partial mode
_voxelsInWriteArrays++; // our know vertices in the arrays
@ -394,7 +378,7 @@ int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) {
int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) {
// If we've run out of room, then just bail...
if (_voxelsInWriteArrays >= MAX_VOXELS_PER_SYSTEM) {
if (_voxelsInWriteArrays >= _maxVoxels) {
return 0;
}
@ -426,17 +410,31 @@ int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) {
// populate the array with points for the 8 vertices
// and RGB color for each added vertex
for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) {
GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
*(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale);
*(writeColorsAt +j) = node->getColor()[j % 3];
}
updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor());
return 1; // updated!
}
return 0; // not-updated
}
void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color) {
for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) {
GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL);
*(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale);
*(writeColorsAt +j) = color[j % 3];
}
}
glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const {
const float* identityVertex = identityVertices + index * 3;
return startVertex + glm::vec3(identityVertex[0], identityVertex[1], identityVertex[2]) * voxelScale;
}
ProgramObject* VoxelSystem::_perlinModulateProgram = 0;
GLuint VoxelSystem::_permutationNormalTextureID = 0;
void VoxelSystem::init() {
_renderWarningsOn = false;
@ -452,23 +450,23 @@ void VoxelSystem::init() {
_unusedArraySpace = 0;
// we will track individual dirty sections with these arrays of bools
_writeVoxelDirtyArray = new bool[MAX_VOXELS_PER_SYSTEM];
memset(_writeVoxelDirtyArray, false, MAX_VOXELS_PER_SYSTEM * sizeof(bool));
_readVoxelDirtyArray = new bool[MAX_VOXELS_PER_SYSTEM];
memset(_readVoxelDirtyArray, false, MAX_VOXELS_PER_SYSTEM * sizeof(bool));
_writeVoxelDirtyArray = new bool[_maxVoxels];
memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
_readVoxelDirtyArray = new bool[_maxVoxels];
memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
// prep the data structures for incoming voxel data
_writeVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
_readVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
_writeVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels];
_readVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels];
_writeColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
_readColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
_writeColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * _maxVoxels];
_readColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * _maxVoxels];
GLuint* indicesArray = new GLuint[INDICES_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
GLuint* indicesArray = new GLuint[INDICES_PER_VOXEL * _maxVoxels];
// populate the indicesArray
// this will not change given new voxels, so we can set it all up now
for (int n = 0; n < MAX_VOXELS_PER_SYSTEM; n++) {
for (int n = 0; n < _maxVoxels; n++) {
// fill the indices array
int voxelIndexOffset = n * INDICES_PER_VOXEL;
GLuint* currentIndicesPos = indicesArray + voxelIndexOffset;
@ -480,11 +478,11 @@ void VoxelSystem::init() {
}
}
GLfloat* normalsArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM];
GLfloat* normalsArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels];
GLfloat* normalsArrayEndPointer = normalsArray;
// populate the normalsArray
for (int n = 0; n < MAX_VOXELS_PER_SYSTEM; n++) {
for (int n = 0; n < _maxVoxels; n++) {
for (int i = 0; i < VERTEX_POINTS_PER_VOXEL; i++) {
*(normalsArrayEndPointer++) = identityNormals[i];
}
@ -493,32 +491,35 @@ void VoxelSystem::init() {
// VBO for the verticesArray
glGenBuffers(1, &_vboVerticesID);
glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);
glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * MAX_VOXELS_PER_SYSTEM, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels, NULL, GL_DYNAMIC_DRAW);
// VBO for the normalsArray
glGenBuffers(1, &_vboNormalsID);
glBindBuffer(GL_ARRAY_BUFFER, _vboNormalsID);
glBufferData(GL_ARRAY_BUFFER,
VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * MAX_VOXELS_PER_SYSTEM,
VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels,
normalsArray, GL_STATIC_DRAW);
// VBO for colorsArray
glGenBuffers(1, &_vboColorsID);
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte) * MAX_VOXELS_PER_SYSTEM, NULL, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte) * _maxVoxels, NULL, GL_DYNAMIC_DRAW);
// VBO for the indicesArray
glGenBuffers(1, &_vboIndicesID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
INDICES_PER_VOXEL * sizeof(GLuint) * MAX_VOXELS_PER_SYSTEM,
INDICES_PER_VOXEL * sizeof(GLuint) * _maxVoxels,
indicesArray, GL_STATIC_DRAW);
// delete the indices and normals arrays that are no longer needed
delete[] indicesArray;
delete[] normalsArray;
// create our simple fragment shader
// create our simple fragment shader if we're the first system to init
if (_perlinModulateProgram != 0) {
return;
}
switchToResourcesParentIfRequired();
_perlinModulateProgram = new ProgramObject();
_perlinModulateProgram->addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/perlin_modulate.vert");
@ -551,20 +552,7 @@ void VoxelSystem::init() {
}
void VoxelSystem::updateFullVBOs() {
glBufferIndex segmentStart = 0;
glBufferIndex segmentEnd = _voxelsInReadArrays;
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom);
updateVBOSegment(0, _voxelsInReadArrays);
// consider the _readVoxelDirtyArray[] clean!
memset(_readVoxelDirtyArray, false, _voxelsInReadArrays * sizeof(bool));
@ -586,39 +574,17 @@ void VoxelSystem::updatePartialVBOs() {
if (!thisVoxelDirty) {
// If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before
// this one and so that's where the "segment" ends
segmentEnd = i - 1;
updateVBOSegment(segmentStart, i - 1);
inSegment = false;
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom);
}
_readVoxelDirtyArray[i] = false; // consider us clean!
}
}
// if we got to the end of the array, and we're in an active dirty segment...
if (inSegment) {
segmentEnd = _voxelsInReadArrays - 1;
if (inSegment) {
updateVBOSegment(segmentStart, _voxelsInReadArrays - 1);
inSegment = false;
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom);
}
}
@ -640,14 +606,28 @@ void VoxelSystem::updateVBOs() {
_callsToTreesToArrays = 0; // clear it
}
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
int segmentLength = (segmentEnd - segmentStart) + 1;
GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat);
GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom);
segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte);
GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL);
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom);
}
void VoxelSystem::render(bool texture) {
PerformanceWarning warn(_renderWarningsOn, "render()");
// get the lock so that the update thread won't change anything
pthread_mutex_lock(&_bufferWriteLock);
glPushMatrix();
updateVBOs();
// tell OpenGL where to find vertex and color information
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
@ -662,10 +642,7 @@ void VoxelSystem::render(bool texture) {
glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID);
glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);
if (texture) {
_perlinModulateProgram->bind();
glBindTexture(GL_TEXTURE_2D, _permutationNormalTextureID);
}
applyScaleAndBindProgram(texture);
// for performance, disable blending and enable backface culling
glDisable(GL_BLEND);
@ -673,17 +650,13 @@ void VoxelSystem::render(bool texture) {
// draw the number of voxels we have
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesID);
glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, VERTICES_PER_VOXEL * _voxelsInReadArrays - 1,
36 * _voxelsInReadArrays, GL_UNSIGNED_INT, 0);
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
if (texture) {
_perlinModulateProgram->release();
glBindTexture(GL_TEXTURE_2D, 0);
}
removeScaleAndReleaseProgram(texture);
// deactivate vertex and color arrays after drawing
glDisableClientState(GL_VERTEX_ARRAY);
@ -693,11 +666,28 @@ void VoxelSystem::render(bool texture) {
// bind with 0 to switch back to normal operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
pthread_mutex_unlock(&_bufferWriteLock);
}
void VoxelSystem::applyScaleAndBindProgram(bool texture) {
glPushMatrix();
glScalef(_treeScale, _treeScale, _treeScale);
if (texture) {
_perlinModulateProgram->bind();
glBindTexture(GL_TEXTURE_2D, _permutationNormalTextureID);
}
}
void VoxelSystem::removeScaleAndReleaseProgram(bool texture) {
// scale back down to 1 so heads aren't massive
glPopMatrix();
pthread_mutex_unlock(&_bufferWriteLock);
if (texture) {
_perlinModulateProgram->release();
glBindTexture(GL_TEXTURE_2D, 0);
}
}
int VoxelSystem::_nodeCount = 0;
@ -867,7 +857,7 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) {
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* childNode = node->getChildAtIndex(i);
if (childNode) {
ViewFrustum::location inFrustum = childNode->inFrustum(*thisVoxelSystem->_viewFrustum);
ViewFrustum::location inFrustum = childNode->inFrustum(*Application::getInstance()->getViewFrustum());
switch (inFrustum) {
case ViewFrustum::OUTSIDE: {
args->nodesOutside++;
@ -919,9 +909,9 @@ bool VoxelSystem::hasViewChanged() {
}
// If our viewFrustum has changed since our _lastKnowViewFrustum
if (_viewFrustum && !_lastStableViewFrustum.matches(_viewFrustum)) {
if (!_lastStableViewFrustum.matches(Application::getInstance()->getViewFrustum())) {
result = true;
_lastStableViewFrustum = *_viewFrustum; // save last stable
_lastStableViewFrustum = *Application::getInstance()->getViewFrustum(); // save last stable
}
return result;
}

View file

@ -16,7 +16,6 @@
#include <AgentData.h>
#include <VoxelTree.h>
#include <ViewFrustum.h>
#include "Avatar.h"
#include "Camera.h"
#include "Util.h"
#include "world.h"
@ -27,22 +26,18 @@ const int NUM_CHILDREN = 8;
class VoxelSystem : public AgentData {
public:
VoxelSystem();
VoxelSystem(float treeScale = TREE_SCALE, int maxVoxels = MAX_VOXELS_PER_SYSTEM);
~VoxelSystem();
int parseData(unsigned char* sourceBuffer, int numBytes);
void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; };
void init();
virtual void init();
void simulate(float deltaTime) { };
void render(bool texture);
unsigned long getVoxelsUpdated() const {return _voxelsUpdated;};
unsigned long getVoxelsRendered() const {return _voxelsInReadArrays;};
void setViewerAvatar(Avatar *newViewerAvatar) { _viewerAvatar = newViewerAvatar; };
void setCamera(Camera* newCamera) { _camera = newCamera; };
void loadVoxelsFile(const char* fileName,bool wantColorRandomizer);
void writeToSVOFile(const char* filename, VoxelNode* node) const;
bool readFromSVOFile(const char* filename);
@ -66,7 +61,7 @@ public:
void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; };
bool getRenderPipelineWarnings() const { return _renderWarningsOn; };
void removeOutOfView();
virtual void removeOutOfView();
bool hasViewChanged();
bool isViewChanging();
@ -88,6 +83,22 @@ public:
void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot);
void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode);
protected:
float _treeScale;
int _maxVoxels;
VoxelTree* _tree;
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
void setupNewVoxelsForDrawing();
virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color);
virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void applyScaleAndBindProgram(bool texture);
virtual void removeScaleAndReleaseProgram(bool texture);
private:
// disallow copying of VoxelSystem objects
@ -116,13 +127,12 @@ private:
void copyWrittenDataToReadArraysFullVBOs();
void copyWrittenDataToReadArraysPartialVBOs();
void updateVBOs();
// these are kinda hacks, used by getDistanceFromViewRangeOperation() probably shouldn't be here
static float _maxDistance;
static float _minDistance;
Avatar* _viewerAvatar;
Camera* _camera;
VoxelTree* _tree;
GLfloat* _readVerticesArray;
GLubyte* _readColorsArray;
GLfloat* _writeVerticesArray;
@ -130,8 +140,8 @@ private:
bool* _writeVoxelDirtyArray;
bool* _readVoxelDirtyArray;
unsigned long _voxelsUpdated;
unsigned long _voxelsInWriteArrays;
unsigned long _voxelsInReadArrays;
unsigned long _voxelsInWriteArrays;
unsigned long _unusedArraySpace;
bool _writeRenderFullVBO;
@ -149,26 +159,21 @@ private:
pthread_mutex_t _bufferWriteLock;
pthread_mutex_t _treeLock;
ProgramObject* _perlinModulateProgram;
GLuint _permutationNormalTextureID;
ViewFrustum* _viewFrustum;
ViewFrustum _lastKnowViewFrustum;
ViewFrustum _lastStableViewFrustum;
int newTreeToArrays(VoxelNode *currentNode);
void cleanupRemovedVoxels();
void setupNewVoxelsForDrawing();
void copyWrittenDataToReadArrays(bool fullVBOs);
void updateFullVBOs(); // all voxels in the VBO
void updatePartialVBOs(); // multiple segments, only dirty voxels
bool _voxelsDirty;
public:
void updateVBOs();
void updateFullVBOs(); // all voxels in the VBO
void updatePartialVBOs(); // multiple segments, only dirty voxels
static ProgramObject* _perlinModulateProgram;
static GLuint _permutationNormalTextureID;
};
#endif

View file

@ -292,13 +292,16 @@ glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4&
glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset,
secondAxisMaxPlane + thirdAxisMaxPlane + offset };
float minDistance = FLT_MAX;
for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) {
float divisor = glm::dot(direction, diagonals[i]);
if (fabs(divisor) < EPSILON) {
continue; // segment is parallel to diagonal plane
}
float directionalDistance = -glm::dot(origin, diagonals[i]) / divisor;
return getClosestPointOnFace(glm::vec3(origin + direction * directionalDistance), face);
minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance);
}
if (minDistance != FLT_MAX) {
return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face);
}
}