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

This commit is contained in:
Stephen Birarda 2013-09-26 09:43:11 -07:00
commit e48a269fe3
20 changed files with 625 additions and 314 deletions

View file

@ -64,8 +64,8 @@ development.
Running Interface
-----
Using finder locate the interface.app Application in build/interface/Debug,
double-click the icon, and wait for interface to launch. At this point you will
Using Finder, locate the interface.app Application in build/interface/Debug,
double-click the icon, and wait for interface to launch. At this point you will automatically
connect to our default domain: "root.highfidelity.io".
I'm in-world, what can I do?
@ -94,13 +94,13 @@ I want to run my own virtual world!
In order to set up your own virtual world, you need to set up and run your own
local "domain". At a minimum, you must run a domain-server, voxel-server,
audio-mixer, and avatar-mixer to have a working virtual world. The audio-mixer, avatar-mixer, and voxel-server are assignments given from the domain-server to any assignment-client that reports directly to it.
audio-mixer, and avatar-mixer to have a working virtual world. The domain server gives three different types of assignments to the assignment-client: audio-mixer, avatar-mixer and voxel server.
Complete the steps above to build the system components, using the default Cmake Unix Makefiles generator. Start with an empty build directory.
cmake ..
Then from the terminal
Then from the Terminal
window, change directory into the build directory, make the needed components, and then launch them.
First we make the targets we'll need.
@ -114,15 +114,17 @@ If after this step you're seeing something like the following
you likely had Cmake generate Xcode project files and have not run `cmake ..` in a clean build directory.
Then, launch the static domain-server. All of the targets will run in the foreground, so you'll either want to background it yourself or open a seperate terminal window per target.
Then, launch the static domain-server. All of the targets will run in the foreground, so you'll either want to background it yourself or open a separate terminal window per target.
cd domain-server && ./domain-server
Then, run an assignment-client with 3 forks to fulfill the avatar-mixer, audio-mixer, and voxel-server assignments. It uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
Then, run an assignment-client with all three necessary components: avatar-mixer, audio-mixer, and voxel-server assignments. The assignment-client uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
In a new Terminal window, run:
./assignment-client/assignment-client -n 3
Any target can be terminated with CTRL-C (SIGINT) in the associated terminal window.
Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal window.
To test things out you'll want to run the Interface client. You can make that target with the following command:
@ -130,9 +132,9 @@ To test things out you'll want to run the Interface client. You can make that ta
Then run the executable it builds, or open interface.app if you're on OS X.
To access your local domain in Interface, open the Preferences dialog box, from
the Interface menu on OS X or the File menu on Linux, and enter "localhost" for the
server hostname in the "Domain" edit control.
To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field.
If everything worked you should see "Servers: 3" in the upper right. Nice work!
In the voxel-server/src directory you will find a README that explains in
further detail how to setup and administer a voxel-server.

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

View file

@ -0,0 +1,28 @@
#version 120
//
// eye.vert
// vertex shader
//
// Created by Andrzej Kapolka on 9/25/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// the interpolated normal
varying vec4 normal;
void main(void) {
// transform and store the normal for interpolation
normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0));
// compute standard diffuse lighting per-vertex
gl_FrontColor = vec4(gl_Color.rgb * (gl_LightModel.ambient.rgb + gl_LightSource[0].ambient.rgb +
gl_LightSource[0].diffuse.rgb * max(0.0, dot(normal, gl_LightSource[0].position))), gl_Color.a);
// pass along the texture coordinate
gl_TexCoord[0] = gl_MultiTexCoord0;
// use standard pipeline transform
gl_Position = ftransform();
}

View file

