mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 09:44:21 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into voxelnode_memory_savings
This commit is contained in:
commit
d566026026
35 changed files with 955 additions and 346 deletions
|
@ -27,6 +27,8 @@ link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR})
|
|||
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(voxel-server-library ${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
include_directories(${ROOT_DIR}/externals/civetweb/include)
|
||||
|
||||
# link the stk library
|
||||
set(STK_ROOT_DIR ${ROOT_DIR}/externals/stk)
|
||||
find_package(STK REQUIRED)
|
||||
|
|
|
@ -161,13 +161,16 @@ void Agent::run() {
|
|||
|
||||
emit willSendAudioDataCallback();
|
||||
|
||||
if (audioMixer) {
|
||||
if (audioMixer && scriptedAudioInjector.hasSamplesToInject()) {
|
||||
int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * INJECT_INTERVAL_USECS) - usecTimestampNow();
|
||||
if (usecToSleep > 0) {
|
||||
usleep(usecToSleep);
|
||||
}
|
||||
|
||||
scriptedAudioInjector.injectAudio(NodeList::getInstance()->getNodeSocket(), audioMixer->getPublicSocket());
|
||||
|
||||
// clear out the audio injector so that it doesn't re-send what we just sent
|
||||
scriptedAudioInjector.clear();
|
||||
}
|
||||
|
||||
// allow the scripter's call back to setup visual data
|
||||
|
|
|
@ -6,7 +6,7 @@ MACRO(SETUP_HIFI_LIBRARY TARGET)
|
|||
set(LIB_SRCS ${LIB_SRCS} ${WRAPPED_SRCS})
|
||||
|
||||
# create a library and set the property so it can be referenced later
|
||||
add_library(${TARGET} ${LIB_SRCS})
|
||||
add_library(${TARGET} ${LIB_SRCS} ${ARGN})
|
||||
|
||||
find_package(Qt5Core REQUIRED)
|
||||
qt5_use_modules(${TARGET} Core)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
MACRO(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
||||
macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
||||
project(${TARGET})
|
||||
|
||||
# grab the implemenation and header files
|
||||
|
@ -12,8 +12,8 @@ MACRO(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
|||
endif()
|
||||
endforeach()
|
||||
|
||||
# add the executable
|
||||
add_executable(${TARGET} ${TARGET_SRCS})
|
||||
# add the executable, include additional optional sources
|
||||
add_executable(${TARGET} ${TARGET_SRCS} ${ARGN})
|
||||
|
||||
IF (${INCLUDE_QT})
|
||||
find_package(Qt5Core REQUIRED)
|
||||
|
@ -21,4 +21,4 @@ MACRO(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
|||
ENDIF()
|
||||
|
||||
target_link_libraries(${TARGET} ${QT_LIBRARIES})
|
||||
ENDMACRO(SETUP_HIFI_PROJECT _target _include_qt)
|
||||
endmacro()
|
|
@ -6,13 +6,13 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
|||
set(TARGET_NAME domain-server)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
# setup a library for civetweb and link it to the domain-server
|
||||
FILE(GLOB CIVETWEB_SRCS external/civetweb/src/*.c)
|
||||
add_library(civetweb ${CIVETWEB_SRCS})
|
||||
include_directories(external/civetweb/include)
|
||||
target_link_libraries(${TARGET_NAME} civetweb)
|
||||
# grab cJSON and civetweb sources to pass as OPTIONAL_SRCS
|
||||
FILE(GLOB OPTIONAL_SRCS ${ROOT_DIR}/externals/civetweb/src/*)
|
||||
|
||||
setup_hifi_project(${TARGET_NAME} TRUE ${OPTIONAL_SRCS})
|
||||
|
||||
include_directories(${ROOT_DIR}/externals/civetweb/include)
|
||||
|
||||
# remove and then copy the files for the webserver
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <QStringList>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -30,12 +33,85 @@ void DomainServer::setDomainServerInstance(DomainServer* domainServer) {
|
|||
int DomainServer::civetwebRequestHandler(struct mg_connection *connection) {
|
||||
const struct mg_request_info* ri = mg_get_request_info(connection);
|
||||
|
||||
const char RESPONSE_200[] = "HTTP/1.0 200 OK\r\n\r\n";
|
||||
|
||||
if (strcmp(ri->uri, "/assignment") == 0 && strcmp(ri->request_method, "POST") == 0) {
|
||||
// return a 200
|
||||
mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n\r\n");
|
||||
mg_printf(connection, "%s", RESPONSE_200);
|
||||
// upload the file
|
||||
mg_upload(connection, "/tmp");
|
||||
|
||||
return 1;
|
||||
} else if (strcmp(ri->uri, "/assignments.json") == 0) {
|
||||
// user is asking for json list of assignments
|
||||
|
||||
// start with a 200 response
|
||||
mg_printf(connection, "%s", RESPONSE_200);
|
||||
|
||||
// setup the JSON
|
||||
QJsonObject assignmentJSON;
|
||||
|
||||
QJsonObject assignedNodesJSON;
|
||||
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
const char ASSIGNMENT_JSON_UUID_KEY[] = "UUID";
|
||||
|
||||
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
|
||||
if (node->getLinkedData()) {
|
||||
// this is a node with assignment
|
||||
QJsonObject assignedNodeJSON;
|
||||
|
||||
// add the assignment UUID
|
||||
QString assignmentUUID = uuidStringWithoutCurlyBraces(((Assignment*) node->getLinkedData())->getUUID());
|
||||
assignedNodeJSON[ASSIGNMENT_JSON_UUID_KEY] = assignmentUUID;
|
||||
|
||||
QJsonObject nodePublicSocketJSON;
|
||||
|
||||
// add the public socket information
|
||||
sockaddr_in* nodePublicSocket = (sockaddr_in*) node->getPublicSocket();
|
||||
nodePublicSocketJSON["ip"] = QString(inet_ntoa(nodePublicSocket->sin_addr));
|
||||
nodePublicSocketJSON["port"] = (int) ntohs(nodePublicSocket->sin_port);
|
||||
|
||||
assignedNodeJSON["public"] = nodePublicSocketJSON;
|
||||
|
||||
// re-format the type name so it matches the target name
|
||||
QString nodeTypeName(node->getTypeName());
|
||||
nodeTypeName = nodeTypeName.toLower();
|
||||
nodeTypeName.replace(' ', '-');
|
||||
|
||||
assignedNodesJSON[nodeTypeName] = assignedNodeJSON;
|
||||
}
|
||||
}
|
||||
|
||||
assignmentJSON["fulfilled"] = assignedNodesJSON;
|
||||
|
||||
QJsonObject queuedAssignmentsJSON;
|
||||
|
||||
// add the queued but unfilled assignments to the json
|
||||
std::deque<Assignment*>::iterator assignment = domainServerInstance->_assignmentQueue.begin();
|
||||
|
||||
while (assignment != domainServerInstance->_assignmentQueue.end()) {
|
||||
QJsonObject queuedAssignmentJSON;
|
||||
|
||||
QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID());
|
||||
queuedAssignmentJSON[ASSIGNMENT_JSON_UUID_KEY] = uuidString;
|
||||
|
||||
// add this queued assignment to the JSON
|
||||
queuedAssignmentsJSON[(*assignment)->getTypeName()] = queuedAssignmentJSON;
|
||||
|
||||
// push forward the iterator to check the next assignment
|
||||
assignment++;
|
||||
}
|
||||
|
||||
assignmentJSON["queued"] = queuedAssignmentsJSON;
|
||||
|
||||
// print out the created JSON
|
||||
QJsonDocument assignmentDocument(assignmentJSON);
|
||||
mg_printf(connection, "%s", assignmentDocument.toJson().constData());
|
||||
|
||||
// we've processed this request
|
||||
return 1;
|
||||
} else {
|
||||
// have mongoose process this request from the document_root
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <Assignment.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include <civetweb.h>
|
||||
#include "civetweb.h"
|
||||
|
||||
const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000;
|
||||
|
||||
|
|
29
interface/resources/shaders/blendface.frag
Normal file
29
interface/resources/shaders/blendface.frag
Normal file
|
@ -0,0 +1,29 @@
|
|||
#version 120
|
||||
|
||||
//
|
||||
// blendface.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/14/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
// the diffuse texture
|
||||
uniform sampler2D texture;
|
||||
|
||||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
void main(void) {
|
||||
// compute the base color based on OpenGL lighting model
|
||||
vec4 normalizedNormal = normalize(normal);
|
||||
vec4 base = gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position));
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal));
|
||||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(texture, gl_TexCoord[0].st) +
|
||||
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
#version 120
|
||||
|
||||
//
|
||||
// eye.vert
|
||||
// blendface.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 9/25/13.
|
||||
// Created by Andrzej Kapolka on 10/14/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
|
@ -16,10 +16,6 @@ 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;
|
||||
|
38
interface/resources/shaders/skin_blendface.vert
Normal file
38
interface/resources/shaders/skin_blendface.vert
Normal file
|
@ -0,0 +1,38 @@
|
|||
#version 120
|
||||
|
||||
//
|
||||
// skin_blendface.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/14/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
const int MAX_CLUSTERS = 32;
|
||||
const int INDICES_PER_VERTEX = 4;
|
||||
|
||||
uniform mat4 clusterMatrices[MAX_CLUSTERS];
|
||||
|
||||
attribute vec4 clusterIndices;
|
||||
attribute vec4 clusterWeights;
|
||||
|
||||
// the interpolated normal
|
||||
varying vec4 normal;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
normal = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[int(clusterIndices[i])];
|
||||
float clusterWeight = clusterWeights[i];
|
||||
position += clusterMatrix * gl_Vertex * clusterWeight;
|
||||
normal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight;
|
||||
}
|
||||
position = gl_ModelViewProjectionMatrix * position;
|
||||
normal = normalize(gl_ModelViewMatrix * normal);
|
||||
|
||||
// pass along the texture coordinate
|
||||
gl_TexCoord[0] = gl_MultiTexCoord0;
|
||||
|
||||
gl_Position = position;
|
||||
}
|
|
@ -2791,9 +2791,11 @@ void Application::displayOverlay() {
|
|||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
_audio.render(_glWidget->width(), _glWidget->height());
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
|
||||
_audioScope.render(45, _glWidget->height() - 200);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
_audio.render(_glWidget->width(), _glWidget->height());
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
|
||||
_audioScope.render(45, _glWidget->height() - 200);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -2833,7 +2835,24 @@ void Application::displayOverlay() {
|
|||
glPointSize(1.0f);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
// Onscreen text about position, servers, etc
|
||||
displayStats();
|
||||
// Bandwidth meter
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) {
|
||||
_bandwidthMeter.render(_glWidget->width(), _glWidget->height());
|
||||
}
|
||||
// Stats at upper right of screen about who domain server is telling us about
|
||||
glPointSize(1.0f);
|
||||
char nodes[100];
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
int totalAvatars = 0, totalServers = 0;
|
||||
|
||||
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
|
||||
node->getType() == NODE_TYPE_AGENT ? totalAvatars++ : totalServers++;
|
||||
}
|
||||
sprintf(nodes, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars);
|
||||
drawtext(_glWidget->width() - 150, 20, 0.10, 0, 1.0, 0, nodes, 1, 0, 0);
|
||||
}
|
||||
|
||||
// testing rendering coverage map
|
||||
|
@ -2845,9 +2864,6 @@ void Application::displayOverlay() {
|
|||
renderCoverageMap();
|
||||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) {
|
||||
_bandwidthMeter.render(_glWidget->width(), _glWidget->height());
|
||||
}
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Log)) {
|
||||
LogDisplay::instance.render(_glWidget->width(), _glWidget->height());
|
||||
|
@ -2867,19 +2883,6 @@ void Application::displayOverlay() {
|
|||
drawtext(_glWidget->width() - 102, _glWidget->height() - 22, 0.30, 0, 1.0, 0, frameTimer, 1, 1, 1);
|
||||
}
|
||||
|
||||
// Stats at upper right of screen about who domain server is telling us about
|
||||
glPointSize(1.0f);
|
||||
char nodes[100];
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
int totalAvatars = 0, totalServers = 0;
|
||||
|
||||
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
|
||||
node->getType() == NODE_TYPE_AGENT ? totalAvatars++ : totalServers++;
|
||||
}
|
||||
|
||||
sprintf(nodes, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars);
|
||||
drawtext(_glWidget->width() - 150, 20, 0.10, 0, 1.0, 0, nodes, 1, 0, 0);
|
||||
|
||||
// render the webcam input frame
|
||||
_webcam.renderPreview(_glWidget->width(), _glWidget->height());
|
||||
|
@ -2941,11 +2944,12 @@ void Application::displayOverlay() {
|
|||
|
||||
void Application::displayStats() {
|
||||
int statsVerticalOffset = 8;
|
||||
|
||||
const int PELS_PER_LINE = 15;
|
||||
char stats[200];
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps ",
|
||||
_fps, _packetsPerSecond, (float)_bytesPerSecond * 8.f / 1000000.f);
|
||||
drawtext(10, statsVerticalOffset + 15, 0.10f, 0, 1.0, 0, stats);
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, stats);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0;
|
||||
|
@ -2975,14 +2979,16 @@ void Application::displayStats() {
|
|||
}
|
||||
|
||||
char pingStats[200];
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
sprintf(pingStats, "Ping audio/avatar/voxel: %d / %d / %d avg %d max ", pingAudio, pingAvatar, pingVoxel, pingVoxelMax);
|
||||
drawtext(10, statsVerticalOffset + 35, 0.10f, 0, 1.0, 0, pingStats);
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, pingStats);
|
||||
}
|
||||
|
||||
char avatarStats[200];
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
glm::vec3 avatarPos = _myAvatar.getPosition();
|
||||
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);
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarStats);
|
||||
|
||||
|
||||
std::stringstream voxelStats;
|
||||
|
@ -2990,7 +2996,8 @@ void Application::displayStats() {
|
|||
voxelStats << "Voxels Rendered: " << _voxels.getVoxelsRendered() / 1000.f << "K " <<
|
||||
"Updated: " << _voxels.getVoxelsUpdated()/1000.f << "K " <<
|
||||
"Max: " << _voxels.getMaxVoxels()/1000.f << "K ";
|
||||
drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats <<
|
||||
|
@ -3006,27 +3013,32 @@ void Application::displayStats() {
|
|||
//voxelStats << "VoxelNode size: " << sizeof(VoxelNode) << " bytes ";
|
||||
//voxelStats << "AABox size: " << sizeof(AABox) << " bytes ";
|
||||
|
||||
drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
voxelStats.str("");
|
||||
char* voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_VOXELS);
|
||||
voxelStats << "Voxels Sent from Server: " << voxelDetails;
|
||||
drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
voxelStats.str("");
|
||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ELAPSED);
|
||||
voxelStats << "Scene Send Time from Server: " << voxelDetails;
|
||||
drawtext(10, statsVerticalOffset + 290, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
voxelStats.str("");
|
||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ENCODE);
|
||||
voxelStats << "Encode Time on Server: " << voxelDetails;
|
||||
drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
voxelStats.str("");
|
||||
voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_MODE);
|
||||
voxelStats << "Sending Mode: " << voxelDetails;
|
||||
drawtext(10, statsVerticalOffset + 330, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str());
|
||||
|
||||
Node *avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
|
||||
char avatarMixerStats[200];
|
||||
|
@ -3038,20 +3050,21 @@ void Application::displayStats() {
|
|||
} else {
|
||||
sprintf(avatarMixerStats, "No Avatar Mixer");
|
||||
}
|
||||
|
||||
drawtext(10, statsVerticalOffset + 350, 0.10f, 0, 1.0, 0, avatarMixerStats);
|
||||
drawtext(10, statsVerticalOffset + 450, 0.10f, 0, 1.0, 0, (char *)LeapManager::statusString().c_str());
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarMixerStats);
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char *)LeapManager::statusString().c_str());
|
||||
|
||||
if (_perfStatsOn) {
|
||||
// Get the PerfStats group details. We need to allocate and array of char* long enough to hold 1+groups
|
||||
char** perfStatLinesArray = new char*[PerfStat::getGroupCount()+1];
|
||||
int lines = PerfStat::DumpStats(perfStatLinesArray);
|
||||
int atZ = 150; // arbitrary place on screen that looks good
|
||||
|
||||
for (int line=0; line < lines; line++) {
|
||||
drawtext(10, statsVerticalOffset + atZ, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]);
|
||||
statsVerticalOffset += PELS_PER_LINE;
|
||||
drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, perfStatLinesArray[line]);
|
||||
delete perfStatLinesArray[line]; // we're responsible for cleanup
|
||||
perfStatLinesArray[line]=NULL;
|
||||
atZ+=20; // height of a line
|
||||
}
|
||||
delete []perfStatLinesArray; // we're responsible for cleanup
|
||||
}
|
||||
|
|
|
@ -460,6 +460,8 @@ void Avatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMovem
|
|||
_skeleton.joint[AVATAR_JOINT_RIGHT_FINGERTIPS].position += transformedHandMovement;
|
||||
}
|
||||
|
||||
enableHandMovement |= updateLeapHandPositions();
|
||||
|
||||
//constrain right arm length and re-adjust elbow position as it bends
|
||||
// NOTE - the following must be called on all avatars - not just _isMine
|
||||
if (enableHandMovement) {
|
||||
|
@ -641,6 +643,51 @@ void Avatar::updateBodyBalls(float deltaTime) {
|
|||
_bodyBall[BODY_BALL_HEAD_TOP].rotation * _skeleton.joint[BODY_BALL_HEAD_TOP].bindPosePosition;
|
||||
}
|
||||
|
||||
// returns true if the Leap controls any of the avatar's hands.
|
||||
bool Avatar::updateLeapHandPositions() {
|
||||
bool returnValue = false;
|
||||
// If there are leap-interaction hands visible, see if we can use them as the endpoints for IK
|
||||
if (getHand().getPalms().size() > 0) {
|
||||
PalmData const* leftLeapHand = NULL;
|
||||
PalmData const* rightLeapHand = NULL;
|
||||
// Look through all of the palms available (there may be more than two), and pick
|
||||
// the leftmost and rightmost. If there's only one, we'll use a heuristic below
|
||||
// to decode whether it's the left or right.
|
||||
for (size_t i = 0; i < getHand().getPalms().size(); ++i) {
|
||||
PalmData& palm = getHand().getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
if (!rightLeapHand || !leftLeapHand) {
|
||||
rightLeapHand = leftLeapHand = &palm;
|
||||
}
|
||||
else if (palm.getRawPosition().x > rightLeapHand->getRawPosition().x) {
|
||||
rightLeapHand = &palm;
|
||||
}
|
||||
else if (palm.getRawPosition().x < leftLeapHand->getRawPosition().x) {
|
||||
leftLeapHand = &palm;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's only one palm visible. Decide if it's the left or right
|
||||
if (leftLeapHand == rightLeapHand && leftLeapHand) {
|
||||
if (leftLeapHand->getRawPosition().x > 0) {
|
||||
leftLeapHand = NULL;
|
||||
}
|
||||
else {
|
||||
rightLeapHand = NULL;
|
||||
}
|
||||
}
|
||||
if (leftLeapHand) {
|
||||
_skeleton.joint[ AVATAR_JOINT_LEFT_FINGERTIPS ].position = leftLeapHand->getPosition();
|
||||
returnValue = true;
|
||||
}
|
||||
if (rightLeapHand) {
|
||||
_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = rightLeapHand->getPosition();
|
||||
returnValue = true;
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void Avatar::updateArmIKAndConstraints(float deltaTime, AvatarJointID fingerTipJointID) {
|
||||
Skeleton::AvatarJoint& fingerJoint = _skeleton.joint[fingerTipJointID];
|
||||
Skeleton::AvatarJoint& wristJoint = _skeleton.joint[fingerJoint.parent];
|
||||
|
|
|
@ -231,6 +231,7 @@ protected:
|
|||
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
void updateBodyBalls(float deltaTime);
|
||||
bool updateLeapHandPositions();
|
||||
void updateArmIKAndConstraints(float deltaTime, AvatarJointID fingerTipJointID);
|
||||
void setScale(const float scale);
|
||||
|
||||
|
|
|
@ -28,18 +28,33 @@ BlendFace::~BlendFace() {
|
|||
deleteGeometry();
|
||||
}
|
||||
|
||||
ProgramObject BlendFace::_eyeProgram;
|
||||
ProgramObject BlendFace::_program;
|
||||
ProgramObject BlendFace::_skinProgram;
|
||||
int BlendFace::_clusterMatricesLocation;
|
||||
int BlendFace::_clusterIndicesLocation;
|
||||
int BlendFace::_clusterWeightsLocation;
|
||||
|
||||
void BlendFace::init() {
|
||||
if (!_eyeProgram.isLinked()) {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
_eyeProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/eye.vert");
|
||||
_eyeProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/iris.frag");
|
||||
_eyeProgram.link();
|
||||
_program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/blendface.vert");
|
||||
_program.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/blendface.frag");
|
||||
_program.link();
|
||||
|
||||
_eyeProgram.bind();
|
||||
_eyeProgram.setUniformValue("texture", 0);
|
||||
_eyeProgram.release();
|
||||
_program.bind();
|
||||
_program.setUniformValue("texture", 0);
|
||||
_program.release();
|
||||
|
||||
_skinProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/skin_blendface.vert");
|
||||
_skinProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/blendface.frag");
|
||||
_skinProgram.link();
|
||||
|
||||
_skinProgram.bind();
|
||||
_clusterMatricesLocation = _skinProgram.uniformLocation("clusterMatrices");
|
||||
_clusterIndicesLocation = _skinProgram.attributeLocation("clusterIndices");
|
||||
_clusterWeightsLocation = _skinProgram.attributeLocation("clusterWeights");
|
||||
_skinProgram.setUniformValue("texture", 0);
|
||||
_skinProgram.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +62,7 @@ void BlendFace::reset() {
|
|||
_resetStates = true;
|
||||
}
|
||||
|
||||
const glm::vec3 MODEL_TRANSLATION(0.0f, -120.0f, 40.0f); // temporary fudge factor
|
||||
const glm::vec3 MODEL_TRANSLATION(0.0f, -60.0f, 40.0f); // temporary fudge factor
|
||||
const float MODEL_SCALE = 0.0006f;
|
||||
|
||||
void BlendFace::simulate(float deltaTime) {
|
||||
|
@ -59,8 +74,14 @@ void BlendFace::simulate(float deltaTime) {
|
|||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (_meshStates.isEmpty()) {
|
||||
QVector<glm::vec3> vertices;
|
||||
foreach (const FBXJoint& joint, geometry.joints) {
|
||||
JointState state;
|
||||
state.rotation = joint.rotation;
|
||||
_jointStates.append(state);
|
||||
}
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
if (mesh.springiness > 0.0f) {
|
||||
state.worldSpaceVertices.resize(mesh.vertices.size());
|
||||
state.vertexVelocities.resize(mesh.vertices.size());
|
||||
|
@ -71,14 +92,52 @@ void BlendFace::simulate(float deltaTime) {
|
|||
_resetStates = true;
|
||||
}
|
||||
|
||||
glm::quat orientation = _owningHead->getOrientation();
|
||||
const Skeleton& skeleton = static_cast<Avatar*>(_owningHead->_owningAvatar)->getSkeleton();
|
||||
glm::quat orientation = skeleton.joint[AVATAR_JOINT_NECK_BASE].absoluteRotation;
|
||||
glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE;
|
||||
glm::vec3 offset = MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot;
|
||||
glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(orientation) *
|
||||
glm::vec3 offset = MODEL_TRANSLATION - geometry.neckPivot;
|
||||
glm::mat4 baseTransform = glm::translate(skeleton.joint[AVATAR_JOINT_NECK_BASE].position) * glm::mat4_cast(orientation) *
|
||||
glm::scale(scale) * glm::translate(offset);
|
||||
|
||||
// update the world space transforms for all joints
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
JointState& state = _jointStates[i];
|
||||
const FBXJoint& joint = geometry.joints.at(i);
|
||||
if (joint.parentIndex == -1) {
|
||||
state.transform = baseTransform * geometry.offset * joint.preRotation *
|
||||
glm::mat4_cast(state.rotation) * joint.postRotation;
|
||||
|
||||
} else {
|
||||
if (i == geometry.neckJointIndex) {
|
||||
// get the rotation axes in joint space and use them to adjust the rotation
|
||||
glm::mat3 axes = glm::mat3_cast(orientation);
|
||||
glm::mat3 inverse = glm::inverse(glm::mat3(_jointStates[joint.parentIndex].transform *
|
||||
joint.preRotation * glm::mat4_cast(joint.rotation)));
|
||||
state.rotation = glm::angleAxis(_owningHead->getRoll(), glm::normalize(inverse * axes[2])) *
|
||||
glm::angleAxis(_owningHead->getYaw(), glm::normalize(inverse * axes[1])) *
|
||||
glm::angleAxis(_owningHead->getPitch(), glm::normalize(inverse * axes[0])) * joint.rotation;
|
||||
|
||||
} else if (i == geometry.leftEyeJointIndex || i == geometry.rightEyeJointIndex) {
|
||||
// likewise with the lookat position
|
||||
glm::mat4 inverse = glm::inverse(_jointStates[joint.parentIndex].transform *
|
||||
joint.preRotation * glm::mat4_cast(joint.rotation));
|
||||
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getOrientation() * IDENTITY_FRONT, 0.0f));
|
||||
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
||||
_owningHead->getSaccade(), 1.0f));
|
||||
state.rotation = rotationBetween(front, lookAt) * joint.rotation;
|
||||
}
|
||||
state.transform = _jointStates[joint.parentIndex].transform * joint.preRotation *
|
||||
glm::mat4_cast(state.rotation) * joint.postRotation;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _meshStates.size(); i++) {
|
||||
MeshState& state = _meshStates[i];
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
|
||||
}
|
||||
int vertexCount = state.worldSpaceVertices.size();
|
||||
if (vertexCount == 0) {
|
||||
continue;
|
||||
|
@ -86,7 +145,7 @@ void BlendFace::simulate(float deltaTime) {
|
|||
glm::vec3* destVertices = state.worldSpaceVertices.data();
|
||||
glm::vec3* destVelocities = state.vertexVelocities.data();
|
||||
glm::vec3* destNormals = state.worldSpaceNormals.data();
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
|
||||
const glm::vec3* sourceVertices = mesh.vertices.constData();
|
||||
if (!mesh.blendshapes.isEmpty()) {
|
||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
||||
|
@ -105,14 +164,30 @@ void BlendFace::simulate(float deltaTime) {
|
|||
_blendedVertices[*index] += *vertex * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
sourceVertices = _blendedVertices.constData();
|
||||
}
|
||||
glm::mat4 transform = baseTransform;
|
||||
if (mesh.isEye) {
|
||||
transform = transform * glm::translate(mesh.pivot) * glm::mat4_cast(glm::inverse(orientation) *
|
||||
_owningHead->getEyeRotation(orientation * ((mesh.pivot + offset) * scale) + _owningHead->getPosition())) *
|
||||
glm::translate(-mesh.pivot);
|
||||
glm::mat4 transform;
|
||||
if (mesh.clusters.size() > 1) {
|
||||
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
|
||||
|
||||
// skin each vertex
|
||||
const glm::vec4* clusterIndices = mesh.clusterIndices.constData();
|
||||
const glm::vec4* clusterWeights = mesh.clusterWeights.constData();
|
||||
for (int j = 0; j < vertexCount; j++) {
|
||||
_blendedVertices[j] =
|
||||
glm::vec3(state.clusterMatrices[clusterIndices[j][0]] *
|
||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] +
|
||||
glm::vec3(state.clusterMatrices[clusterIndices[j][1]] *
|
||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] +
|
||||
glm::vec3(state.clusterMatrices[clusterIndices[j][2]] *
|
||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] +
|
||||
glm::vec3(state.clusterMatrices[clusterIndices[j][3]] *
|
||||
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3];
|
||||
}
|
||||
sourceVertices = _blendedVertices.constData();
|
||||
|
||||
} else {
|
||||
transform = state.clusterMatrices[0];
|
||||
}
|
||||
if (_resetStates) {
|
||||
for (int j = 0; j < vertexCount; j++) {
|
||||
|
@ -161,39 +236,19 @@ bool BlendFace::render(float alpha) {
|
|||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
_blendedVertexBufferIDs.append(id);
|
||||
|
||||
QVector<QSharedPointer<Texture> > dilated;
|
||||
dilated.resize(mesh.parts.size());
|
||||
_dilatedTextures.append(dilated);
|
||||
}
|
||||
|
||||
// make sure we have the right number of dilated texture pointers
|
||||
_dilatedTextures.resize(geometry.meshes.size());
|
||||
}
|
||||
|
||||
glm::mat4 viewMatrix;
|
||||
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&viewMatrix);
|
||||
|
||||
glPushMatrix();
|
||||
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);
|
||||
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
|
||||
-_owningHead->getScale() * MODEL_SCALE);
|
||||
glScalef(scale.x, scale.y, scale.z);
|
||||
|
||||
glm::vec3 offset = MODEL_TRANSLATION - geometry.neckPivot;
|
||||
glTranslatef(offset.x, offset.y, offset.z);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
// enable normalization under the expectation that the GPU can do it faster
|
||||
glEnable(GL_NORMALIZE);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDisable(GL_COLOR_MATERIAL);
|
||||
|
||||
// the eye shader uses the color state even though color material is disabled
|
||||
glColor4f(1.0f, 1.0f, 1.0f, alpha);
|
||||
|
||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
|
@ -201,39 +256,31 @@ bool BlendFace::render(float alpha) {
|
|||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
int vertexCount = mesh.vertices.size();
|
||||
|
||||
glPushMatrix();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
// apply eye rotation if appropriate
|
||||
Texture* texture = networkMesh.diffuseTexture.data();
|
||||
if (mesh.isEye) {
|
||||
glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z);
|
||||
glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation *
|
||||
((mesh.pivot + offset) * scale) + _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);
|
||||
|
||||
_eyeProgram.bind();
|
||||
|
||||
if (texture != NULL) {
|
||||
texture = (_dilatedTextures[i] = static_cast<DilatableNetworkTexture*>(texture)->getDilatedTexture(
|
||||
_owningHead->getPupilDilation())).data();
|
||||
const MeshState& state = _meshStates.at(i);
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
_skinProgram.bind();
|
||||
glUniformMatrix4fvARB(_clusterMatricesLocation, state.clusterMatrices.size(), false,
|
||||
(const float*)state.clusterMatrices.constData());
|
||||
int offset = vertexCount * sizeof(glm::vec2) + (mesh.blendshapes.isEmpty() ?
|
||||
vertexCount * 2 * sizeof(glm::vec3) : 0);
|
||||
_skinProgram.setAttributeBuffer(_clusterIndicesLocation, GL_FLOAT, offset, 4);
|
||||
_skinProgram.setAttributeBuffer(_clusterWeightsLocation, GL_FLOAT,
|
||||
offset + vertexCount * sizeof(glm::vec4), 4);
|
||||
_skinProgram.enableAttributeArray(_clusterIndicesLocation);
|
||||
_skinProgram.enableAttributeArray(_clusterWeightsLocation);
|
||||
|
||||
} else {
|
||||
glPushMatrix();
|
||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||
_program.bind();
|
||||
}
|
||||
} else {
|
||||
_program.bind();
|
||||
}
|
||||
|
||||
// apply material properties
|
||||
glm::vec4 diffuse = glm::vec4(mesh.diffuseColor, alpha);
|
||||
glm::vec4 specular = glm::vec4(mesh.specularColor, alpha);
|
||||
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
||||
glMaterialf(GL_FRONT, GL_SHININESS, mesh.shininess);
|
||||
|
||||
glMultMatrixf((const GLfloat*)&mesh.transform);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID());
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3)));
|
||||
|
||||
|
@ -241,10 +288,7 @@ bool BlendFace::render(float alpha) {
|
|||
glTexCoordPointer(2, GL_FLOAT, 0, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
|
||||
|
||||
const MeshState& state = _meshStates.at(i);
|
||||
if (!state.worldSpaceVertices.isEmpty()) {
|
||||
glLoadMatrixf((const GLfloat*)&viewMatrix);
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
|
||||
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
|
||||
|
@ -281,20 +325,51 @@ bool BlendFace::render(float alpha) {
|
|||
glVertexPointer(3, GL_FLOAT, 0, 0);
|
||||
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * 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)));
|
||||
qint64 offset = 0;
|
||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
|
||||
if (mesh.isEye) {
|
||||
_eyeProgram.release();
|
||||
// apply material properties
|
||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
|
||||
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
|
||||
|
||||
Texture* texture = networkPart.diffuseTexture.data();
|
||||
if (mesh.isEye) {
|
||||
if (texture != NULL) {
|
||||
texture = (_dilatedTextures[i][j] = static_cast<DilatableNetworkTexture*>(texture)->getDilatedTexture(
|
||||
_owningHead->getPupilDilation())).data();
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, texture == NULL ? Application::getInstance()->getTextureCache()->getWhiteTextureID() :
|
||||
texture->getID());
|
||||
|
||||
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
||||
GL_UNSIGNED_INT, (void*)offset);
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
if (state.worldSpaceVertices.isEmpty()) {
|
||||
if (state.clusterMatrices.size() > 1) {
|
||||
_skinProgram.disableAttributeArray(_clusterIndicesLocation);
|
||||
_skinProgram.disableAttributeArray(_clusterWeightsLocation);
|
||||
_skinProgram.release();
|
||||
|
||||
} else {
|
||||
glPopMatrix();
|
||||
_program.release();
|
||||
}
|
||||
} else {
|
||||
_program.release();
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_NORMALIZE);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
// deactivate vertex arrays after drawing
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
@ -305,8 +380,6 @@ bool BlendFace::render(float alpha) {
|
|||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
// restore all the default material settings
|
||||
Application::getInstance()->setupWorldLight(*Application::getInstance()->getCamera());
|
||||
|
||||
|
@ -314,32 +387,19 @@ bool BlendFace::render(float alpha) {
|
|||
}
|
||||
|
||||
bool BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright) const {
|
||||
if (!isActive()) {
|
||||
if (!isActive() || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
glm::vec3 translation = _owningHead->getPosition();
|
||||
glm::quat orientation = _owningHead->getOrientation();
|
||||
if (upright) {
|
||||
translation = static_cast<MyAvatar*>(_owningHead->_owningAvatar)->getUprightHeadPosition();
|
||||
orientation = static_cast<Avatar*>(_owningHead->_owningAvatar)->getWorldAlignedOrientation();
|
||||
}
|
||||
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
|
||||
-_owningHead->getScale() * MODEL_SCALE);
|
||||
bool foundFirst = false;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
if (mesh.isEye) {
|
||||
glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + translation;
|
||||
if (foundFirst) {
|
||||
secondEyePosition = position;
|
||||
return true;
|
||||
}
|
||||
firstEyePosition = position;
|
||||
foundFirst = true;
|
||||
}
|
||||
if (geometry.leftEyeJointIndex != -1) {
|
||||
const glm::mat4& transform = _jointStates[geometry.leftEyeJointIndex].transform;
|
||||
firstEyePosition = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
|
||||
}
|
||||
return false;
|
||||
if (geometry.rightEyeJointIndex != -1) {
|
||||
const glm::mat4& transform = _jointStates[geometry.rightEyeJointIndex].transform;
|
||||
secondEyePosition = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
|
||||
}
|
||||
return geometry.leftEyeJointIndex != -1 && geometry.rightEyeJointIndex != -1;
|
||||
}
|
||||
|
||||
glm::vec4 BlendFace::computeAverageColor() const {
|
||||
|
@ -365,5 +425,6 @@ void BlendFace::deleteGeometry() {
|
|||
glDeleteBuffers(1, &id);
|
||||
}
|
||||
_blendedVertexBufferIDs.clear();
|
||||
_jointStates.clear();
|
||||
_meshStates.clear();
|
||||
}
|
||||
|
|
|
@ -58,8 +58,17 @@ private:
|
|||
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
|
||||
class JointState {
|
||||
public:
|
||||
glm::quat rotation;
|
||||
glm::mat4 transform;
|
||||
};
|
||||
|
||||
QVector<JointState> _jointStates;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
QVector<glm::mat4> clusterMatrices;
|
||||
QVector<glm::vec3> worldSpaceVertices;
|
||||
QVector<glm::vec3> vertexVelocities;
|
||||
QVector<glm::vec3> worldSpaceNormals;
|
||||
|
@ -67,13 +76,17 @@ private:
|
|||
|
||||
QVector<MeshState> _meshStates;
|
||||
QVector<GLuint> _blendedVertexBufferIDs;
|
||||
QVector<QSharedPointer<Texture> > _dilatedTextures;
|
||||
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
||||
bool _resetStates;
|
||||
|
||||
QVector<glm::vec3> _blendedVertices;
|
||||
QVector<glm::vec3> _blendedNormals;
|
||||
|
||||
static ProgramObject _eyeProgram;
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _skinProgram;
|
||||
static int _clusterMatricesLocation;
|
||||
static int _clusterIndicesLocation;
|
||||
static int _clusterWeightsLocation;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__BlendFace__) */
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
glm::vec3 getPosition() const { return _position; }
|
||||
const glm::vec3& getSkinColor() const { return _skinColor; }
|
||||
const glm::vec3& getEyePosition() const { return _eyePosition; }
|
||||
const glm::vec3& getSaccade() const { return _saccade; }
|
||||
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
|
|
|
@ -351,6 +351,8 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
}
|
||||
}
|
||||
|
||||
updateChatCircle(deltaTime);
|
||||
|
||||
_position += _velocity * deltaTime;
|
||||
|
||||
// Zero thrust out now that we've added it to velocity in this frame
|
||||
|
@ -912,43 +914,7 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMov
|
|||
_avatarTouch.setHasInteractingOther(false);
|
||||
}
|
||||
|
||||
// If there are leap-interaction hands visible, see if we can use them as the endpoints for IK
|
||||
if (getHand().getPalms().size() > 0) {
|
||||
PalmData const* leftLeapHand = NULL;
|
||||
PalmData const* rightLeapHand = NULL;
|
||||
// Look through all of the palms available (there may be more than two), and pick
|
||||
// the leftmost and rightmost. If there's only one, we'll use a heuristic below
|
||||
// to decode whether it's the left or right.
|
||||
for (size_t i = 0; i < getHand().getPalms().size(); ++i) {
|
||||
PalmData& palm = getHand().getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
if (!rightLeapHand || !leftLeapHand) {
|
||||
rightLeapHand = leftLeapHand = &palm;
|
||||
}
|
||||
else if (palm.getRawPosition().x > rightLeapHand->getRawPosition().x) {
|
||||
rightLeapHand = &palm;
|
||||
}
|
||||
else if (palm.getRawPosition().x < leftLeapHand->getRawPosition().x) {
|
||||
leftLeapHand = &palm;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there's only one palm visible. Decide if it's the left or right
|
||||
if (leftLeapHand == rightLeapHand && leftLeapHand) {
|
||||
if (leftLeapHand->getRawPosition().x > 0) {
|
||||
leftLeapHand = NULL;
|
||||
}
|
||||
else {
|
||||
rightLeapHand = NULL;
|
||||
}
|
||||
}
|
||||
if (leftLeapHand) {
|
||||
_skeleton.joint[ AVATAR_JOINT_LEFT_FINGERTIPS ].position = leftLeapHand->getPosition();
|
||||
}
|
||||
if (rightLeapHand) {
|
||||
_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = rightLeapHand->getPosition();
|
||||
}
|
||||
}
|
||||
enableHandMovement |= updateLeapHandPositions();
|
||||
|
||||
//constrain right arm length and re-adjust elbow position as it bends
|
||||
// NOTE - the following must be called on all avatars - not just _isMine
|
||||
|
@ -1128,6 +1094,104 @@ void MyAvatar::applyCollisionWithOtherAvatar(Avatar * otherAvatar, float deltaTi
|
|||
_velocity += bodyPushForce;
|
||||
}
|
||||
|
||||
class SortedAvatar {
|
||||
public:
|
||||
Avatar* avatar;
|
||||
float distance;
|
||||
glm::vec3 accumulatedCenter;
|
||||
};
|
||||
|
||||
bool operator<(const SortedAvatar& s1, const SortedAvatar& s2) {
|
||||
return s1.distance < s2.distance;
|
||||
}
|
||||
|
||||
void MyAvatar::updateChatCircle(float deltaTime) {
|
||||
// find all members and sort by distance
|
||||
QVector<SortedAvatar> sortedAvatars;
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
|
||||
if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) {
|
||||
SortedAvatar sortedAvatar;
|
||||
sortedAvatar.avatar = (Avatar*)node->getLinkedData();
|
||||
sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition());
|
||||
sortedAvatars.append(sortedAvatar);
|
||||
}
|
||||
}
|
||||
qSort(sortedAvatars.begin(), sortedAvatars.end());
|
||||
|
||||
// compute the accumulated centers
|
||||
glm::vec3 center = _position;
|
||||
for (int i = 0; i < sortedAvatars.size(); i++) {
|
||||
SortedAvatar& sortedAvatar = sortedAvatars[i];
|
||||
sortedAvatar.accumulatedCenter = (center += sortedAvatar.avatar->getPosition()) / (i + 2.0f);
|
||||
}
|
||||
|
||||
// remove members whose accumulated circles are too far away to influence us
|
||||
const float CIRCUMFERENCE_PER_MEMBER = 0.5f;
|
||||
const float CIRCLE_INFLUENCE_SCALE = 2.0f;
|
||||
for (int i = sortedAvatars.size() - 1; i >= 0; i--) {
|
||||
float radius = (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / PI_TIMES_TWO;
|
||||
if (glm::distance(_position, sortedAvatars[i].accumulatedCenter) > radius * CIRCLE_INFLUENCE_SCALE) {
|
||||
sortedAvatars.remove(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sortedAvatars.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
center = sortedAvatars.last().accumulatedCenter;
|
||||
float radius = (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / PI_TIMES_TWO;
|
||||
|
||||
// compute the average up vector
|
||||
glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP;
|
||||
foreach (const SortedAvatar& sortedAvatar, sortedAvatars) {
|
||||
up += sortedAvatar.avatar->getWorldAlignedOrientation() * IDENTITY_UP;
|
||||
}
|
||||
up = glm::normalize(up);
|
||||
|
||||
// find reasonable corresponding right/front vectors
|
||||
glm::vec3 front = glm::cross(up, IDENTITY_RIGHT);
|
||||
if (glm::length(front) < EPSILON) {
|
||||
front = glm::cross(up, IDENTITY_FRONT);
|
||||
}
|
||||
front = glm::normalize(front);
|
||||
glm::vec3 right = glm::cross(front, up);
|
||||
|
||||
// find our angle and the angular distances to our closest neighbors
|
||||
glm::vec3 delta = _position - center;
|
||||
glm::vec3 projected = glm::vec3(glm::dot(right, delta), glm::dot(front, delta), 0.0f);
|
||||
float myAngle = glm::length(projected) > EPSILON ? atan2f(projected.y, projected.x) : 0.0f;
|
||||
float leftDistance = PI_TIMES_TWO;
|
||||
float rightDistance = PI_TIMES_TWO;
|
||||
foreach (const SortedAvatar& sortedAvatar, sortedAvatars) {
|
||||
delta = sortedAvatar.avatar->getPosition() - center;
|
||||
projected = glm::vec3(glm::dot(right, delta), glm::dot(front, delta), 0.0f);
|
||||
float angle = glm::length(projected) > EPSILON ? atan2f(projected.y, projected.x) : 0.0f;
|
||||
if (angle < myAngle) {
|
||||
leftDistance = min(myAngle - angle, leftDistance);
|
||||
rightDistance = min(PI_TIMES_TWO - (myAngle - angle), rightDistance);
|
||||
|
||||
} else {
|
||||
leftDistance = min(PI_TIMES_TWO - (angle - myAngle), leftDistance);
|
||||
rightDistance = min(angle - myAngle, rightDistance);
|
||||
}
|
||||
}
|
||||
|
||||
// if we're on top of a neighbor, we need to randomize so that they don't both go in the same direction
|
||||
if (rightDistance == 0.0f && randomBoolean()) {
|
||||
swap(leftDistance, rightDistance);
|
||||
}
|
||||
|
||||
// split the difference between our neighbors
|
||||
float targetAngle = myAngle + (rightDistance - leftDistance) / 4.0f;
|
||||
glm::vec3 targetPosition = center + (front * sinf(targetAngle) + right * cosf(targetAngle)) * radius;
|
||||
|
||||
// approach the target position
|
||||
const float APPROACH_RATE = 0.05f;
|
||||
_position = glm::mix(_position, targetPosition, APPROACH_RATE);
|
||||
}
|
||||
|
||||
void MyAvatar::setGravity(glm::vec3 gravity) {
|
||||
_gravity = gravity;
|
||||
_head.setGravity(_gravity);
|
||||
|
|
|
@ -94,6 +94,7 @@ private:
|
|||
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
|
||||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void applyCollisionWithOtherAvatar( Avatar * other, float deltaTime );
|
||||
void updateChatCircle(float deltaTime);
|
||||
void checkForMouseRayTouching();
|
||||
};
|
||||
|
||||
|
|
|
@ -298,28 +298,27 @@ const char* FACESHIFT_BLENDSHAPES[] = {
|
|||
""
|
||||
};
|
||||
|
||||
class Transform {
|
||||
class Model {
|
||||
public:
|
||||
QByteArray name;
|
||||
bool inheritScale;
|
||||
glm::mat4 withScale;
|
||||
glm::mat4 withoutScale;
|
||||
|
||||
glm::mat4 preRotation;
|
||||
glm::quat rotation;
|
||||
glm::mat4 postRotation;
|
||||
|
||||
int parentIndex;
|
||||
};
|
||||
|
||||
glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, Transform>& localTransforms,
|
||||
qint64 nodeID, bool forceScale = true) {
|
||||
|
||||
glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, Model>& models, qint64 nodeID) {
|
||||
glm::mat4 globalTransform;
|
||||
bool useScale = true;
|
||||
while (nodeID != 0) {
|
||||
const Transform& localTransform = localTransforms.value(nodeID);
|
||||
globalTransform = (useScale ? localTransform.withScale : localTransform.withoutScale) * globalTransform;
|
||||
useScale = (useScale && localTransform.inheritScale) || forceScale;
|
||||
const Model& model = models.value(nodeID);
|
||||
globalTransform = model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation * globalTransform;
|
||||
|
||||
QList<qint64> parentIDs = parentMap.values(nodeID);
|
||||
nodeID = 0;
|
||||
foreach (qint64 parentID, parentIDs) {
|
||||
if (localTransforms.contains(parentID)) {
|
||||
if (models.contains(parentID)) {
|
||||
nodeID = parentID;
|
||||
break;
|
||||
}
|
||||
|
@ -354,13 +353,34 @@ public:
|
|||
float shininess;
|
||||
};
|
||||
|
||||
class Cluster {
|
||||
public:
|
||||
QVector<int> indices;
|
||||
QVector<double> weights;
|
||||
glm::mat4 transformLink;
|
||||
};
|
||||
|
||||
void appendModelIDs(qint64 parentID, const QMultiHash<qint64, qint64>& childMap,
|
||||
QHash<qint64, Model>& models, QVector<qint64>& modelIDs) {
|
||||
if (parentID != 0) {
|
||||
modelIDs.append(parentID);
|
||||
}
|
||||
int parentIndex = modelIDs.size() - 1;
|
||||
foreach (qint64 childID, childMap.values(parentID)) {
|
||||
if (models.contains(childID)) {
|
||||
models[childID].parentIndex = parentIndex;
|
||||
appendModelIDs(childID, childMap, models, modelIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
||||
QHash<qint64, FBXMesh> meshes;
|
||||
QVector<ExtractedBlendshape> blendshapes;
|
||||
QMultiHash<qint64, qint64> parentMap;
|
||||
QMultiHash<qint64, qint64> childMap;
|
||||
QHash<qint64, Transform> localTransforms;
|
||||
QHash<qint64, glm::mat4> transformLinkMatrices;
|
||||
QHash<qint64, Model> models;
|
||||
QHash<qint64, Cluster> clusters;
|
||||
QHash<qint64, QByteArray> textureFilenames;
|
||||
QHash<qint64, Material> materials;
|
||||
QHash<qint64, qint64> diffuseTextures;
|
||||
|
@ -406,6 +426,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
QVector<int> normalIndices;
|
||||
QVector<glm::vec2> texCoords;
|
||||
QVector<int> texCoordIndices;
|
||||
QVector<int> materials;
|
||||
foreach (const FBXNode& data, object.children) {
|
||||
if (data.name == "Vertices") {
|
||||
mesh.vertices = createVec3Vector(data.properties.at(0).value<QVector<double> >());
|
||||
|
@ -439,6 +460,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
texCoordIndices = subdata.properties.at(0).value<QVector<int> >();
|
||||
}
|
||||
}
|
||||
} else if (data.name == "LayerElementMaterial") {
|
||||
foreach (const FBXNode& subdata, data.children) {
|
||||
if (subdata.name == "Materials") {
|
||||
materials = subdata.properties.at(0).value<QVector<int> >();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,25 +501,30 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
}
|
||||
|
||||
// convert the polygons to quads and triangles
|
||||
int polygonIndex = 0;
|
||||
for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size();
|
||||
beginIndex != end; ) {
|
||||
beginIndex != end; polygonIndex++) {
|
||||
const int* endIndex = beginIndex;
|
||||
while (*endIndex++ >= 0);
|
||||
|
||||
int materialIndex = (polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0;
|
||||
mesh.parts.resize(max(mesh.parts.size(), materialIndex + 1));
|
||||
FBXMeshPart& part = mesh.parts[materialIndex];
|
||||
|
||||
if (endIndex - beginIndex == 4) {
|
||||
mesh.quadIndices.append(*beginIndex++);
|
||||
mesh.quadIndices.append(*beginIndex++);
|
||||
mesh.quadIndices.append(*beginIndex++);
|
||||
mesh.quadIndices.append(-*beginIndex++ - 1);
|
||||
part.quadIndices.append(*beginIndex++);
|
||||
part.quadIndices.append(*beginIndex++);
|
||||
part.quadIndices.append(*beginIndex++);
|
||||
part.quadIndices.append(-*beginIndex++ - 1);
|
||||
|
||||
} else {
|
||||
for (const int* nextIndex = beginIndex + 1;; ) {
|
||||
mesh.triangleIndices.append(*beginIndex);
|
||||
mesh.triangleIndices.append(*nextIndex++);
|
||||
part.triangleIndices.append(*beginIndex);
|
||||
part.triangleIndices.append(*nextIndex++);
|
||||
if (*nextIndex >= 0) {
|
||||
mesh.triangleIndices.append(*nextIndex);
|
||||
part.triangleIndices.append(*nextIndex);
|
||||
} else {
|
||||
mesh.triangleIndices.append(-*nextIndex - 1);
|
||||
part.triangleIndices.append(-*nextIndex - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -536,7 +568,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
glm::vec3 preRotation, rotation, postRotation;
|
||||
glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||
glm::vec3 scalePivot, rotationPivot;
|
||||
Transform transform = { name, true };
|
||||
Model model = { name };
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "Properties70") {
|
||||
foreach (const FBXNode& property, subobject.children) {
|
||||
|
@ -574,23 +606,19 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
} else if (property.properties.at(0) == "Lcl Scaling") {
|
||||
scale = glm::vec3(property.properties.at(4).value<double>(),
|
||||
property.properties.at(5).value<double>(),
|
||||
property.properties.at(6).value<double>());
|
||||
|
||||
} else if (property.properties.at(0) == "InheritType") {
|
||||
transform.inheritScale = property.properties.at(4) != 2;
|
||||
property.properties.at(6).value<double>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html
|
||||
transform.withoutScale = glm::translate(translation) * glm::translate(rotationPivot) *
|
||||
glm::mat4_cast(glm::quat(glm::radians(preRotation))) *
|
||||
glm::mat4_cast(glm::quat(glm::radians(rotation))) *
|
||||
glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot);
|
||||
transform.withScale = transform.withoutScale * glm::translate(scalePivot) * glm::scale(scale) *
|
||||
glm::translate(-scalePivot);
|
||||
localTransforms.insert(object.properties.at(0).value<qint64>(), transform);
|
||||
model.preRotation = glm::translate(translation) * glm::translate(rotationPivot) *
|
||||
glm::mat4_cast(glm::quat(glm::radians(preRotation)));
|
||||
model.rotation = glm::quat(glm::radians(rotation));
|
||||
model.postRotation = glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot) *
|
||||
glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot);
|
||||
models.insert(object.properties.at(0).value<qint64>(), model);
|
||||
|
||||
} else if (object.name == "Texture") {
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
|
@ -628,12 +656,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
} else if (object.name == "Deformer") {
|
||||
if (object.properties.at(2) == "Cluster") {
|
||||
Cluster cluster;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "TransformLink") {
|
||||
if (subobject.name == "Indexes") {
|
||||
cluster.indices = subobject.properties.at(0).value<QVector<int> >();
|
||||
|
||||
} else if (subobject.name == "Weights") {
|
||||
cluster.weights = subobject.properties.at(0).value<QVector<double> >();
|
||||
|
||||
} else if (subobject.name == "TransformLink") {
|
||||
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
|
||||
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
|
||||
cluster.transformLink = createMat4(values);
|
||||
}
|
||||
}
|
||||
clusters.insert(object.properties.at(0).value<qint64>(), cluster);
|
||||
|
||||
} else if (object.properties.at(2) == "BlendShapeChannel") {
|
||||
QByteArray name = object.properties.at(1).toByteArray();
|
||||
name = name.left(name.indexOf('\0'));
|
||||
|
@ -675,16 +712,58 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
FBXMesh& mesh = meshes[meshID];
|
||||
mesh.blendshapes.resize(max(mesh.blendshapes.size(), index.first + 1));
|
||||
mesh.blendshapes[index.first] = extracted.blendshape;
|
||||
|
||||
// apply scale if non-unity
|
||||
if (index.second != 1.0f) {
|
||||
FBXBlendshape& blendshape = mesh.blendshapes[index.first];
|
||||
for (int i = 0; i < blendshape.vertices.size(); i++) {
|
||||
blendshape.vertices[i] *= index.second;
|
||||
blendshape.normals[i] *= index.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get offset transform from mapping
|
||||
FBXGeometry geometry;
|
||||
float offsetScale = mapping.value("scale", 1.0f).toFloat();
|
||||
glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
||||
geometry.offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
||||
mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
||||
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) *
|
||||
glm::scale(offsetScale, offsetScale, offsetScale);
|
||||
|
||||
FBXGeometry geometry;
|
||||
// get the list of models in depth-first traversal order
|
||||
QVector<qint64> modelIDs;
|
||||
appendModelIDs(0, childMap, models, modelIDs);
|
||||
|
||||
// convert the models to joints
|
||||
foreach (qint64 modelID, modelIDs) {
|
||||
const Model& model = models[modelID];
|
||||
FBXJoint joint;
|
||||
joint.parentIndex = model.parentIndex;
|
||||
joint.preRotation = model.preRotation;
|
||||
joint.rotation = model.rotation;
|
||||
joint.postRotation = model.postRotation;
|
||||
if (joint.parentIndex == -1) {
|
||||
joint.transform = geometry.offset * model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation;
|
||||
|
||||
} else {
|
||||
joint.transform = geometry.joints.at(joint.parentIndex).transform *
|
||||
model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation;
|
||||
}
|
||||
geometry.joints.append(joint);
|
||||
}
|
||||
|
||||
// find our special joints
|
||||
geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID);
|
||||
geometry.rightEyeJointIndex = modelIDs.indexOf(jointEyeRightID);
|
||||
geometry.neckJointIndex = modelIDs.indexOf(jointNeckID);
|
||||
|
||||
// extract the translation component of the neck transform
|
||||
if (geometry.neckJointIndex != -1) {
|
||||
const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform;
|
||||
geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
|
||||
}
|
||||
|
||||
QVariantHash springs = mapping.value("spring").toHash();
|
||||
QVariant defaultSpring = springs.value("default");
|
||||
for (QHash<qint64, FBXMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
|
||||
|
@ -692,48 +771,82 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
// accumulate local transforms
|
||||
qint64 modelID = parentMap.value(it.key());
|
||||
mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat();
|
||||
glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID);
|
||||
mesh.springiness = springs.value(models.value(modelID).name, defaultSpring).toFloat();
|
||||
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
|
||||
|
||||
// look for textures, material properties
|
||||
int partIndex = 0;
|
||||
foreach (qint64 childID, childMap.values(modelID)) {
|
||||
if (!materials.contains(childID)) {
|
||||
if (!materials.contains(childID) || partIndex >= mesh.parts.size()) {
|
||||
continue;
|
||||
}
|
||||
Material material = materials.value(childID);
|
||||
mesh.diffuseColor = material.diffuse;
|
||||
mesh.specularColor = material.specular;
|
||||
mesh.shininess = material.shininess;
|
||||
FBXMeshPart& part = mesh.parts[mesh.parts.size() - ++partIndex];
|
||||
part.diffuseColor = material.diffuse;
|
||||
part.specularColor = material.specular;
|
||||
part.shininess = material.shininess;
|
||||
qint64 diffuseTextureID = diffuseTextures.value(childID);
|
||||
if (diffuseTextureID != 0) {
|
||||
mesh.diffuseFilename = textureFilenames.value(diffuseTextureID);
|
||||
part.diffuseFilename = textureFilenames.value(diffuseTextureID);
|
||||
}
|
||||
qint64 bumpTextureID = bumpTextures.value(childID);
|
||||
if (bumpTextureID != 0) {
|
||||
mesh.normalFilename = textureFilenames.value(bumpTextureID);
|
||||
part.normalFilename = textureFilenames.value(bumpTextureID);
|
||||
}
|
||||
}
|
||||
|
||||
// look for a limb pivot
|
||||
// find the clusters with which the mesh is associated
|
||||
mesh.isEye = false;
|
||||
QVector<qint64> clusterIDs;
|
||||
foreach (qint64 childID, childMap.values(it.key())) {
|
||||
foreach (qint64 clusterID, childMap.values(childID)) {
|
||||
if (!transformLinkMatrices.contains(clusterID)) {
|
||||
if (!clusters.contains(clusterID)) {
|
||||
continue;
|
||||
}
|
||||
FBXCluster fbxCluster;
|
||||
const Cluster& cluster = clusters[clusterID];
|
||||
clusterIDs.append(clusterID);
|
||||
|
||||
qint64 jointID = childMap.value(clusterID);
|
||||
if (jointID == jointEyeLeftID || jointID == jointEyeRightID) {
|
||||
mesh.isEye = true;
|
||||
}
|
||||
|
||||
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
|
||||
// of skinning information in FBX
|
||||
glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID);
|
||||
mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform;
|
||||
fbxCluster.jointIndex = modelIDs.indexOf(jointID);
|
||||
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
|
||||
mesh.clusters.append(fbxCluster);
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't have a skinned joint, parent to the model itself
|
||||
if (mesh.clusters.isEmpty()) {
|
||||
FBXCluster cluster;
|
||||
cluster.jointIndex = modelIDs.indexOf(modelID);
|
||||
mesh.clusters.append(cluster);
|
||||
}
|
||||
|
||||
// whether we're skinned depends on how many clusters are attached
|
||||
if (clusterIDs.size() > 1) {
|
||||
mesh.clusterIndices.resize(mesh.vertices.size());
|
||||
mesh.clusterWeights.resize(mesh.vertices.size());
|
||||
for (int i = 0; i < clusterIDs.size(); i++) {
|
||||
qint64 clusterID = clusterIDs.at(i);
|
||||
const Cluster& cluster = clusters[clusterID];
|
||||
|
||||
// extract translation component for pivot
|
||||
glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true);
|
||||
mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]);
|
||||
for (int j = 0; j < cluster.indices.size(); j++) {
|
||||
int index = cluster.indices.at(j);
|
||||
glm::vec4& weights = mesh.clusterWeights[index];
|
||||
|
||||
// look for an unused slot in the weights vector
|
||||
for (int k = 0; k < 4; k++) {
|
||||
if (weights[k] == 0.0f) {
|
||||
mesh.clusterIndices[index][k] = i;
|
||||
weights[k] = cluster.weights.at(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -742,34 +855,36 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
QSet<QPair<int, int> > edges;
|
||||
|
||||
mesh.vertexConnections.resize(mesh.vertices.size());
|
||||
for (int i = 0; i < mesh.quadIndices.size(); i += 4) {
|
||||
int index0 = mesh.quadIndices.at(i);
|
||||
int index1 = mesh.quadIndices.at(i + 1);
|
||||
int index2 = mesh.quadIndices.at(i + 2);
|
||||
int index3 = mesh.quadIndices.at(i + 3);
|
||||
|
||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
|
||||
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
|
||||
mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
|
||||
}
|
||||
for (int i = 0; i < mesh.triangleIndices.size(); i += 3) {
|
||||
int index0 = mesh.triangleIndices.at(i);
|
||||
int index1 = mesh.triangleIndices.at(i + 1);
|
||||
int index2 = mesh.triangleIndices.at(i + 2);
|
||||
|
||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
for (int i = 0; i < part.quadIndices.size(); i += 4) {
|
||||
int index0 = part.quadIndices.at(i);
|
||||
int index1 = part.quadIndices.at(i + 1);
|
||||
int index2 = part.quadIndices.at(i + 2);
|
||||
int index3 = part.quadIndices.at(i + 3);
|
||||
|
||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
|
||||
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
|
||||
mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
|
||||
}
|
||||
for (int i = 0; i < part.triangleIndices.size(); i += 3) {
|
||||
int index0 = part.triangleIndices.at(i);
|
||||
int index1 = part.triangleIndices.at(i + 1);
|
||||
int index2 = part.triangleIndices.at(i + 2);
|
||||
|
||||
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
|
||||
}
|
||||
}
|
||||
|
||||
for (QSet<QPair<int, int> >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) {
|
||||
|
@ -780,10 +895,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
geometry.meshes.append(mesh);
|
||||
}
|
||||
|
||||
// extract translation component for neck pivot
|
||||
glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
|
||||
geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,20 +37,31 @@ public:
|
|||
QVector<glm::vec3> normals;
|
||||
};
|
||||
|
||||
/// A single mesh (with optional blendshapes) extracted from an FBX document.
|
||||
class FBXMesh {
|
||||
/// A single joint (transformation node) extracted from an FBX document.
|
||||
class FBXJoint {
|
||||
public:
|
||||
|
||||
int parentIndex;
|
||||
glm::mat4 preRotation;
|
||||
glm::quat rotation;
|
||||
glm::mat4 postRotation;
|
||||
glm::mat4 transform;
|
||||
};
|
||||
|
||||
/// A single binding to a joint in an FBX document.
|
||||
class FBXCluster {
|
||||
public:
|
||||
|
||||
int jointIndex;
|
||||
glm::mat4 inverseBindMatrix;
|
||||
};
|
||||
|
||||
/// A single part of a mesh (with the same material).
|
||||
class FBXMeshPart {
|
||||
public:
|
||||
|
||||
QVector<int> quadIndices;
|
||||
QVector<int> triangleIndices;
|
||||
QVector<glm::vec3> vertices;
|
||||
QVector<glm::vec3> normals;
|
||||
QVector<glm::vec2> texCoords;
|
||||
|
||||
glm::vec3 pivot;
|
||||
glm::mat4 transform;
|
||||
|
||||
bool isEye;
|
||||
|
||||
glm::vec3 diffuseColor;
|
||||
glm::vec3 specularColor;
|
||||
|
@ -58,6 +69,23 @@ public:
|
|||
|
||||
QByteArray diffuseFilename;
|
||||
QByteArray normalFilename;
|
||||
};
|
||||
|
||||
/// A single mesh (with optional blendshapes) extracted from an FBX document.
|
||||
class FBXMesh {
|
||||
public:
|
||||
|
||||
QVector<FBXMeshPart> parts;
|
||||
|
||||
QVector<glm::vec3> vertices;
|
||||
QVector<glm::vec3> normals;
|
||||
QVector<glm::vec2> texCoords;
|
||||
QVector<glm::vec4> clusterIndices;
|
||||
QVector<glm::vec4> clusterWeights;
|
||||
|
||||
QVector<FBXCluster> clusters;
|
||||
|
||||
bool isEye;
|
||||
|
||||
QVector<FBXBlendshape> blendshapes;
|
||||
|
||||
|
@ -70,8 +98,16 @@ public:
|
|||
class FBXGeometry {
|
||||
public:
|
||||
|
||||
QVector<FBXJoint> joints;
|
||||
|
||||
QVector<FBXMesh> meshes;
|
||||
|
||||
glm::mat4 offset;
|
||||
|
||||
int leftEyeJointIndex;
|
||||
int rightEyeJointIndex;
|
||||
int neckJointIndex;
|
||||
|
||||
glm::vec3 neckPivot;
|
||||
};
|
||||
|
||||
|
|
|
@ -290,19 +290,26 @@ NetworkGeometry::~NetworkGeometry() {
|
|||
|
||||
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||
glm::vec4 totalColor;
|
||||
int totalVertices = 0;
|
||||
int totalTriangles = 0;
|
||||
for (int i = 0; i < _meshes.size(); i++) {
|
||||
if (_geometry.meshes.at(i).isEye) {
|
||||
const FBXMesh& mesh = _geometry.meshes.at(i);
|
||||
if (mesh.isEye) {
|
||||
continue; // skip eyes
|
||||
}
|
||||
glm::vec4 color = glm::vec4(_geometry.meshes.at(i).diffuseColor, 1.0f);
|
||||
if (_meshes.at(i).diffuseTexture) {
|
||||
color *= _meshes.at(i).diffuseTexture->getAverageColor();
|
||||
const NetworkMesh& networkMesh = _meshes.at(i);
|
||||
for (int j = 0; j < mesh.parts.size(); j++) {
|
||||
const FBXMeshPart& part = mesh.parts.at(j);
|
||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||
glm::vec4 color = glm::vec4(part.diffuseColor, 1.0f);
|
||||
if (networkPart.diffuseTexture) {
|
||||
color *= networkPart.diffuseTexture->getAverageColor();
|
||||
}
|
||||
int triangles = part.quadIndices.size() * 2 + part.triangleIndices.size();
|
||||
totalColor += color * triangles;
|
||||
totalTriangles += triangles;
|
||||
}
|
||||
totalColor += color * _geometry.meshes.at(i).vertices.size();
|
||||
totalVertices += _geometry.meshes.at(i).vertices.size();
|
||||
}
|
||||
return (totalVertices == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalVertices;
|
||||
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
||||
}
|
||||
|
||||
void NetworkGeometry::handleModelReplyError() {
|
||||
|
@ -351,44 +358,76 @@ void NetworkGeometry::maybeReadModelWithMapping() {
|
|||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
||||
NetworkMesh networkMesh;
|
||||
|
||||
int totalIndices = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
NetworkMeshPart networkPart;
|
||||
QString basePath = url.path();
|
||||
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
|
||||
if (!part.diffuseFilename.isEmpty()) {
|
||||
url.setPath(basePath + part.diffuseFilename);
|
||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye);
|
||||
}
|
||||
if (!part.normalFilename.isEmpty()) {
|
||||
url.setPath(basePath + part.normalFilename);
|
||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url);
|
||||
}
|
||||
networkMesh.parts.append(networkPart);
|
||||
|
||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||
}
|
||||
|
||||
glGenBuffers(1, &networkMesh.indexBufferID);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
||||
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());
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
|
||||
int offset = 0;
|
||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
||||
part.quadIndices.constData());
|
||||
offset += part.quadIndices.size() * sizeof(int);
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
||||
part.triangleIndices.constData());
|
||||
offset += part.triangleIndices.size() * sizeof(int);
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
glGenBuffers(1, &networkMesh.vertexBufferID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
||||
|
||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
|
||||
mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
|
||||
mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.clusterIndices.size() +
|
||||
mesh.clusterWeights.size()) * sizeof(glm::vec4), 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());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
|
||||
mesh.texCoords.size() * sizeof(glm::vec2), mesh.clusterIndices.size() * sizeof(glm::vec4),
|
||||
mesh.clusterIndices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
|
||||
mesh.texCoords.size() * sizeof(glm::vec2) + mesh.clusterIndices.size() * sizeof(glm::vec4),
|
||||
mesh.clusterWeights.size() * sizeof(glm::vec4), mesh.clusterWeights.constData());
|
||||
|
||||
// if there's no springiness, then the cluster indices/weights can be static
|
||||
} else if (mesh.springiness == 0.0f) {
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.clusterIndices.size() +
|
||||
mesh.clusterWeights.size()) * sizeof(glm::vec4), NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2),
|
||||
mesh.clusterIndices.size() * sizeof(glm::vec4), mesh.clusterIndices.constData());
|
||||
glBufferSubData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2) +
|
||||
mesh.clusterIndices.size() * sizeof(glm::vec4), mesh.clusterWeights.size() * sizeof(glm::vec4),
|
||||
mesh.clusterWeights.constData());
|
||||
|
||||
} else {
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2),
|
||||
mesh.texCoords.constData(), GL_STATIC_DRAW);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
QString basePath = url.path();
|
||||
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
|
||||
if (!mesh.diffuseFilename.isEmpty()) {
|
||||
url.setPath(basePath + mesh.diffuseFilename);
|
||||
networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye);
|
||||
}
|
||||
if (!mesh.normalFilename.isEmpty()) {
|
||||
url.setPath(basePath + mesh.normalFilename);
|
||||
networkMesh.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url);
|
||||
}
|
||||
_meshes.append(networkMesh);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,14 @@ private:
|
|||
QVector<NetworkMesh> _meshes;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh part.
|
||||
class NetworkMeshPart {
|
||||
public:
|
||||
|
||||
QSharedPointer<NetworkTexture> diffuseTexture;
|
||||
QSharedPointer<NetworkTexture> normalTexture;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh.
|
||||
class NetworkMesh {
|
||||
public:
|
||||
|
@ -87,8 +95,7 @@ public:
|
|||
GLuint indexBufferID;
|
||||
GLuint vertexBufferID;
|
||||
|
||||
QSharedPointer<NetworkTexture> diffuseTexture;
|
||||
QSharedPointer<NetworkTexture> normalTexture;
|
||||
QVector<NetworkMeshPart> parts;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__GeometryCache__) */
|
||||
|
|
|
@ -133,7 +133,7 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
|
|||
|
||||
QOpenGLFramebufferObject* destFBO = toTexture ?
|
||||
Application::getInstance()->getTextureCache()->getSecondaryFramebufferObject() : NULL;
|
||||
if (_isEmpty) {
|
||||
if (_isEmpty && _renderMode != DIFFUSE_ADD_MODE) {
|
||||
// copy the primary to the screen
|
||||
if (QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) {
|
||||
QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO);
|
||||
|
|
|
@ -17,14 +17,22 @@
|
|||
#include "Application.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
TextureCache::TextureCache() : _permutationNormalTextureID(0),
|
||||
_primaryFramebufferObject(NULL), _secondaryFramebufferObject(NULL), _tertiaryFramebufferObject(NULL) {
|
||||
TextureCache::TextureCache() :
|
||||
_permutationNormalTextureID(0),
|
||||
_whiteTextureID(0),
|
||||
_primaryFramebufferObject(NULL),
|
||||
_secondaryFramebufferObject(NULL),
|
||||
_tertiaryFramebufferObject(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
TextureCache::~TextureCache() {
|
||||
if (_permutationNormalTextureID != 0) {
|
||||
glDeleteTextures(1, &_permutationNormalTextureID);
|
||||
}
|
||||
if (_whiteTextureID != 0) {
|
||||
glDeleteTextures(1, &_whiteTextureID);
|
||||
}
|
||||
foreach (GLuint id, _fileTextureIDs) {
|
||||
glDeleteTextures(1, &id);
|
||||
}
|
||||
|
@ -66,6 +74,22 @@ GLuint TextureCache::getPermutationNormalTextureID() {
|
|||
return _permutationNormalTextureID;
|
||||
}
|
||||
|
||||
static void loadWhiteTexture() {
|
||||
const char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, OPAQUE_WHITE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
GLuint TextureCache::getWhiteTextureID() {
|
||||
if (_whiteTextureID == 0) {
|
||||
glGenTextures(1, &_whiteTextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, _whiteTextureID);
|
||||
loadWhiteTexture();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
return _whiteTextureID;
|
||||
}
|
||||
|
||||
GLuint TextureCache::getFileTextureID(const QString& filename) {
|
||||
GLuint id = _fileTextureIDs.value(filename);
|
||||
if (id == 0) {
|
||||
|
@ -85,10 +109,19 @@ GLuint TextureCache::getFileTextureID(const QString& filename) {
|
|||
}
|
||||
|
||||
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool dilatable) {
|
||||
QSharedPointer<NetworkTexture> texture = _networkTextures.value(url);
|
||||
if (texture.isNull()) {
|
||||
texture = QSharedPointer<NetworkTexture>(dilatable ? new DilatableNetworkTexture(url) : new NetworkTexture(url));
|
||||
_networkTextures.insert(url, texture);
|
||||
QSharedPointer<NetworkTexture> texture;
|
||||
if (dilatable) {
|
||||
texture = _dilatableNetworkTextures.value(url);
|
||||
if (texture.isNull()) {
|
||||
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url));
|
||||
_dilatableNetworkTextures.insert(url, texture);
|
||||
}
|
||||
} else {
|
||||
texture = _networkTextures.value(url);
|
||||
if (texture.isNull()) {
|
||||
texture = QSharedPointer<NetworkTexture>(new NetworkTexture(url));
|
||||
_networkTextures.insert(url, texture);
|
||||
}
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
@ -183,6 +216,11 @@ NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL), _averageColor(1.
|
|||
|
||||
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||
|
||||
// default to white
|
||||
glBindTexture(GL_TEXTURE_2D, getID());
|
||||
loadWhiteTexture();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
NetworkTexture::~NetworkTexture() {
|
||||
|
|
|
@ -37,6 +37,9 @@ public:
|
|||
/// the second, a set of random unit vectors to be used as noise gradients.
|
||||
GLuint getPermutationNormalTextureID();
|
||||
|
||||
/// Returns the ID of an opaque white texture (useful for a default).
|
||||
GLuint getWhiteTextureID();
|
||||
|
||||
/// Returns the ID of a texture containing the contents of the specified file, loading it if necessary.
|
||||
GLuint getFileTextureID(const QString& filename);
|
||||
|
||||
|
@ -65,11 +68,13 @@ private:
|
|||
QOpenGLFramebufferObject* createFramebufferObject();
|
||||
|
||||
GLuint _permutationNormalTextureID;
|
||||
|
||||
GLuint _whiteTextureID;
|
||||
|
||||
QHash<QString, GLuint> _fileTextureIDs;
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkTexture> > _networkTextures;
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures;
|
||||
|
||||
GLuint _primaryDepthTextureID;
|
||||
QOpenGLFramebufferObject* _primaryFramebufferObject;
|
||||
QOpenGLFramebufferObject* _secondaryFramebufferObject;
|
||||
|
|
|
@ -64,7 +64,7 @@ AudioInjector::~AudioInjector() {
|
|||
}
|
||||
|
||||
void AudioInjector::injectAudio(UDPSocket* injectorSocket, sockaddr* destinationSocket) {
|
||||
if (_audioSampleArray) {
|
||||
if (_audioSampleArray && _indexOfNextSlot > 0) {
|
||||
_isInjectingAudio = true;
|
||||
|
||||
timeval startTime;
|
||||
|
@ -139,6 +139,11 @@ void AudioInjector::addSamples(int16_t* sampleBuffer, int numSamples) {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioInjector::clear() {
|
||||
_indexOfNextSlot = 0;
|
||||
memset(_audioSampleArray, 0, _numTotalSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
int16_t& AudioInjector::sampleAt(const int index) {
|
||||
assert(index >= 0 && index < _numTotalSamples);
|
||||
|
||||
|
@ -149,4 +154,5 @@ void AudioInjector::insertSample(const int index, int sample) {
|
|||
assert (index >= 0 && index < _numTotalSamples);
|
||||
|
||||
_audioSampleArray[index] = (int16_t) sample;
|
||||
_indexOfNextSlot = index + 1;
|
||||
}
|
||||
|
|
|
@ -51,8 +51,12 @@ public:
|
|||
float getRadius() const { return _radius; }
|
||||
void setRadius(float radius) { _radius = radius; }
|
||||
|
||||
bool hasSamplesToInject() const { return _indexOfNextSlot > 0; }
|
||||
|
||||
void addSample(const int16_t sample);
|
||||
void addSamples(int16_t* sampleBuffer, int numSamples);
|
||||
|
||||
void clear();
|
||||
public slots:
|
||||
int16_t& sampleAt(const int index);
|
||||
void insertSample(const int index, int sample);
|
||||
|
|
|
@ -139,6 +139,21 @@ void Assignment::setPayload(const uchar* payload, int numBytes) {
|
|||
memcpy(_payload, payload, _numPayloadBytes);
|
||||
}
|
||||
|
||||
const char* Assignment::getTypeName() const {
|
||||
switch (_type) {
|
||||
case Assignment::AudioMixerType:
|
||||
return "audio-mixer";
|
||||
case Assignment::AvatarMixerType:
|
||||
return "avatar-mixer";
|
||||
case Assignment::AgentType:
|
||||
return "agent";
|
||||
case Assignment::VoxelServerType:
|
||||
return "voxel-server";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int Assignment::packToBuffer(unsigned char* buffer) {
|
||||
int numPackedBytes = 0;
|
||||
|
||||
|
|
|
@ -73,6 +73,8 @@ public:
|
|||
int getNumberOfInstances() const { return _numberOfInstances; }
|
||||
void setNumberOfInstances(int numberOfInstances) { _numberOfInstances = numberOfInstances; }
|
||||
void decrementNumberOfInstances() { --_numberOfInstances; }
|
||||
|
||||
const char* getTypeName() const;
|
||||
|
||||
/// Packs the assignment to the passed buffer
|
||||
/// \param buffer the buffer in which to pack the assignment
|
||||
|
|
|
@ -11,18 +11,19 @@ set(TARGET_NAME voxel-server-library)
|
|||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||
setup_hifi_library(${TARGET_NAME})
|
||||
|
||||
# grab cJSON and civetweb sources to pass as OPTIONAL_SRCS
|
||||
FILE(GLOB OPTIONAL_SRCS ${ROOT_DIR}/externals/civetweb/src/*)
|
||||
|
||||
setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS})
|
||||
|
||||
include_directories(${ROOT_DIR}/externals/civetweb/include)
|
||||
|
||||
qt5_use_modules(${TARGET_NAME} Widgets)
|
||||
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
# setup a library for civetweb and link it to the voxel-server-library
|
||||
# this assumes that the domain-server cmake has already correctly set up the civetweb library
|
||||
include_directories(../../domain-server/external/civetweb/include)
|
||||
target_link_libraries(${TARGET_NAME} civetweb)
|
||||
|
||||
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
#ifndef __voxel_server__VoxelServer__
|
||||
#define __voxel_server__VoxelServer__
|
||||
|
||||
#include "../../domain-server/external/civetweb/include/civetweb.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <EnvironmentData.h>
|
||||
|
||||
#include "civetweb.h"
|
||||
|
||||
#include "NodeWatcher.h"
|
||||
#include "VoxelPersistThread.h"
|
||||
#include "VoxelSendThread.h"
|
||||
|
|
Loading…
Reference in a new issue