@ -1798,27 +1798,7 @@ void Application::update(float deltaTime) {
_isHoverVoxelSounding = true;
}
}
// If we are dragging on a voxel, add thrust according to the amount the mouse is dragging
const float VOXEL_GRAB_THRUST = 0.0f;
if (_mousePressed && (_mouseVoxel.s != 0)) {
glm::vec2 mouseDrag(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY);
glm::quat orientation = _myAvatar.getOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
glm::vec3 up = orientation * IDENTITY_UP;
glm::vec3 towardVoxel = getMouseVoxelWorldCoordinates(_mouseVoxelDragging)
- _myAvatar.getCameraPosition();
towardVoxel = front * glm::length(towardVoxel);
glm::vec3 lateralToVoxel = glm::cross(up, glm::normalize(towardVoxel)) * glm::length(towardVoxel);
_voxelThrust = glm::vec3(0, 0, 0);
_voxelThrust += towardVoxel * VOXEL_GRAB_THRUST * deltaTime * mouseDrag.y;
_voxelThrust += lateralToVoxel * VOXEL_GRAB_THRUST * deltaTime * mouseDrag.x;
// Add thrust from voxel grabbing to the avatar
_myAvatar.addThrust(_voxelThrust);
}
_mouseVoxel.s = 0.0f;
if (Menu::getInstance()->isVoxelModeActionChecked() &&
(fabs(_myAvatar.getVelocity().x) +
@ -2820,7 +2800,7 @@ void Application::displayStats() {
char avatarStats[200];
glm::vec3 avatarPos = _myAvatar.getPosition();
sprintf(avatarStats, "Avatar position: %.3f, %.3f, %.3f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, _myAvatar.getBodyYaw());
sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw());
drawtext(10, statsVerticalOffset + 55, 0.10f, 0, 1.0, 0, avatarStats);

View file

@ -297,7 +297,6 @@ private:
float _pitchFromTouch;
VoxelDetail _mouseVoxelDragging;
glm::vec3 _voxelThrust;
bool _mousePressed; // true if mouse has been pressed (clear when finished)
VoxelDetail _hoverVoxel; // Stuff about the voxel I am hovering or clicking

View file

@ -275,8 +275,6 @@ Menu::Menu() :
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::UseFaceshiftRig, 0, false,
appInstance->getFaceshift(), SLOT(setUsingRig(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::UsePerlinFace, 0, false);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtVectors, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtIndicator, 0, true);

View file

@ -199,7 +199,6 @@ namespace MenuOption {
const QString TestRaveGlove = "Test Rave Glove";
const QString TreeStats = "Calculate Tree Stats";
const QString TransmitterDrive = "Transmitter Drive";
const QString UseFaceshiftRig = "Use Faceshift Rig";
const QString UsePerlinFace = "Use Perlin's Face";
const QString Quit = "Quit";
const QString Webcam = "Webcam";

View file

@ -17,22 +17,39 @@ using namespace std;
BlendFace::BlendFace(Head* owningHead) :
_owningHead(owningHead),
_modelReply(NULL),
_iboID(0)
_modelReply(NULL)
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
BlendFace::~BlendFace() {
if (_iboID != 0) {
glDeleteBuffers(1, &_iboID);
glDeleteBuffers(1, &_vboID);
deleteGeometry();
}
ProgramObject BlendFace::_eyeProgram;
GLuint BlendFace::_eyeTextureID;
void BlendFace::init() {
if (!_eyeProgram.isLinked()) {
switchToResourcesParentIfRequired();
_eyeProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/eye.vert");
_eyeProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/iris.frag");
_eyeProgram.link();
_eyeProgram.bind();
_eyeProgram.setUniformValue("texture", 0);
_eyeProgram.release();
_eyeTextureID = Application::getInstance()->getTextureCache()->getFileTextureID("resources/images/eye.png");
}
}
const glm::vec3 MODEL_TRANSLATION(0.0f, -0.025f, -0.025f); // temporary fudge factor
const float MODEL_SCALE = 0.0006f;
bool BlendFace::render(float alpha) {
if (_iboID == 0) {
if (_meshIDs.isEmpty()) {
return false;
}
@ -40,51 +57,101 @@ bool BlendFace::render(float alpha) {
glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z);
glm::quat orientation = _owningHead->getOrientation();
glm::vec3 axis = glm::axis(orientation);
glRotatef(glm::angle(orientation), axis.x, axis.y, axis.z);
glTranslatef(0.0f, -0.025f, -0.025f); // temporary fudge factor until we have a better method of per-model positioning
const float MODEL_SCALE = 0.0006f;
glScalef(_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
glRotatef(glm::angle(orientation), axis.x, axis.y, axis.z);
glTranslatef(MODEL_TRANSLATION.x, MODEL_TRANSLATION.y, MODEL_TRANSLATION.z);
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
glScalef(scale.x, scale.y, scale.z);
glColor4f(1.0f, 1.0f, 1.0f, alpha);
// start with the base
int vertexCount = _geometry.vertices.size();
_blendedVertices.resize(vertexCount);
memcpy(_blendedVertices.data(), _geometry.vertices.constData(), vertexCount * sizeof(glm::vec3));
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
// blend in each coefficient
const vector<float>& coefficients = _owningHead->getBlendshapeCoefficients();
for (int i = 0; i < coefficients.size(); i++) {
float coefficient = coefficients[i];
if (coefficient == 0.0f || i >= _geometry.blendshapes.size() || _geometry.blendshapes[i].vertices.isEmpty()) {
continue;
// enable normalization under the expectation that the GPU can do it faster
glEnable(GL_NORMALIZE);
glColor4f(_owningHead->getSkinColor().r, _owningHead->getSkinColor().g, _owningHead->getSkinColor().b, alpha);
for (int i = 0; i < _meshIDs.size(); i++) {
const VerticesIndices& ids = _meshIDs.at(i);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ids.first);
glBindBuffer(GL_ARRAY_BUFFER, ids.second);
const FBXMesh& mesh = _geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
// apply eye rotation if appropriate
if (mesh.isEye) {
glPushMatrix();
glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z);
glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation *
(mesh.pivot * scale + MODEL_TRANSLATION) + _owningHead->getPosition());
glm::vec3 rotationAxis = glm::axis(rotation);
glRotatef(glm::angle(rotation), -rotationAxis.x, rotationAxis.y, -rotationAxis.z);
glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z);
// use texture coordinates only for the eye, for now
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glBindTexture(GL_TEXTURE_2D, _eyeTextureID);
glEnable(GL_TEXTURE_2D);
_eyeProgram.bind();
}
const glm::vec3* source = _geometry.blendshapes[i].vertices.constData();
for (const int* index = _geometry.blendshapes[i].indices.constData(),
*end = index + _geometry.blendshapes[i].indices.size(); index != end; index++, source++) {
_blendedVertices[*index] += *source * coefficient;
// all meshes after the first are white
if (i == 1) {
glColor4f(1.0f, 1.0f, 1.0f, alpha);
}
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
const vector<float>& coefficients = _owningHead->getBlendshapeCoefficients();
for (int i = 0; i < coefficients.size(); i++) {
float coefficient = coefficients[i];
if (coefficient == 0.0f || i >= mesh.blendshapes.size() || mesh.blendshapes[i].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = mesh.blendshapes[i].vertices.constData();
const glm::vec3* normal = mesh.blendshapes[i].normals.constData();
for (const int* index = mesh.blendshapes[i].indices.constData(),
*end = index + mesh.blendshapes[i].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
}
glVertexPointer(3, GL_FLOAT, 0, 0);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3)));
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, mesh.quadIndices.size(), GL_UNSIGNED_INT, 0);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, mesh.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)(mesh.quadIndices.size() * sizeof(int)));
if (mesh.isEye) {
_eyeProgram.release();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glPopMatrix();
}
}
// update the blended vertices
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
// tell OpenGL where to find vertex information
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, 0);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID);
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, _geometry.quadIndices.size(), GL_UNSIGNED_INT, 0);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, _geometry.triangleIndices.size(), GL_UNSIGNED_INT,
(void*)(_geometry.quadIndices.size() * sizeof(int)));
glDisable(GL_NORMALIZE);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// deactivate vertex arrays after drawing
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// bind with 0 to switch back to normal operation
@ -96,6 +163,25 @@ bool BlendFace::render(float alpha) {
return true;
}
void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
glm::quat orientation = _owningHead->getOrientation();
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
bool foundFirst = false;
foreach (const FBXMesh& mesh, _geometry.meshes) {
if (mesh.isEye) {
glm::vec3 position = orientation * (mesh.pivot * scale + MODEL_TRANSLATION) + _owningHead->getPosition();
if (foundFirst) {
secondEyePosition = position;
return;
}
firstEyePosition = position;
foundFirst = true;
}
}
}
void BlendFace::setModelURL(const QUrl& url) {
// don't restart the download if it's the same URL
if (_modelURL == url) {
@ -129,41 +215,6 @@ glm::vec3 createVec3(const fsVector3f& vector) {
return glm::vec3(vector.x, vector.y, vector.z);
}
void BlendFace::setRig(const fsMsgRig& rig) {
// convert to FBX geometry
FBXGeometry geometry;
for (vector<fsVector4i>::const_iterator it = rig.mesh().m_quads.begin(), end = rig.mesh().m_quads.end(); it != end; it++) {
geometry.quadIndices.append(it->x);
geometry.quadIndices.append(it->y);
geometry.quadIndices.append(it->z);
geometry.quadIndices.append(it->w);
}
for (vector<fsVector3i>::const_iterator it = rig.mesh().m_tris.begin(), end = rig.mesh().m_tris.end(); it != end; it++) {
geometry.triangleIndices.append(it->x);
geometry.triangleIndices.append(it->y);
geometry.triangleIndices.append(it->z);
}
for (vector<fsVector3f>::const_iterator it = rig.mesh().m_vertex_data.m_vertices.begin(),
end = rig.mesh().m_vertex_data.m_vertices.end(); it != end; it++) {
geometry.vertices.append(glm::vec3(it->x, it->y, it->z));
}
for (vector<fsVertexData>::const_iterator it = rig.blendshapes().begin(), end = rig.blendshapes().end(); it != end; it++) {
FBXBlendshape blendshape;
for (int i = 0, n = it->m_vertices.size(); i < n; i++) {
// subtract the base vertex position; we want the deltas
blendshape.vertices.append(createVec3(it->m_vertices[i]) - geometry.vertices[i]);
blendshape.indices.append(i);
}
geometry.blendshapes.append(blendshape);
}
setGeometry(geometry);
}
void BlendFace::handleModelDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (bytesReceived < bytesTotal && !_modelReply->isFinished()) {
return;
@ -192,30 +243,52 @@ void BlendFace::handleModelReplyError() {
}
void BlendFace::setGeometry(const FBXGeometry& geometry) {
if (geometry.vertices.isEmpty()) {
// clear any existing geometry
if (_iboID != 0) {
glDeleteBuffers(1, &_iboID);
glDeleteBuffers(1, &_vboID);
_iboID = 0;
}
// clear any existing geometry
deleteGeometry();
if (geometry.meshes.isEmpty()) {
return;
}
if (_iboID == 0) {
glGenBuffers(1, &_iboID);
glGenBuffers(1, &_vboID);
foreach (const FBXMesh& mesh, geometry.meshes) {
VerticesIndices ids;
glGenBuffers(1, &ids.first);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ids.first);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mesh.quadIndices.size() + mesh.triangleIndices.size()) * sizeof(int),
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh.quadIndices.size() * sizeof(int), mesh.quadIndices.constData());
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, mesh.quadIndices.size() * sizeof(int),
mesh.triangleIndices.size() * sizeof(int), mesh.triangleIndices.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenBuffers(1, &ids.second);
glBindBuffer(GL_ARRAY_BUFFER, ids.second);
if (mesh.blendshapes.isEmpty()) {
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(glm::vec3),
mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3),
mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
} else {
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3),
NULL, GL_DYNAMIC_DRAW);
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
_meshIDs.append(ids);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _iboID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (geometry.quadIndices.size() + geometry.triangleIndices.size()) * sizeof(int),
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, geometry.quadIndices.size() * sizeof(int), geometry.quadIndices.constData());
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, geometry.quadIndices.size() * sizeof(int),
geometry.triangleIndices.size() * sizeof(int), geometry.triangleIndices.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glBufferData(GL_ARRAY_BUFFER, geometry.vertices.size() * sizeof(glm::vec3), NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
_geometry = geometry;
}
void BlendFace::deleteGeometry() {
foreach (const VerticesIndices& meshIDs, _meshIDs) {
glDeleteBuffers(1, &meshIDs.first);
glDeleteBuffers(1, &meshIDs.second);
}
_meshIDs.clear();
}

View file

@ -12,10 +12,9 @@
#include <QObject>
#include <QUrl>
#include <fsbinarystream.h>
#include "InterfaceConfig.h"
#include "renderer/FBXReader.h"
#include "renderer/ProgramObject.h"
class QNetworkReply;
@ -30,16 +29,15 @@ public:
BlendFace(Head* owningHead);
~BlendFace();
bool isActive() const { return _iboID != 0; }
bool isActive() const { return !_meshIDs.isEmpty(); }
void init();
bool render(float alpha);
Q_INVOKABLE void setModelURL(const QUrl& url);
const QUrl& getModelURL() const { return _modelURL; }
public slots:
void setRig(const fs::fsMsgRig& rig);
void getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
private slots:
@ -49,6 +47,7 @@ private slots:
private:
void setGeometry(const FBXGeometry& geometry);
void deleteGeometry();
Head* _owningHead;
@ -56,11 +55,15 @@ private:
QNetworkReply* _modelReply;
GLuint _iboID;
GLuint _vboID;
typedef QPair<GLuint, GLuint> VerticesIndices;
QVector<VerticesIndices> _meshIDs;
FBXGeometry _geometry;
QVector<glm::vec3> _blendedVertices;
QVector<glm::vec3> _blendedNormals;
static ProgramObject _eyeProgram;
static GLuint _eyeTextureID;
};
#endif /* defined(__interface__BlendFace__) */

View file

@ -6,8 +6,6 @@
#include <glm/gtx/quaternion.hpp>
#include <QImage>
#include <NodeList.h>
#include "Application.h"
@ -105,16 +103,14 @@ void Head::init() {
_irisProgram.setUniformValue("texture", 0);
_eyePositionLocation = _irisProgram.uniformLocation("eyePosition");
QImage image = QImage(IRIS_TEXTURE_FILENAME).convertToFormat(QImage::Format_ARGB32);
glGenTextures(1, &_irisTextureID);
_irisTextureID = Application::getInstance()->getTextureCache()->getFileTextureID(IRIS_TEXTURE_FILENAME);
glBindTexture(GL_TEXTURE_2D, _irisTextureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glBindTexture(GL_TEXTURE_2D, 0);
}
_blendFace.init();
}
void Head::reset() {
@ -347,7 +343,13 @@ void Head::render(float alpha, bool isMine) {
}
if (_renderLookatVectors) {
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
glm::vec3 firstEyePosition = _leftEyePosition;
glm::vec3 secondEyePosition = _rightEyePosition;
if (_blendFace.isActive()) {
// the blend face may have custom eye meshes
_blendFace.getEyePositions(firstEyePosition, secondEyePosition);
}
renderLookatVectors(firstEyePosition, secondEyePosition, _lookAtPosition);
}
}
@ -459,6 +461,11 @@ glm::quat Head::getCameraOrientation () const {
* glm::quat(glm::radians(glm::vec3(_cameraPitch + _mousePitch, _cameraYaw, 0.0f)));
}
glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
glm::quat orientation = getOrientation();
return rotationBetween(orientation * IDENTITY_FRONT, _lookAtPosition + _saccade - eyePosition) * orientation;
}
void Head::renderHeadSphere() {
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z); //translate to head position
@ -663,17 +670,13 @@ void Head::renderEyeBalls() {
glBindTexture(GL_TEXTURE_2D, _irisTextureID);
glEnable(GL_TEXTURE_2D);
glm::quat orientation = getOrientation();
glm::vec3 front = orientation * IDENTITY_FRONT;
// render left iris
glm::quat leftIrisRotation;
glPushMatrix(); {
glTranslatef(_leftEyePosition.x, _leftEyePosition.y, _leftEyePosition.z); //translate to eyeball position
//rotate the eyeball to aim towards the lookat position
glm::vec3 targetLookatVector = _lookAtPosition + _saccade - _leftEyePosition;
leftIrisRotation = rotationBetween(front, targetLookatVector) * orientation;
leftIrisRotation = getEyeRotation(_leftEyePosition);
glm::vec3 rotationAxis = glm::axis(leftIrisRotation);
glRotatef(glm::angle(leftIrisRotation), rotationAxis.x, rotationAxis.y, rotationAxis.z);
glTranslatef(0.0f, 0.0f, -_scale * IRIS_PROTRUSION);
@ -697,8 +700,7 @@ void Head::renderEyeBalls() {
glTranslatef(_rightEyePosition.x, _rightEyePosition.y, _rightEyePosition.z); //translate to eyeball position
//rotate the eyeball to aim towards the lookat position
glm::vec3 targetLookatVector = _lookAtPosition + _saccade - _rightEyePosition;
rightIrisRotation = rotationBetween(front, targetLookatVector) * orientation;
rightIrisRotation = getEyeRotation(_rightEyePosition);
glm::vec3 rotationAxis = glm::axis(rightIrisRotation);
glRotatef(glm::angle(rightIrisRotation), rotationAxis.x, rotationAxis.y, rotationAxis.z);
glTranslatef(0.0f, 0.0f, -_scale * IRIS_PROTRUSION);

View file

@ -66,11 +66,14 @@ public:
float getScale() const { return _scale; }
glm::vec3 getPosition() const { return _position; }
const glm::vec3& getSkinColor() const { return _skinColor; }
const glm::vec3& getEyePosition() const { return _eyePosition; }
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat getEyeRotation(const glm::vec3& eyePosition) const;
Face& getFace() { return _face; }
BlendFace& getBlendFace() { return _blendFace; }

View file

@ -50,7 +50,8 @@ MyAvatar::MyAvatar(Node* owningNode) :
_elapsedTimeSinceCollision(0.0f),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
_isThrustOn(false)
_isThrustOn(false),
_thrustMultiplier(1.0f)
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = false;
@ -59,14 +60,6 @@ MyAvatar::MyAvatar(Node* owningNode) :
_collisionRadius = _height * COLLISION_RADIUS_SCALE;
}
void MyAvatar::init() {
Avatar::init();
// when we receive a Faceshift rig, apply it to our own blend face
_head.getBlendFace().connect(Application::getInstance()->getFaceshift(), SIGNAL(rigReceived(fs::fsMsgRig)),
SLOT(setRig(fs::fsMsgRig)));
}
void MyAvatar::reset() {
_head.reset();
_hand.reset();
@ -218,9 +211,10 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter, float gyroCam
const float STATIC_FRICTION_STRENGTH = _scale * 20.f;
applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_VELOCITY, STATIC_FRICTION_STRENGTH);
const float LINEAR_DAMPING_STRENGTH = 1.0f;
const float LINEAR_DAMPING_STRENGTH = 0.5f;
const float SPEED_BRAKE_POWER = _scale * 10.0f;
const float SQUARED_DAMPING_STRENGTH = 0.2f;
// Note: PER changed squared damping strength to zero
const float SQUARED_DAMPING_STRENGTH = 0.0f;
if (_speedBrakes) {
applyDamping(deltaTime, _velocity, LINEAR_DAMPING_STRENGTH * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER);
} else {
@ -634,15 +628,23 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
const float THRUST_JUMP = 120.f;
// Add Thrusts from keyboard
if (_driveKeys[FWD]) {_thrust += _scale * THRUST_MAG_FWD * deltaTime * front;}
if (_driveKeys[BACK]) {_thrust -= _scale * THRUST_MAG_BACK * deltaTime * front;}
if (_driveKeys[RIGHT]) {_thrust += _scale * THRUST_MAG_LATERAL * deltaTime * right;}
if (_driveKeys[LEFT]) {_thrust -= _scale * THRUST_MAG_LATERAL * deltaTime * right;}
if (_driveKeys[UP]) {_thrust += _scale * THRUST_MAG_UP * deltaTime * up;}
if (_driveKeys[DOWN]) {_thrust -= _scale * THRUST_MAG_DOWN * deltaTime * up;}
if (_driveKeys[FWD]) {_thrust += _scale * THRUST_MAG_FWD * _thrustMultiplier * deltaTime * front;}
if (_driveKeys[BACK]) {_thrust -= _scale * THRUST_MAG_BACK * _thrustMultiplier * deltaTime * front;}
if (_driveKeys[RIGHT]) {_thrust += _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;}
if (_driveKeys[LEFT]) {_thrust -= _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;}
if (_driveKeys[UP]) {_thrust += _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up;}
if (_driveKeys[DOWN]) {_thrust -= _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;}
if (_driveKeys[ROT_RIGHT]) {_bodyYawDelta -= YAW_MAG * deltaTime;}
if (_driveKeys[ROT_LEFT]) {_bodyYawDelta += YAW_MAG * deltaTime;}
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
const float THRUST_INCREASE_RATE = 1.0;
_thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE;
} else {
_thrustMultiplier = 1.f;
}
// Add one time jumping force if requested
if (_shouldJump) {
_thrust += _scale * THRUST_JUMP * up;

View file

@ -15,7 +15,6 @@ class MyAvatar : public Avatar {
public:
MyAvatar(Node* owningNode = NULL);
void init();
void reset();
void simulate(float deltaTime, Transmitter* transmitter, float gyroCameraSensitivity);
void updateFromGyrosAndOrWebcam(bool gyroLook, float pitchFromTouch);
@ -72,6 +71,7 @@ public:
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
bool _isThrustOn;
float _thrustMultiplier;
float _collisionRadius;
// private methods

View file

@ -20,7 +20,7 @@ const quint16 FACESHIFT_PORT = 33433;
Faceshift::Faceshift() :
_tcpEnabled(false),
_lastMessageReceived(0),
_lastTrackingStateReceived(0),
_eyeGazeLeftPitch(0.0f),
_eyeGazeLeftYaw(0.0f),
_eyeGazeRightPitch(0.0f),
@ -53,8 +53,7 @@ Faceshift::Faceshift() :
bool Faceshift::isActive() const {
const uint64_t ACTIVE_TIMEOUT_USECS = 1000000;
return (_tcpSocket.state() == QAbstractSocket::ConnectedState ||
(usecTimestampNow() - _lastMessageReceived) < ACTIVE_TIMEOUT_USECS) && _tracking;
return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS;
}
void Faceshift::update() {
@ -87,17 +86,6 @@ void Faceshift::setTCPEnabled(bool enabled) {
}
}
void Faceshift::setUsingRig(bool usingRig) {
if (usingRig && _tcpSocket.state() == QAbstractSocket::ConnectedState) {
string message;
fsBinaryStream::encode_message(message, fsMsgSendRig());
send(message);
} else {
emit rigReceived(fsMsgRig());
}
}
void Faceshift::connectSocket() {
if (_tcpEnabled) {
qDebug("Faceshift: Connecting...\n");
@ -114,11 +102,6 @@ void Faceshift::noteConnected() {
string message;
fsBinaryStream::encode_message(message, fsMsgSendBlendshapeNames());
send(message);
// if using faceshift rig, request it
if (Menu::getInstance()->isOptionChecked(MenuOption::UseFaceshiftRig)) {
setUsingRig(true);
}
}
void Faceshift::noteError(QAbstractSocket::SocketError error) {
@ -169,6 +152,8 @@ void Faceshift::receive(const QByteArray& buffer) {
_eyeGazeRightPitch = -data.m_eyeGazeRightPitch;
_eyeGazeRightYaw = data.m_eyeGazeRightYaw;
_blendshapeCoefficients = data.m_coeffs;
_lastTrackingStateReceived = usecTimestampNow();
}
break;
}
@ -214,14 +199,8 @@ void Faceshift::receive(const QByteArray& buffer) {
}
break;
}
case fsMsg::MSG_OUT_RIG: {
fsMsgRig* rig = static_cast<fsMsgRig*>(msg.get());
emit rigReceived(*rig);
break;
}
default:
break;
}
}
_lastMessageReceived = usecTimestampNow();
}

View file

@ -60,15 +60,10 @@ public:
void update();
void reset();
signals:
void rigReceived(const fs::fsMsgRig& rig);
public slots:
void setTCPEnabled(bool enabled);
void setUsingRig(bool usingRig);
private slots:
@ -90,7 +85,7 @@ private:
fs::fsBinaryStream _stream;
bool _tcpEnabled;
bool _tracking;
uint64_t _lastMessageReceived;
uint64_t _lastTrackingStateReceived;
glm::quat _headRotation;
glm::vec3 _headTranslation;

View file

@ -189,7 +189,20 @@ FBXNode parseFBX(QIODevice* device) {
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec3> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
values.append(glm::vec3(*it++, *it++, *it++));
float x = *it++;
float y = *it++;
float z = *it++;
values.append(glm::vec3(x, y, z));
}
return values;
}
QVector<glm::vec2> createVec2Vector(const QVector<double>& doubleVector) {
QVector<glm::vec2> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
float s = *it++;
float t = *it++;
values.append(glm::vec2(s, t));
}
return values;
}
@ -259,21 +272,33 @@ QHash<QByteArray, int> createBlendshapeMap() {
}
}
class ExtractedBlendshape {
public:
qint64 id;
int index;
FBXBlendshape blendshape;
};
FBXGeometry extractFBXGeometry(const FBXNode& node) {
QVector<FBXBlendshape> blendshapes;
QHash<qint64, FBXGeometry> meshMap;
qint64 blendshapeId = 0;
QHash<qint64, FBXMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
QHash<qint64, qint64> parentMap;
QMultiHash<qint64, qint64> childMap;
QHash<qint64, glm::vec3> pivots;
qint64 jointEyeLeftID = 0;
qint64 jointEyeRightID = 0;
foreach (const FBXNode& child, node.children) {
if (child.name == "Objects") {
foreach (const FBXNode& object, child.children) {
if (object.name == "Geometry") {
if (object.properties.at(2) == "Mesh") {
FBXGeometry mesh;
FBXMesh mesh;
QVector<glm::vec3> normals;
QVector<int> polygonIndices;
QVector<glm::vec3> normals;
QVector<glm::vec2> texCoords;
QVector<int> texCoordIndices;
foreach (const FBXNode& data, object.children) {
if (data.name == "Vertices") {
mesh.vertices = createVec3Vector(data.properties.at(0).value<QVector<double> >());
@ -282,19 +307,48 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
polygonIndices = data.properties.at(0).value<QVector<int> >();
} else if (data.name == "LayerElementNormal") {
bool byVertex = false;
foreach (const FBXNode& subdata, data.children) {
if (subdata.name == "Normals") {
normals = createVec3Vector(subdata.properties.at(0).value<QVector<double> >());
} else if (subdata.name == "MappingInformationType" &&
subdata.properties.at(0) == "ByVertice") {
byVertex = true;
}
}
}
if (byVertex) {
mesh.normals = normals;
}
} else if (data.name == "LayerElementUV" && data.properties.at(0).toInt() == 0) {
foreach (const FBXNode& subdata, data.children) {
if (subdata.name == "UV") {
texCoords = createVec2Vector(subdata.properties.at(0).value<QVector<double> >());
} else if (subdata.name == "UVIndex") {
texCoordIndices = subdata.properties.at(0).value<QVector<int> >();
}
}
}
}
// the (base) normals correspond to the polygon indices, for some reason
mesh.normals.resize(mesh.vertices.size());
// convert normals from per-index to per-vertex if necessary
if (mesh.normals.isEmpty()) {
mesh.normals.resize(mesh.vertices.size());
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
mesh.normals[index < 0 ? (-index - 1) : index] = normals.at(i);
}
}
// same with the tex coords
mesh.texCoords.resize(mesh.vertices.size());
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
mesh.normals[index < 0 ? (-index - 1) : index] = normals[i];
int texCoordIndex = texCoordIndices.at(i);
if (texCoordIndex >= 0) {
mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex);
}
}
// convert the polygons to quads and triangles
@ -323,50 +377,96 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
beginIndex = endIndex;
}
}
meshMap.insert(object.properties.at(0).value<qint64>(), mesh);
meshes.insert(object.properties.at(0).value<qint64>(), mesh);
} else { // object.properties.at(2) == "Shape"
FBXBlendshape blendshape;
ExtractedBlendshape extracted = { object.properties.at(0).value<qint64>() };
foreach (const FBXNode& data, object.children) {
if (data.name == "Indexes") {
blendshape.indices = data.properties.at(0).value<QVector<int> >();
extracted.blendshape.indices = data.properties.at(0).value<QVector<int> >();
} else if (data.name == "Vertices") {
blendshape.vertices = createVec3Vector(data.properties.at(0).value<QVector<double> >());
extracted.blendshape.vertices = createVec3Vector(
data.properties.at(0).value<QVector<double> >());
} else if (data.name == "Normals") {
blendshape.normals = createVec3Vector(data.properties.at(0).value<QVector<double> >());
extracted.blendshape.normals = createVec3Vector(
data.properties.at(0).value<QVector<double> >());
}
}
// the name is followed by a null and some type info
QByteArray name = object.properties.at(1).toByteArray();
static QHash<QByteArray, int> blendshapeMap = createBlendshapeMap();
int index = blendshapeMap.value(name.left(name.indexOf('\0')));
blendshapes.resize(qMax(blendshapes.size(), index + 1));
blendshapes[index] = blendshape;
extracted.index = blendshapeMap.value(name.left(name.indexOf('\0')));
blendshapes.append(extracted);
}
} else if (object.name == "Model" && object.properties.at(2) == "LimbNode") {
if (object.properties.at(1).toByteArray().startsWith("jointEyeLeft")) {
jointEyeLeftID = object.properties.at(0).value<qint64>();
} else if (object.properties.at(1).toByteArray().startsWith("jointEyeRight")) {
jointEyeRightID = object.properties.at(0).value<qint64>();
}
} else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "TransformLink") {
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
pivots.insert(object.properties.at(0).value<qint64>(),
glm::vec3(values.at(12), values.at(13), values.at(14))); // matrix translation component
}
}
} else if (object.name == "Deformer" && object.properties.at(2) == "BlendShape") {
blendshapeId = object.properties.at(0).value<qint64>();
}
}
} else if (child.name == "Connections") {
foreach (const FBXNode& connection, child.children) {
if (connection.name == "C") {
parentMap.insert(connection.properties.at(1).value<qint64>(), connection.properties.at(2).value<qint64>());
childMap.insert(connection.properties.at(2).value<qint64>(), connection.properties.at(1).value<qint64>());
}
}
}
}
// get the mesh that owns the blendshape
FBXGeometry geometry;
if (meshMap.size() == 1) {
geometry = *meshMap.begin();
} else {
geometry = meshMap.take(parentMap.value(blendshapeId));
// assign the blendshapes to their corresponding meshes
foreach (const ExtractedBlendshape& extracted, blendshapes) {
qint64 blendshapeChannelID = parentMap.value(extracted.id);
qint64 blendshapeID = parentMap.value(blendshapeChannelID);
qint64 meshID = parentMap.value(blendshapeID);
FBXMesh& mesh = meshes[meshID];
mesh.blendshapes.resize(max(mesh.blendshapes.size(), extracted.index + 1));
mesh.blendshapes[extracted.index] = extracted.blendshape;
}
// as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face
FBXGeometry geometry;
int mostBlendshapes = 0;
for (QHash<qint64, FBXMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
FBXMesh& mesh = it.value();
// look for a limb pivot
mesh.isEye = false;
foreach (qint64 childID, childMap.values(it.key())) {
qint64 clusterID = childMap.value(childID);
if (pivots.contains(clusterID)) {
mesh.pivot = pivots.value(clusterID);
qint64 jointID = childMap.value(clusterID);
if (jointID == jointEyeLeftID || jointID == jointEyeRightID) {
mesh.isEye = true;
}
}
}
if (mesh.blendshapes.size() > mostBlendshapes) {
geometry.meshes.prepend(mesh);
mostBlendshapes = mesh.blendshapes.size();
} else {
geometry.meshes.append(mesh);
}
}
geometry.blendshapes = blendshapes;
return geometry;
}

View file

@ -38,18 +38,30 @@ public:
QVector<glm::vec3> normals;
};
/// Base geometry with blendshapes mapped by name.
class FBXGeometry {
/// A single mesh (with optional blendshapes) extracted from an FBX document.
class FBXMesh {
public:
QVector<int> quadIndices;
QVector<int> triangleIndices;
QVector<glm::vec3> vertices;
QVector<glm::vec3> normals;
QVector<glm::vec2> texCoords;
glm::vec3 pivot;
bool isEye;
QVector<FBXBlendshape> blendshapes;
};
/// A set of meshes extracted from an FBX document.
class FBXGeometry {
public:
QVector<FBXMesh> meshes;
};
/// Parses the input from the supplied data as an FBX file.
/// \exception QString if an error occurs in parsing
FBXNode parseFBX(const QByteArray& data);

View file

@ -24,6 +24,9 @@ TextureCache::~TextureCache() {
if (_permutationNormalTextureID != 0) {
glDeleteTextures(1, &_permutationNormalTextureID);
}
foreach (GLuint id, _fileTextureIDs) {
glDeleteTextures(1, &id);
}
if (_primaryFramebufferObject != NULL) {
delete _primaryFramebufferObject;
glDeleteTextures(1, &_primaryDepthTextureID);
@ -62,6 +65,24 @@ GLuint TextureCache::getPermutationNormalTextureID() {
return _permutationNormalTextureID;
}
GLuint TextureCache::getFileTextureID(const QString& filename) {
GLuint id = _fileTextureIDs.value(filename);
if (id == 0) {
switchToResourcesParentIfRequired();
QImage image = QImage(filename).convertToFormat(QImage::Format_ARGB32);
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
_fileTextureIDs.insert(filename, id);
}
return id;
}
QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() {
if (_primaryFramebufferObject == NULL) {
_primaryFramebufferObject = createFramebufferObject();

View file

@ -9,6 +9,7 @@
#ifndef __interface__TextureCache__
#define __interface__TextureCache__
#include <QHash>
#include <QObject>
#include "InterfaceConfig.h"
@ -27,6 +28,9 @@ public:
/// the second, a set of random unit vectors to be used as noise gradients.
GLuint getPermutationNormalTextureID();
/// Returns the ID of a texture containing the contents of the specified file, loading it if necessary.
GLuint getFileTextureID(const QString& filename);
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
/// used for scene rendering.
QOpenGLFramebufferObject* getPrimaryFramebufferObject();
@ -50,6 +54,8 @@ private:
GLuint _permutationNormalTextureID;
QHash<QString, GLuint> _fileTextureIDs;
GLuint _primaryDepthTextureID;
QOpenGLFramebufferObject* _primaryFramebufferObject;
QOpenGLFramebufferObject* _secondaryFramebufferObject;

View file

@ -55,6 +55,184 @@ void voxelTutorial(VoxelTree * tree) {
}
}
void processSplitSVOFile(const char* splitSVOFile,const char* splitJurisdictionRoot,const char* splitJurisdictionEndNodes) {
char outputFileName[512];
printf("splitSVOFile: %s Jurisdictions Root: %s EndNodes: %s\n",
splitSVOFile, splitJurisdictionRoot, splitJurisdictionEndNodes);
VoxelTree rootSVO;
rootSVO.readFromSVOFile(splitSVOFile);
JurisdictionMap jurisdiction(splitJurisdictionRoot, splitJurisdictionEndNodes);
printf("Jurisdiction Root Octcode: ");
printOctalCode(jurisdiction.getRootOctalCode());
printf("Jurisdiction End Nodes: %d \n", jurisdiction.getEndNodeCount());
for (int i = 0; i < jurisdiction.getEndNodeCount(); i++) {
unsigned char* endNodeCode = jurisdiction.getEndNodeOctalCode(i);
printf("End Node: %d ", i);
printOctalCode(endNodeCode);
// get the endNode details
VoxelPositionSize endNodeDetails;
voxelDetailsForCode(endNodeCode, endNodeDetails);
// Now, create a split SVO for the EndNode.
// copy the EndNode into a temporary tree
VoxelTree endNodeTree;
// create a small voxels at corners of the endNode Tree, this will is a hack
// to work around a bug in voxel server that will send Voxel not exists
// for regions that don't contain anything even if they're not in the
// jurisdiction of the server
// This hack assumes the end nodes for demo dinner since it only guarantees
// nodes in the 8 child voxels of the main root voxel
const float verySmall = 0.015625;
endNodeTree.createVoxel(0.0, 0.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 0.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 1.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 0.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 1.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 1.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 1.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 0.0, 1.0, verySmall, 1, 1, 1, true);
// Delete the voxel for the EndNode from the temporary tree, so we can
// import our endNode content into it...
endNodeTree.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE);
VoxelNode* endNode = rootSVO.getVoxelAt(endNodeDetails.x,
endNodeDetails.y,
endNodeDetails.z,
endNodeDetails.s);
rootSVO.copySubTreeIntoNewTree(endNode, &endNodeTree, false);
sprintf(outputFileName, "splitENDNODE%d%s", i, splitSVOFile);
printf("outputFile: %s\n", outputFileName);
endNodeTree.writeToSVOFile(outputFileName);
// Delete the voxel for the EndNode from the root tree...
rootSVO.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE);
// create a small voxel in center of each EndNode, this will is a hack
// to work around a bug in voxel server that will send Voxel not exists
// for regions that don't contain anything even if they're not in the
// jurisdiction of the server
float x = endNodeDetails.x + endNodeDetails.s * 0.5;
float y = endNodeDetails.y + endNodeDetails.s * 0.5;
float z = endNodeDetails.z + endNodeDetails.s * 0.5;
float s = endNodeDetails.s * verySmall;
rootSVO.createVoxel(x, y, z, s, 1, 1, 1, true);
}
sprintf(outputFileName, "splitROOT%s", splitSVOFile);
printf("outputFile: %s\n", outputFileName);
rootSVO.writeToSVOFile(outputFileName);
printf("exiting now\n");
}
class copyAndFillArgs {
public:
VoxelTree* destinationTree;
unsigned long outCount;
unsigned long inCount;
unsigned long originalCount;
};
bool copyAndFillOperation(VoxelNode* node, void* extraData) {
copyAndFillArgs* args = (copyAndFillArgs*)extraData;
char outputMessage[128];
args->inCount++;
int percentDone = (100*args->inCount/args->originalCount);
// For each leaf node...
if (node->isLeaf()) {
// create a copy of the leaf in the copy destination
float x = node->getCorner().x;
float y = node->getCorner().y;
float z = node->getCorner().z;
float s = node->getScale();
unsigned char red = node->getTrueColor()[RED_INDEX];
unsigned char green = node->getTrueColor()[GREEN_INDEX];
unsigned char blue = node->getTrueColor()[BLUE_INDEX];
bool destructive = true;
args->destinationTree->createVoxel(x, y, z, s, red, green, blue, destructive);
args->outCount++;
sprintf(outputMessage,"Completed: %d%% (%lu of %lu) - Creating voxel %lu at [%f,%f,%f,%f]",
percentDone,args->inCount,args->originalCount,args->outCount,x,y,z,s);
printf("%s",outputMessage);
for (int b = 0; b < strlen(outputMessage); b++) {
printf("\b");
}
// and create same sized leafs from this leaf voxel down to zero in the destination tree
for (float yFill = y-s; yFill >= 0.0f; yFill -= s) {
args->destinationTree->createVoxel(x, yFill, z, s, red, green, blue, destructive);
args->outCount++;
sprintf(outputMessage,"Completed: %d%% (%lu of %lu) - Creating fill voxel %lu at [%f,%f,%f,%f]",
percentDone,args->inCount,args->originalCount,args->outCount,x,y,z,s);
printf("%s",outputMessage);
for (int b = 0; b < strlen(outputMessage); b++) {
printf("\b");
}
}
}
return true;
}
void processFillSVOFile(const char* fillSVOFile) {
char outputFileName[512];
printf("fillSVOFile: %s\n", fillSVOFile);
VoxelTree originalSVO(true); // reaveraging
VoxelTree filledSVO(true); // reaveraging
originalSVO.readFromSVOFile(fillSVOFile);
qDebug("Nodes after loading %lu nodes\n", originalSVO.getVoxelCount());
originalSVO.reaverageVoxelColors(originalSVO.rootNode);
qDebug("Original Voxels reAveraged\n");
qDebug("Nodes after reaveraging %lu nodes\n", originalSVO.getVoxelCount());
copyAndFillArgs args;
args.destinationTree = &filledSVO;
args.inCount = 0;
args.outCount = 0;
args.originalCount = originalSVO.getVoxelCount();
printf("Begin processing...\n");
originalSVO.recurseTreeWithOperation(copyAndFillOperation, &args);
printf("DONE processing...\n");
qDebug("Original input nodes used for filling %lu nodes\n", args.originalCount);
qDebug("Input nodes traversed during filling %lu nodes\n", args.inCount);
qDebug("Nodes created during filling %lu nodes\n", args.outCount);
qDebug("Nodes after filling %lu nodes\n", filledSVO.getVoxelCount());
filledSVO.reaverageVoxelColors(filledSVO.rootNode);
qDebug("Nodes after reaveraging %lu nodes\n", filledSVO.getVoxelCount());
sprintf(outputFileName, "filled%s", fillSVOFile);
printf("outputFile: %s\n", outputFileName);
filledSVO.writeToSVOFile(outputFileName);
printf("exiting now\n");
}
int main(int argc, const char * argv[])
{
qInstallMessageHandler(sharedMessageHandler);
@ -68,88 +246,19 @@ int main(int argc, const char * argv[])
const char* splitJurisdictionRoot = getCmdOption(argc, argv, SPLIT_JURISDICTION_ROOT);
const char* splitJurisdictionEndNodes = getCmdOption(argc, argv, SPLIT_JURISDICTION_ENDNODES);
if (splitSVOFile && splitJurisdictionRoot && splitJurisdictionEndNodes) {
char outputFileName[512];
printf("splitSVOFile: %s Jurisdictions Root: %s EndNodes: %s\n",
splitSVOFile, splitJurisdictionRoot, splitJurisdictionEndNodes);
VoxelTree rootSVO;
rootSVO.readFromSVOFile(splitSVOFile);
JurisdictionMap jurisdiction(splitJurisdictionRoot, splitJurisdictionEndNodes);
printf("Jurisdiction Root Octcode: ");
printOctalCode(jurisdiction.getRootOctalCode());
printf("Jurisdiction End Nodes: %d \n", jurisdiction.getEndNodeCount());
for (int i = 0; i < jurisdiction.getEndNodeCount(); i++) {
unsigned char* endNodeCode = jurisdiction.getEndNodeOctalCode(i);
printf("End Node: %d ", i);
printOctalCode(endNodeCode);
// get the endNode details
VoxelPositionSize endNodeDetails;
voxelDetailsForCode(endNodeCode, endNodeDetails);
// Now, create a split SVO for the EndNode.
// copy the EndNode into a temporary tree
VoxelTree endNodeTree;
// create a small voxels at corners of the endNode Tree, this will is a hack
// to work around a bug in voxel server that will send Voxel not exists
// for regions that don't contain anything even if they're not in the
// jurisdiction of the server
// This hack assumes the end nodes for demo dinner since it only guarantees
// nodes in the 8 child voxels of the main root voxel
const float verySmall = 0.015625;
endNodeTree.createVoxel(0.0, 0.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 0.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 1.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 0.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 1.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 1.0, 0.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(0.0, 1.0, 1.0, verySmall, 1, 1, 1, true);
endNodeTree.createVoxel(1.0, 0.0, 1.0, verySmall, 1, 1, 1, true);
// Delete the voxel for the EndNode from the temporary tree, so we can
// import our endNode content into it...
endNodeTree.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE);
VoxelNode* endNode = rootSVO.getVoxelAt(endNodeDetails.x,
endNodeDetails.y,
endNodeDetails.z,
endNodeDetails.s);
rootSVO.copySubTreeIntoNewTree(endNode, &endNodeTree, false);
sprintf(outputFileName, "splitENDNODE%d%s", i, splitSVOFile);
printf("outputFile: %s\n", outputFileName);
endNodeTree.writeToSVOFile(outputFileName);
// Delete the voxel for the EndNode from the root tree...
rootSVO.deleteVoxelCodeFromTree(endNodeCode, COLLAPSE_EMPTY_TREE);
// create a small voxel in center of each EndNode, this will is a hack
// to work around a bug in voxel server that will send Voxel not exists
// for regions that don't contain anything even if they're not in the
// jurisdiction of the server
float x = endNodeDetails.x + endNodeDetails.s * 0.5;
float y = endNodeDetails.y + endNodeDetails.s * 0.5;
float z = endNodeDetails.z + endNodeDetails.s * 0.5;
float s = endNodeDetails.s * verySmall;
rootSVO.createVoxel(x, y, z, s, 1, 1, 1, true);
}
sprintf(outputFileName, "splitROOT%s", splitSVOFile);
printf("outputFile: %s\n", outputFileName);
rootSVO.writeToSVOFile(outputFileName);
printf("exiting now\n");
processSplitSVOFile(splitSVOFile, splitJurisdictionRoot, splitJurisdictionEndNodes);
return 0;
}
// Handles taking an SVO and filling in the empty space below the voxels to make it solid.
const char* FILL_SVO = "--fillSVO";
const char* fillSVOFile = getCmdOption(argc, argv, FILL_SVO);
if (fillSVOFile) {
processFillSVOFile(fillSVOFile);
return 0;
}
const char* DONT_CREATE_FILE = "--dontCreateSceneFile";
bool dontCreateFile = cmdOptionExists(argc, argv, DONT_CREATE_FILE);