Merge branch 'master' of ssh://github.com/highfidelity/hifi into avatar-interaction

This commit is contained in:
Andrew Meadows 2014-02-17 09:14:06 -08:00
commit 4848aa1d16
52 changed files with 2018 additions and 284 deletions

279
examples/overlaysExample.js Normal file
View file

@ -0,0 +1,279 @@
//
// overlaysExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/14/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates use of the Overlays class
//
//
// The "Swatches" example of this script will create 9 different image overlays, that use the color feature to
// display different colors as color swatches. The overlays can be clicked on, to change the "selectedSwatch" variable
// and update the image used for the overlay so that it appears to have a selected indicator.
// These are our colors...
var swatchColors = new Array();
swatchColors[0] = { red: 255, green: 0, blue: 0};
swatchColors[1] = { red: 0, green: 255, blue: 0};
swatchColors[2] = { red: 0, green: 0, blue: 255};
swatchColors[3] = { red: 255, green: 255, blue: 0};
swatchColors[4] = { red: 255, green: 0, blue: 255};
swatchColors[5] = { red: 0, green: 255, blue: 255};
swatchColors[6] = { red: 128, green: 128, blue: 128};
swatchColors[7] = { red: 128, green: 0, blue: 0};
swatchColors[8] = { red: 0, green: 240, blue: 240};
// The location of the placement of these overlays
var swatchesX = 100;
var swatchesY = 200;
// These will be our "overlay IDs"
var swatches = new Array();
var numberOfSwatches = 9;
var selectedSwatch = 0;
// create the overlays, position them in a row, set their colors, and for the selected one, use a different source image
// location so that it displays the "selected" marker
for (s = 0; s < numberOfSwatches; s++) {
var imageFromX = 12 + (s * 27);
var imageFromY = 0;
if (s == selectedSwatch) {
imageFromY = 55;
}
swatches[s] = Overlays.addOverlay("image", {
x: 100 + (30 * s),
y: 200,
width: 31,
height: 54,
subImage: { x: imageFromX, y: imageFromY, width: 30, height: 54 },
imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg",
color: swatchColors[s],
alpha: 1
});
}
// This will create a text overlay that when you click on it, the text will change
var text = Overlays.addOverlay("text", {
x: 200,
y: 100,
width: 150,
height: 50,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: "Here is some text.\nAnd a second line."
});
// This will create an image overlay, which starts out as invisible
var toolA = Overlays.addOverlay("image", {
x: 100,
y: 100,
width: 62,
height: 40,
subImage: { x: 0, y: 0, width: 62, height: 40 },
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg",
color: { red: 255, green: 255, blue: 255},
visible: false
});
// This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to
// move the slider
var slider = Overlays.addOverlay("image", {
// alternate form of expressing bounds
bounds: { x: 100, y: 300, width: 158, height: 35},
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
// This is the thumb of our slider
var minThumbX = 130;
var maxThumbX = minThumbX + 65;
var thumbX = (minThumbX + maxThumbX) / 2;
var thumb = Overlays.addOverlay("image", {
x: thumbX,
y: 309,
width: 18,
height: 17,
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
color: { red: 255, green: 255, blue: 255},
alpha: 1
});
// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines
// our 3D cube that moves around...
var cubePosition = { x: 2, y: 0, z: 2 };
var cubeSize = 5;
var cubeMove = 0.1;
var minCubeX = 1;
var maxCubeX = 20;
var cube = Overlays.addOverlay("cube", {
position: cubePosition,
size: cubeSize,
color: { red: 255, green: 0, blue: 0},
alpha: 1,
solid: false
});
var solidCubePosition = { x: 0, y: 5, z: 0 };
var solidCubeSize = 2;
var minSolidCubeX = 0;
var maxSolidCubeX = 10;
var solidCubeMove = 0.05;
var solidCube = Overlays.addOverlay("cube", {
position: solidCubePosition,
size: solidCubeSize,
color: { red: 0, green: 255, blue: 0},
alpha: 1,
solid: true
});
var spherePosition = { x: 5, y: 5, z: 5 };
var sphereSize = 1;
var minSphereSize = 0.5;
var maxSphereSize = 10;
var sphereSizeChange = 0.05;
var sphere = Overlays.addOverlay("sphere", {
position: spherePosition,
size: sphereSize,
color: { red: 0, green: 0, blue: 255},
alpha: 1,
solid: false
});
var line3d = Overlays.addOverlay("line3d", {
position: { x: 0, y: 0, z:0 },
end: { x: 10, y: 10, z:10 },
color: { red: 0, green: 255, blue: 255},
alpha: 1,
lineWidth: 5
});
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
Overlays.deleteOverlay(toolA);
for (s = 0; s < numberOfSwatches; s++) {
Overlays.deleteOverlay(swatches[s]);
}
Overlays.deleteOverlay(thumb);
Overlays.deleteOverlay(slider);
Overlays.deleteOverlay(text);
Overlays.deleteOverlay(cube);
Overlays.deleteOverlay(solidCube);
Overlays.deleteOverlay(sphere);
Overlays.deleteOverlay(line3d);
}
Script.scriptEnding.connect(scriptEnding);
var toolAVisible = false;
var count = 0;
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
function update() {
count++;
// every second or so, toggle the visibility our our blinking tool
if (count % 60 == 0) {
if (toolAVisible) {
toolAVisible = false;
} else {
toolAVisible = true;
}
Overlays.editOverlay(toolA, { visible: toolAVisible } );
}
// move our 3D cube
cubePosition.x += cubeMove;
cubePosition.z += cubeMove;
if (cubePosition.x > maxCubeX || cubePosition.x < minCubeX) {
cubeMove = cubeMove * -1;
}
Overlays.editOverlay(cube, { position: cubePosition } );
// move our solid 3D cube
solidCubePosition.x += solidCubeMove;
solidCubePosition.z += solidCubeMove;
if (solidCubePosition.x > maxSolidCubeX || solidCubePosition.x < minSolidCubeX) {
solidCubeMove = solidCubeMove * -1;
}
Overlays.editOverlay(solidCube, { position: solidCubePosition } );
// adjust our 3D sphere
sphereSize += sphereSizeChange;
if (sphereSize > maxSphereSize || sphereSize < minSphereSize) {
sphereSizeChange = sphereSizeChange * -1;
}
Overlays.editOverlay(sphere, { size: sphereSize, solid: (sphereSizeChange < 0) } );
// update our 3D line to go from origin to our avatar's position
Overlays.editOverlay(line3d, { end: MyAvatar.position } );
}
Script.willSendVisualDataCallback.connect(update);
// The slider is handled in the mouse event callbacks.
var movingSlider = false;
var thumbClickOffsetX = 0;
function mouseMoveEvent(event) {
if (movingSlider) {
newThumbX = event.x - thumbClickOffsetX;
if (newThumbX < minThumbX) {
newThumbX = minThumbX;
}
if (newThumbX > maxThumbX) {
newThumbX = maxThumbX;
}
Overlays.editOverlay(thumb, { x: newThumbX } );
}
}
// we also handle click detection in our mousePressEvent()
function mousePressEvent(event) {
var clickedText = false;
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
// If the user clicked on the thumb, handle the slider logic
if (clickedOverlay == thumb) {
movingSlider = true;
thumbClickOffsetX = event.x - thumbX;
} else if (clickedOverlay == text) { // if the user clicked on the text, update text with where you clicked
Overlays.editOverlay(text, { text: "you clicked here:\n " + event.x + "," + event.y } );
clickedText = true;
} else { // if the user clicked on one of the color swatches, update the selectedSwatch
for (s = 0; s < numberOfSwatches; s++) {
if (clickedOverlay == swatches[s]) {
Overlays.editOverlay(swatches[selectedSwatch], { subImage: { y: 0 } } );
Overlays.editOverlay(swatches[s], { subImage: { y: 55 } } );
selectedSwatch = s;
}
}
}
if (!clickedText) { // if you didn't click on the text, then update the text accordningly
Overlays.editOverlay(text, { text: "you didn't click here" } );
}
}
function mouseReleaseEvent(event) {
if (movingSlider) {
movingSlider = false;
}
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);

View file

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 330 KiB

View file

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View file

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,3 +1,5 @@
filename=defaultAvatar/body.fbx
texdir=defaultAvatar
scale=130
joint = jointRoot = jointRoot
joint = jointLean = jointSpine

View file

@ -1,7 +1,7 @@
# faceshift target mapping file
name= defaultAvatar_head
filename=../../../Avatars/Jelly/jellyrob_blue.fbx
texdir=../../../Avatars/Jelly
filename=defaultAvatar/head.fbx
texdir=defaultAvatar
scale=80
rx=0
ry=0

View file

@ -12,7 +12,7 @@
uniform sampler2D permutationNormalTexture;
// the noise frequency
const float frequency = 1024.0;
const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
// the noise amplitude
const float amplitude = 0.1;

View file

@ -291,6 +291,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
checkVersion();
_overlays.init(_glWidget); // do this before scripts load
// do this as late as possible so that all required subsystems are inialized
loadScripts();
@ -1909,6 +1912,8 @@ void Application::init() {
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
}
void Application::closeMirrorView() {
@ -2740,7 +2745,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels...");
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
_voxels.render();
// double check that our LOD doesn't need to be auto-adjusted
// only adjust if our option is set
if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
Menu::getInstance()->autoAdjustLOD(_fps);
}
}
}
@ -2831,7 +2842,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_mouseVoxel.s,
_mouseVoxel.s);
_sharedVoxelSystem.render(true);
_sharedVoxelSystem.render();
glPopMatrix();
}
}
@ -2871,6 +2882,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// give external parties a change to hook in
emit renderingInWorldInterface();
// render JS/scriptable overlays
_overlays.render3D();
}
}
@ -3017,6 +3031,8 @@ void Application::displayOverlay() {
_pieMenu.render();
}
_overlays.render2D();
glPopMatrix();
}
@ -4094,6 +4110,8 @@ void Application::loadScript(const QString& fileNameString) {
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
scriptEngine->registerGlobalObject("Overlays", &_overlays);
QThread* workerThread = new QThread(this);
// when the worker thread is started, call our engine's run..

View file

@ -70,6 +70,7 @@
#include "FileLogger.h"
#include "ParticleTreeRenderer.h"
#include "ControllerScriptingInterface.h"
#include "ui/Overlays.h"
class QAction;
@ -490,6 +491,8 @@ private:
void takeSnapshot();
TouchEvent _lastTouchEvent;
Overlays _overlays;
};
#endif /* defined(__interface__Application__) */

View file

@ -70,7 +70,8 @@ Menu::Menu() :
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_boundaryLevelAdjust(0),
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
_lastAdjust(usecTimestampNow())
{
Application *appInstance = Application::getInstance();
@ -311,6 +312,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD);
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
@ -1117,14 +1119,38 @@ void Menu::voxelStatsDetailsClosed() {
}
}
void Menu::autoAdjustLOD(float currentFPS) {
bool changed = false;
quint64 now = usecTimestampNow();
quint64 elapsed = now - _lastAdjust;
if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
}
if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_UP_BY;
changed = true;
_lastAdjust = now;
qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
}
if (changed) {
if (_lodToolsDialog) {
_lodToolsDialog->reloadSliders();
}
}
}
void Menu::setVoxelSizeScale(float sizeScale) {
_voxelSizeScale = sizeScale;
Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust;
Application::getInstance()->getVoxels()->redrawInViewVoxels();
}
void Menu::lodTools() {

View file

@ -16,6 +16,18 @@
#include <AbstractMenuInterface.h>
const float ADJUST_LOD_DOWN_FPS = 40.0;
const float ADJUST_LOD_UP_FPS = 55.0;
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
const float ADJUST_LOD_DOWN_BY = 0.9f;
const float ADJUST_LOD_UP_BY = 1.1f;
const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f;
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
enum FrustumDrawMode {
FRUSTUM_DRAW_MODE_ALL,
FRUSTUM_DRAW_MODE_VECTORS,
@ -68,6 +80,7 @@ public:
void handleViewFrustumOffsetKeyModifier(int key);
// User Tweakable LOD Items
void autoAdjustLOD(float currentFPS);
void setVoxelSizeScale(float sizeScale);
float getVoxelSizeScale() const { return _voxelSizeScale; }
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
@ -159,6 +172,7 @@ private:
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
quint64 _lastAdjust;
};
namespace MenuOption {
@ -166,6 +180,7 @@ namespace MenuOption {
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Avatars = "Avatars";
const QString Atmosphere = "Atmosphere";
const QString AutoAdjustLOD = "Automatically Adjust LOD";
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";

View file

@ -19,15 +19,6 @@
#include <glm/gtc/quaternion.hpp>
#include <QSettings>
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);

View file

@ -57,9 +57,12 @@ GLubyte identityIndicesBack[] = { 4, 5, 6, 4, 6, 7 };
VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
: NodeData(),
_treeScale(treeScale),
_maxVoxels(maxVoxels),
_initialized(false) {
_treeScale(treeScale),
_maxVoxels(maxVoxels),
_initialized(false),
_writeArraysLock(QReadWriteLock::Recursive),
_readArraysLock(QReadWriteLock::Recursive)
{
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
_writeRenderFullVBO = true;
@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_culledOnce = false;
_inhideOutOfView = false;
_lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
_lastKnownBoundaryLevelAdjust = 0;
}
void VoxelSystem::elementDeleted(OctreeElement* element) {
@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
void VoxelSystem::elementUpdated(OctreeElement* element) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
return;
@ -249,6 +256,9 @@ VoxelSystem::~VoxelSystem() {
delete _tree;
}
// This is called by the main application thread on both the initialization of the application and when
// the preferences dialog box is called/saved
void VoxelSystem::setMaxVoxels(int maxVoxels) {
if (maxVoxels == _maxVoxels) {
return;
@ -267,6 +277,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) {
}
}
// This is called by the main application thread on both the initialization of the application and when
// the use voxel shader menu item is chosen
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
if (_useVoxelShader == useVoxelShader) {
return;
@ -330,7 +342,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) {
void VoxelSystem::cleanupVoxelMemory() {
if (_initialized) {
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
_initialized = false; // no longer initialized
if (_useVoxelShader) {
// these are used when in VoxelShader mode.
@ -368,7 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
delete[] _writeVoxelDirtyArray;
delete[] _readVoxelDirtyArray;
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
_bufferWriteLock.unlock();
_readArraysLock.unlock();
}
}
@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
}
void VoxelSystem::initVoxelMemory() {
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
_writeArraysLock.lockForWrite();
_memoryUsageRAM = 0;
_memoryUsageVBO = 0; // our VBO allocations as we know them
@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
_initialized = true;
_bufferWriteLock.unlock();
_writeArraysLock.unlock();
_readArraysLock.unlock();
}
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
@ -646,7 +660,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
_inSetupNewVoxelsForDrawing = true;
bool didWriteFullVBO = _writeRenderFullVBO;
if (_tree->isDirty()) {
static char buffer[64] = { 0 };
@ -673,7 +687,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
}
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
_bufferWriteLock.lock();
_readArraysLock.lockForWrite();
if (_voxelsUpdated) {
_voxelsDirty=true;
@ -682,7 +696,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(didWriteFullVBO);
_bufferWriteLock.unlock();
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@ -713,8 +727,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
_bufferWriteLock.lock();
"setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
_readArraysLock.lockForWrite();
}
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
@ -725,7 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
// after...
_voxelsUpdated = 0;
_bufferWriteLock.unlock();
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
}
void VoxelSystem::checkForCulling() {
class recreateVoxelGeometryInViewArgs {
public:
VoxelSystem* thisVoxelSystem;
ViewFrustum thisViewFrustum;
unsigned long nodesScanned;
float voxelSizeScale;
int boundaryLevelAdjust;
recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) :
thisVoxelSystem(voxelSystem),
thisViewFrustum(*voxelSystem->getViewFrustum()),
nodesScanned(0),
voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()),
boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust())
{
}
};
// The goal of this operation is to remove any old references to old geometry, and if the voxel
// should be visible, create new geometry for it.
bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData;
args->nodesScanned++;
// reset the old geometry...
// note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the
// old geometry
voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust);
bool inView = voxel->isInView(args->thisViewFrustum);
voxel->setShouldRender(inView && shouldRender);
if (shouldRender && inView) {
// recreate the geometry
args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
}
return true; // keep recursing!
}
// TODO: does cleanupRemovedVoxels() ever get called?
// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
void VoxelSystem::recreateVoxelGeometryInView() {
qDebug() << "recreateVoxelGeometryInView()...";
recreateVoxelGeometryInViewArgs args(this);
_writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done
_tree->lockForRead(); // don't let anyone change our tree structure until we're run
// reset our write arrays bookkeeping to think we've got no voxels in it
clearFreeBufferIndexes();
// do we need to reset out _writeVoxelDirtyArray arrays??
memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
_tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args);
_tree->unlock();
_writeArraysLock.unlock();
}
void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
quint64 start = usecTimestampNow();
@ -762,7 +841,20 @@ void VoxelSystem::checkForCulling() {
_hasRecentlyChanged = false;
}
hideOutOfView(forceFullFrustum);
// This would be a good place to do a special processing pass, for example, switching the LOD of the scene
bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() ||
_lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust());
// track that these values
_lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
_lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
if (fullRedraw) {
// this will remove all old geometry and recreate the correct geometry for all in view voxels
recreateVoxelGeometryInView();
} else {
hideOutOfView(forceFullFrustum);
}
if (forceFullFrustum) {
quint64 endViewCulling = usecTimestampNow();
@ -880,12 +972,26 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"copyWrittenDataToReadArrays()");
if (_voxelsDirty && _voxelsUpdated) {
if (fullVBOs) {
copyWrittenDataToReadArraysFullVBOs();
// attempt to get the writeArraysLock for reading and the readArraysLock for writing
// so we can copy from the write to the read... if we fail, that's ok, we'll get it the next
// time around, the only side effect is the VBOs won't be updated this frame
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) {
if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_voxelsDirty && _voxelsUpdated) {
if (fullVBOs) {
copyWrittenDataToReadArraysFullVBOs();
} else {
copyWrittenDataToReadArraysPartialVBOs();
}
}
_writeArraysLock.unlock();
} else {
copyWrittenDataToReadArraysPartialVBOs();
qDebug() << "couldn't get _writeArraysLock.LockForRead()...";
}
_readArraysLock.unlock();
} else {
qDebug() << "couldn't get _readArraysLock.LockForWrite()...";
}
}
@ -1141,17 +1247,27 @@ void VoxelSystem::updateVBOs() {
// would like to include _callsToTreesToArrays
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
if (_voxelsDirty) {
if (_readRenderFullVBO) {
updateFullVBOs();
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_readRenderFullVBO) {
updateFullVBOs();
} else {
updatePartialVBOs();
}
_voxelsDirty = false;
_readRenderFullVBO = false;
_readArraysLock.unlock();
} else {
updatePartialVBOs();
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
}
_voxelsDirty = false;
_readRenderFullVBO = false;
}
_callsToTreesToArrays = 0; // clear it
}
// this should only be called on the main application thread during render
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarning, "updateVBOSegment()");
@ -1197,7 +1313,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg
}
}
void VoxelSystem::render(bool texture) {
void VoxelSystem::render() {
bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures);
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "render()");
@ -1404,11 +1521,7 @@ void VoxelSystem::killLocalVoxels() {
setupNewVoxelsForDrawing();
}
void VoxelSystem::redrawInViewVoxels() {
hideOutOfView(true);
}
// only called on main thread
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
@ -1416,12 +1529,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
return true;
}
// only called on main thread, and also always followed by a call to cleanupVoxelMemory()
// you shouldn't be calling this on any other thread or without also cleaning up voxel memory
void VoxelSystem::clearAllNodesBufferIndex() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::clearAllNodesBufferIndex()");
_nodeCount = 0;
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
clearFreeBufferIndexes(); // this should be called too
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug("clearing buffer index of %d nodes", _nodeCount);

View file

@ -53,7 +53,7 @@ public:
virtual void init();
void simulate(float deltaTime) { }
void render(bool texture);
void render();
void changeTree(VoxelTree* newTree);
VoxelTree* getTree() const { return _tree; }
@ -79,7 +79,6 @@ public:
unsigned long getVoxelMemoryUsageGPU();
void killLocalVoxels();
void redrawInViewVoxels();
virtual void removeOutOfView();
virtual void hideOutOfView(bool forceFullFrustum = false);
@ -151,6 +150,7 @@ protected:
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
void checkForCulling();
void recreateVoxelGeometryInView();
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
@ -194,6 +194,7 @@ private:
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData);
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
@ -211,6 +212,11 @@ private:
GLfloat* _readVerticesArray;
GLubyte* _readColorsArray;
QReadWriteLock _writeArraysLock;
QReadWriteLock _readArraysLock;
GLfloat* _writeVerticesArray;
GLubyte* _writeColorsArray;
bool* _writeVoxelDirtyArray;
@ -253,9 +259,6 @@ private:
GLuint _vboIndicesFront;
GLuint _vboIndicesBack;
QMutex _bufferWriteLock;
QMutex _treeLock;
ViewFrustum _lastKnownViewFrustum;
ViewFrustum _lastStableViewFrustum;
ViewFrustum* _viewFrustum;
@ -299,6 +302,9 @@ private:
bool _useFastVoxelPipeline;
bool _inhideOutOfView;
float _lastKnownVoxelSizeScale;
int _lastKnownBoundaryLevelAdjust;
};
#endif

View file

@ -355,13 +355,13 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
}

View file

@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
void FaceModel::simulate(float deltaTime) {
if (!isActive()) {
Model::simulate(deltaTime);
return;
}
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);

View file

@ -21,9 +21,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
void SkeletonModel::simulate(float deltaTime) {
if (!isActive()) {
Model::simulate(deltaTime);
return;
}
setTranslation(_owningAvatar->getPosition());
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
const float MODEL_SCALE = 0.0006f;

View file

@ -1577,14 +1577,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
return geometry;
}
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
modelBuffer.open(QIODevice::ReadOnly);
QVariantHash readMapping(const QByteArray& data) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);
return parseMapping(&buffer);
}
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
mappingBuffer.open(QIODevice::ReadOnly);
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return extractFBXGeometry(parseFBX(&buffer), mapping);
}
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {

View file

@ -164,9 +164,12 @@ public:
QVector<FBXAttachment> attachments;
};
/// Reads an FST mapping from the supplied data.
QVariantHash readMapping(const QByteArray& data);
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
/// Reads SVO geometry from the supplied model data.
FBXGeometry readSVO(const QByteArray& model);

View file

@ -7,11 +7,7 @@
#include <cmath>
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QNetworkReply>
#include <QOpenGLBuffer>
#include <QTimer>
#include "Application.h"
@ -298,46 +294,86 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
if (geometry.isNull()) {
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
geometry->setLODParent(geometry);
_networkGeometry.insert(url, geometry);
}
return geometry;
}
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
_modelRequest(url),
_modelReply(NULL),
_mappingReply(NULL),
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
const QVariantHash& mapping, const QUrl& textureBase) :
_request(url),
_reply(NULL),
_mapping(mapping),
_textureBase(textureBase.isValid() ? textureBase : url),
_fallback(fallback),
_attempts(0)
{
_startedLoading(false),
_failedToLoad(false),
_attempts(0) {
if (!url.isValid()) {
return;
}
_modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
makeModelRequest();
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
QUrl mappingURL = url;
QString path = url.path();
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
QNetworkRequest mappingRequest(mappingURL);
mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
_mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
// if we already have a mapping (because we're an LOD), hold off on loading until we're requested
if (mapping.isEmpty()) {
makeRequest();
}
}
NetworkGeometry::~NetworkGeometry() {
if (_modelReply != NULL) {
delete _modelReply;
if (_reply != NULL) {
delete _reply;
}
if (_mappingReply != NULL) {
delete _mappingReply;
}
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
if (_lodParent.data() != this) {
return _lodParent.data()->getLODOrFallback(distance, hysteresis);
}
foreach (const NetworkMesh& mesh, _meshes) {
glDeleteBuffers(1, &mesh.indexBufferID);
glDeleteBuffers(1, &mesh.vertexBufferID);
}
if (_failedToLoad && _fallback) {
return _fallback;
}
QSharedPointer<NetworkGeometry> lod = _lodParent;
float lodDistance = 0.0f;
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
if (it != _lods.constBegin()) {
it = it - 1;
lod = it.value();
lodDistance = it.key();
}
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
// if we previously selected a different distance, make sure we've moved far enough to justify switching
const float HYSTERESIS_PROPORTION = 0.1f;
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
return getLODOrFallback(hysteresis, hysteresis);
}
}
if (lod->isLoaded()) {
hysteresis = lodDistance;
return lod;
}
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
if (!lod->_startedLoading) {
lod->makeRequest();
}
float closestDistance = FLT_MAX;
if (isLoaded()) {
lod = _lodParent;
closestDistance = distance;
}
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
float distanceToLOD = glm::abs(distance - it.key());
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
lod = it.value();
closestDistance = distanceToLOD;
}
}
hysteresis = NO_HYSTERESIS;
return lod;
}
glm::vec4 NetworkGeometry::computeAverageColor() const {
@ -364,20 +400,167 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
}
void NetworkGeometry::makeModelRequest() {
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
void NetworkGeometry::makeRequest() {
_startedLoading = true;
_reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
}
void NetworkGeometry::handleModelReplyError() {
QDebug debug = qDebug() << _modelReply->errorString();
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (!_reply->isFinished()) {
return;
}
QNetworkReply::NetworkError error = _modelReply->error();
_modelReply->disconnect(this);
_modelReply->deleteLater();
_modelReply = NULL;
QUrl url = _reply->url();
QByteArray data = _reply->readAll();
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
if (url.path().toLower().endsWith(".fst")) {
// it's a mapping file; parse it and get the mesh filename
_mapping = readMapping(data);
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {
qDebug() << "Mapping file " << url << " has no filename.";
_failedToLoad = true;
} else {
QString texdir = _mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
_textureBase = url.resolved(texdir);
}
QVariantHash lods = _mapping.value("lod").toHash();
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase));
geometry->setLODParent(_lodParent);
_lods.insert(it.value().toFloat(), geometry);
}
_request.setUrl(url.resolved(filename));
// make the request immediately only if we have no LODs to switch between
_startedLoading = false;
if (_lods.isEmpty()) {
makeRequest();
}
}
return;
}
try {
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
} catch (const QString& error) {
qDebug() << "Error reading " << url << ": " << error;
_failedToLoad = true;
return;
}
foreach (const FBXMesh& mesh, _geometry.meshes) {
NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
int totalIndices = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
if (!part.diffuseFilename.isEmpty()) {
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
}
if (!part.normalFilename.isEmpty()) {
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.normalFilename)), true);
}
networkMesh.parts.append(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
}
networkMesh.indexBuffer.create();
networkMesh.indexBuffer.bind();
networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
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);
}
networkMesh.indexBuffer.release();
networkMesh.vertexBuffer.create();
networkMesh.vertexBuffer.bind();
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
// 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) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
mesh.tangents.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
mesh.texCoords.size() * sizeof(glm::vec2));
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
mesh.clusterIndices.size() * sizeof(glm::vec4));
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
mesh.clusterWeights.size() * sizeof(glm::vec4));
// if there's no springiness, then the cluster indices/weights can be static
} else if (mesh.springiness == 0.0f) {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
mesh.texCoords.size() * sizeof(glm::vec2));
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
mesh.clusterIndices.size() * sizeof(glm::vec4));
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
mesh.clusterWeights.size() * sizeof(glm::vec4));
} else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
mesh.texCoords.size() * sizeof(glm::vec2));
}
networkMesh.vertexBuffer.release();
_meshes.append(networkMesh);
}
}
void NetworkGeometry::handleReplyError() {
QDebug debug = qDebug() << _reply->errorString();
QNetworkReply::NetworkError error = _reply->error();
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
// retry for certain types of failures
switch (error) {
@ -394,164 +577,19 @@ void NetworkGeometry::handleModelReplyError() {
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
debug << " -- retrying...";
return;
}
// fall through to final failure
}
default:
maybeLoadFallback();
_failedToLoad = true;
break;
}
}
void NetworkGeometry::handleMappingReplyError() {
_mappingReply->disconnect(this);
_mappingReply->deleteLater();
_mappingReply = NULL;
maybeReadModelWithMapping();
}
void NetworkGeometry::maybeReadModelWithMapping() {
if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
return;
}
QUrl url = _modelReply->url();
QByteArray model = _modelReply->readAll();
_modelReply->disconnect(this);
_modelReply->deleteLater();
_modelReply = NULL;
QByteArray mapping;
if (_mappingReply != NULL) {
mapping = _mappingReply->readAll();
_mappingReply->disconnect(this);
_mappingReply->deleteLater();
_mappingReply = NULL;
}
try {
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
} catch (const QString& error) {
qDebug() << "Error reading " << url << ": " << error;
maybeLoadFallback();
return;
}
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, false, mesh.isEye);
}
if (!part.normalFilename.isEmpty()) {
url.setPath(basePath + part.normalFilename);
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
}
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, 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) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + 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, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
mesh.texCoords.constData());
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
mesh.clusterIndices.constData());
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, 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) {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
mesh.clusterIndices.constData());
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
mesh.clusterWeights.constData());
} else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
mesh.texCoords.constData());
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
_meshes.append(networkMesh);
}
emit loaded();
}
void NetworkGeometry::loadFallback() {
_geometry = _fallback->_geometry;
_meshes = _fallback->_meshes;
emit loaded();
}
void NetworkGeometry::maybeLoadFallback() {
if (_fallback) {
if (_fallback->isLoaded()) {
loadFallback();
} else {
connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
}
}
}
bool NetworkMeshPart::isTranslucent() const {
return diffuseTexture && diffuseTexture->isTranslucent();
}

View file

@ -9,17 +9,20 @@
#ifndef __interface__GeometryCache__
#define __interface__GeometryCache__
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QHash>
#include <QMap>
#include <QNetworkRequest>
#include <QObject>
#include <QOpenGLBuffer>
#include <QSharedPointer>
#include <QWeakPointer>
#include "FBXReader.h"
#include "InterfaceConfig.h"
class QNetworkReply;
class QOpenGLBuffer;
class NetworkGeometry;
class NetworkMesh;
@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
public:
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback);
/// A hysteresis value indicating that we have no state memory.
static const float NO_HYSTERESIS;
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
~NetworkGeometry();
/// Checks whether the geometry is fulled loaded.
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
/// Returns a pointer to the geometry appropriate for the specified distance.
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis) const;
const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
signals:
void loaded();
private slots:
void makeModelRequest();
void handleModelReplyError();
void handleMappingReplyError();
void maybeReadModelWithMapping();
void loadFallback();
void makeRequest();
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleReplyError();
private:
void maybeLoadFallback();
friend class GeometryCache;
QNetworkRequest _modelRequest;
QNetworkReply* _modelReply;
QNetworkReply* _mappingReply;
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
QNetworkRequest _request;
QNetworkReply* _reply;
QVariantHash _mapping;
QUrl _textureBase;
QSharedPointer<NetworkGeometry> _fallback;
bool _startedLoading;
bool _failedToLoad;
int _attempts;
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
FBXGeometry _geometry;
QVector<NetworkMesh> _meshes;
QWeakPointer<NetworkGeometry> _lodParent;
};
/// The state associated with a single mesh part.
@ -110,8 +124,8 @@ public:
class NetworkMesh {
public:
GLuint indexBufferID;
GLuint vertexBufferID;
QOpenGLBuffer indexBuffer;
QOpenGLBuffer vertexBuffer;
QVector<NetworkMeshPart> parts;

View file

@ -90,6 +90,16 @@ void Model::reset() {
}
void Model::simulate(float deltaTime) {
// update our LOD
if (_geometry) {
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
if (_geometry != geometry) {
deleteGeometry();
_dilatedTextures.clear();
_geometry = geometry;
}
}
if (!isActive()) {
return;
}
@ -409,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
// delete our local geometry and custom textures
deleteGeometry();
_dilatedTextures.clear();
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
_baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
}
glm::vec4 Model::computeAverageColor() const {
@ -797,13 +808,17 @@ void Model::renderMeshes(float alpha, bool translucent) {
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
continue;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
const FBXMesh& mesh = geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
if (vertexCount == 0) {
// sanity check
continue;
}
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
ProgramObject* program = &_program;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
@ -910,11 +925,11 @@ void Model::renderMeshes(float alpha, bool translucent) {
qint64 offset = 0;
for (int j = 0; j < networkMesh.parts.size(); j++) {
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
const FBXMeshPart& part = mesh.parts.at(j);
if (networkPart.isTranslucent() != translucent) {
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
continue;
}
const FBXMeshPart& part = mesh.parts.at(j);
// apply material properties
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(part.specularColor, alpha);

View file

@ -227,6 +227,9 @@ private:
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);
QSharedPointer<NetworkGeometry> _baseGeometry;
float _lodHysteresis;
float _pupilDilation;
std::vector<float> _blendshapeCoefficients;

View file

@ -0,0 +1,61 @@
//
// Base3DOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Base3DOverlay.h"
#include "TextRenderer.h"
const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f);
const float DEFAULT_LINE_WIDTH = 1.0f;
Base3DOverlay::Base3DOverlay() :
_position(DEFAULT_POSITION),
_lineWidth(DEFAULT_LINE_WIDTH)
{
}
Base3DOverlay::~Base3DOverlay() {
}
void Base3DOverlay::setProperties(const QScriptValue& properties) {
Overlay::setProperties(properties);
QScriptValue position = properties.property("position");
// if "position" property was not there, check to see if they included aliases: start, point, p1
if (!position.isValid()) {
position = properties.property("start");
if (!position.isValid()) {
position = properties.property("p1");
if (!position.isValid()) {
position = properties.property("point");
}
}
}
if (position.isValid()) {
QScriptValue x = position.property("x");
QScriptValue y = position.property("y");
QScriptValue z = position.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setPosition(newPosition);
}
}
if (properties.property("lineWidth").isValid()) {
setLineWidth(properties.property("lineWidth").toVariant().toFloat());
}
}

View file

@ -0,0 +1,36 @@
//
// Base3DOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Base3DOverlay__
#define __interface__Base3DOverlay__
#include "Overlay.h"
class Base3DOverlay : public Overlay {
Q_OBJECT
public:
Base3DOverlay();
~Base3DOverlay();
// getters
const glm::vec3& getPosition() const { return _position; }
float getLineWidth() const { return _lineWidth; }
// setters
void setPosition(const glm::vec3& position) { _position = position; }
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
virtual void setProperties(const QScriptValue& properties);
protected:
glm::vec3 _position;
float _lineWidth;
};
#endif /* defined(__interface__Base3DOverlay__) */

View file

@ -0,0 +1,44 @@
//
// Cube3DOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Cube3DOverlay.h"
Cube3DOverlay::Cube3DOverlay() {
}
Cube3DOverlay::~Cube3DOverlay() {
}
void Cube3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glDisable(GL_LIGHTING);
glPushMatrix();
glTranslatef(_position.x + _size * 0.5f,
_position.y + _size * 0.5f,
_position.z + _size * 0.5f);
glLineWidth(_lineWidth);
if (_isSolid) {
glutSolidCube(_size);
} else {
glutWireCube(_size);
}
glPopMatrix();
}

View file

@ -0,0 +1,23 @@
//
// Cube3DOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Cube3DOverlay__
#define __interface__Cube3DOverlay__
#include "Volume3DOverlay.h"
class Cube3DOverlay : public Volume3DOverlay {
Q_OBJECT
public:
Cube3DOverlay();
~Cube3DOverlay();
virtual void render();
};
#endif /* defined(__interface__Cube3DOverlay__) */

View file

@ -0,0 +1,145 @@
//
// ImageOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QPainter>
#include <QSvgRenderer>
#include <SharedUtil.h>
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_textureID(0),
_renderImage(false),
_textureBound(false),
_wantClipFromImage(false)
{
}
ImageOverlay::~ImageOverlay() {
if (_parent && _textureID) {
// do we need to call this?
//_parent->deleteTexture(_textureID);
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
manager->get(QNetworkRequest(url));
}
void ImageOverlay::replyFinished(QNetworkReply* reply) {
// replace our byte array with the downloaded data
QByteArray rawData = reply->readAll();
_textureImage.loadFromData(rawData);
_renderImage = true;
}
void ImageOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
if (_renderImage && !_textureBound) {
_textureID = _parent->bindTexture(_textureImage);
_textureBound = true;
}
if (_renderImage) {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _textureID);
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
float imageWidth = _textureImage.width();
float imageHeight = _textureImage.height();
QRect fromImage;
if (_wantClipFromImage) {
fromImage = _fromImage;
} else {
fromImage.setX(0);
fromImage.setY(0);
fromImage.setWidth(imageWidth);
fromImage.setHeight(imageHeight);
}
float x = fromImage.x() / imageWidth;
float y = fromImage.y() / imageHeight;
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
float h = fromImage.height() / imageHeight;
glBegin(GL_QUADS);
if (_renderImage) {
glTexCoord2f(x, 1.0f - y);
}
glVertex2f(_bounds.left(), _bounds.top());
if (_renderImage) {
glTexCoord2f(x + w, 1.0f - y);
}
glVertex2f(_bounds.right(), _bounds.top());
if (_renderImage) {
glTexCoord2f(x + w, 1.0f - (y + h));
}
glVertex2f(_bounds.right(), _bounds.bottom());
if (_renderImage) {
glTexCoord2f(x, 1.0f - (y + h));
}
glVertex2f(_bounds.left(), _bounds.bottom());
glEnd();
if (_renderImage) {
glDisable(GL_TEXTURE_2D);
}
}
void ImageOverlay::setProperties(const QScriptValue& properties) {
Overlay2D::setProperties(properties);
QScriptValue subImageBounds = properties.property("subImage");
if (subImageBounds.isValid()) {
QRect oldSubImageRect = _fromImage;
QRect subImageRect = _fromImage;
if (subImageBounds.property("x").isValid()) {
subImageRect.setX(subImageBounds.property("x").toVariant().toInt());
} else {
subImageRect.setX(oldSubImageRect.x());
}
if (subImageBounds.property("y").isValid()) {
subImageRect.setY(subImageBounds.property("y").toVariant().toInt());
} else {
subImageRect.setY(oldSubImageRect.y());
}
if (subImageBounds.property("width").isValid()) {
subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt());
} else {
subImageRect.setWidth(oldSubImageRect.width());
}
if (subImageBounds.property("height").isValid()) {
subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt());
} else {
subImageRect.setHeight(oldSubImageRect.height());
}
setClipFromSource(subImageRect);
}
QScriptValue imageURL = properties.property("imageURL");
if (imageURL.isValid()) {
setImageURL(imageURL.toVariant().toString());
}
}

View file

@ -0,0 +1,60 @@
//
// ImageOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__ImageOverlay__
#define __interface__ImageOverlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <QUrl>
#include <SharedUtil.h>
#include "Overlay.h"
#include "Overlay2D.h"
class ImageOverlay : public Overlay2D {
Q_OBJECT
public:
ImageOverlay();
~ImageOverlay();
virtual void render();
// getters
const QRect& getClipFromSource() const { return _fromImage; }
const QUrl& getImageURL() const { return _imageURL; }
// setters
void setClipFromSource(const QRect& bounds) { _fromImage = bounds; _wantClipFromImage = true; }
void setImageURL(const QUrl& url);
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply); // we actually want to hide this...
private:
QUrl _imageURL;
QImage _textureImage;
GLuint _textureID;
QRect _fromImage; // where from in the image to sample
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
bool _textureBound; // has the texture been bound
bool _wantClipFromImage;
};
#endif /* defined(__interface__ImageOverlay__) */

View file

@ -0,0 +1,60 @@
//
// Line3DOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include "Line3DOverlay.h"
Line3DOverlay::Line3DOverlay() {
}
Line3DOverlay::~Line3DOverlay() {
}
void Line3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glDisable(GL_LIGHTING);
glLineWidth(_lineWidth);
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glBegin(GL_LINES);
glVertex3f(_position.x, _position.y, _position.z);
glVertex3f(_end.x, _end.y, _end.z);
glEnd();
glEnable(GL_LIGHTING);
}
void Line3DOverlay::setProperties(const QScriptValue& properties) {
Base3DOverlay::setProperties(properties);
QScriptValue end = properties.property("end");
// if "end" property was not there, check to see if they included aliases: endPoint, or p2
if (!end.isValid()) {
end = properties.property("endPoint");
if (!end.isValid()) {
end = properties.property("p2");
}
}
if (end.isValid()) {
QScriptValue x = end.property("x");
QScriptValue y = end.property("y");
QScriptValue z = end.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newEnd;
newEnd.x = x.toVariant().toFloat();
newEnd.y = y.toVariant().toFloat();
newEnd.z = z.toVariant().toFloat();
setEnd(newEnd);
}
}
}

View file

@ -0,0 +1,34 @@
//
// Line3DOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Line3DOverlay__
#define __interface__Line3DOverlay__
#include "Base3DOverlay.h"
class Line3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
Line3DOverlay();
~Line3DOverlay();
virtual void render();
// getters
const glm::vec3& getEnd() const { return _end; }
// setters
void setEnd(const glm::vec3& end) { _end = end; }
virtual void setProperties(const QScriptValue& properties);
protected:
glm::vec3 _end;
};
#endif /* defined(__interface__Line3DOverlay__) */

View file

@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() {
delete _boundaryLevelAdjust;
}
void LodToolsDialog::reloadSliders() {
_lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE);
_boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust());
_feedback->setText(getFeedbackText());
}
void LodToolsDialog::sizeScaleValueChanged(int value) {
float realValue = value * TREE_SCALE;
Menu::getInstance()->setVoxelSizeScale(realValue);

View file

@ -28,6 +28,7 @@ public slots:
void sizeScaleValueChanged(int value);
void boundaryLevelValueChanged(int value);
void resetClicked(bool checked);
void reloadSliders();
protected:

View file

@ -0,0 +1,55 @@
//
// Overlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QSvgRenderer>
#include <QPainter>
#include <QGLWidget>
#include <SharedUtil.h>
#include "Overlay.h"
Overlay::Overlay() :
_parent(NULL),
_alpha(DEFAULT_ALPHA),
_color(DEFAULT_BACKGROUND_COLOR),
_visible(true)
{
}
void Overlay::init(QGLWidget* parent) {
_parent = parent;
}
Overlay::~Overlay() {
}
void Overlay::setProperties(const QScriptValue& properties) {
QScriptValue color = properties.property("color");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_color.red = red.toVariant().toInt();
_color.green = green.toVariant().toInt();
_color.blue = blue.toVariant().toInt();
}
}
if (properties.property("alpha").isValid()) {
setAlpha(properties.property("alpha").toVariant().toFloat());
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
}
}

View file

@ -0,0 +1,53 @@
//
// Overlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Overlay__
#define __interface__Overlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <SharedUtil.h> // for xColor
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
const float DEFAULT_ALPHA = 0.7f;
class Overlay : public QObject {
Q_OBJECT
public:
Overlay();
~Overlay();
void init(QGLWidget* parent);
virtual void render() = 0;
// getters
bool getVisible() const { return _visible; }
const xColor& getColor() const { return _color; }
float getAlpha() const { return _alpha; }
// setters
void setVisible(bool visible) { _visible = visible; }
void setColor(const xColor& color) { _color = color; }
void setAlpha(float alpha) { _alpha = alpha; }
virtual void setProperties(const QScriptValue& properties);
protected:
QGLWidget* _parent;
float _alpha;
xColor _color;
bool _visible; // should the overlay be drawn at all
};
#endif /* defined(__interface__Overlay__) */

View file

@ -0,0 +1,63 @@
//
// Overlay2D.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QSvgRenderer>
#include <QPainter>
#include <QGLWidget>
#include <SharedUtil.h>
#include "Overlay2D.h"
Overlay2D::Overlay2D() {
}
Overlay2D::~Overlay2D() {
}
void Overlay2D::setProperties(const QScriptValue& properties) {
Overlay::setProperties(properties);
QScriptValue bounds = properties.property("bounds");
if (bounds.isValid()) {
QRect boundsRect;
boundsRect.setX(bounds.property("x").toVariant().toInt());
boundsRect.setY(bounds.property("y").toVariant().toInt());
boundsRect.setWidth(bounds.property("width").toVariant().toInt());
boundsRect.setHeight(bounds.property("height").toVariant().toInt());
setBounds(boundsRect);
} else {
QRect oldBounds = getBounds();
QRect newBounds = oldBounds;
if (properties.property("x").isValid()) {
newBounds.setX(properties.property("x").toVariant().toInt());
} else {
newBounds.setX(oldBounds.x());
}
if (properties.property("y").isValid()) {
newBounds.setY(properties.property("y").toVariant().toInt());
} else {
newBounds.setY(oldBounds.y());
}
if (properties.property("width").isValid()) {
newBounds.setWidth(properties.property("width").toVariant().toInt());
} else {
newBounds.setWidth(oldBounds.width());
}
if (properties.property("height").isValid()) {
newBounds.setHeight(properties.property("height").toVariant().toInt());
} else {
newBounds.setHeight(oldBounds.height());
}
setBounds(newBounds);
//qDebug() << "set bounds to " << getBounds();
}
}

View file

@ -0,0 +1,51 @@
//
// Overlay2D.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Overlay2D__
#define __interface__Overlay2D__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <SharedUtil.h> // for xColor
#include "Overlay.h"
class Overlay2D : public Overlay {
Q_OBJECT
public:
Overlay2D();
~Overlay2D();
// getters
int getX() const { return _bounds.x(); }
int getY() const { return _bounds.y(); }
int getWidth() const { return _bounds.width(); }
int getHeight() const { return _bounds.height(); }
const QRect& getBounds() const { return _bounds; }
// setters
void setX(int x) { _bounds.setX(x); }
void setY(int y) { _bounds.setY(y); }
void setWidth(int width) { _bounds.setWidth(width); }
void setHeight(int height) { _bounds.setHeight(height); }
void setBounds(const QRect& bounds) { _bounds = bounds; }
virtual void setProperties(const QScriptValue& properties);
protected:
QRect _bounds; // where on the screen to draw
};
#endif /* defined(__interface__Overlay2D__) */

View file

@ -0,0 +1,129 @@
//
// Overlays.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include "Cube3DOverlay.h"
#include "ImageOverlay.h"
#include "Line3DOverlay.h"
#include "Overlays.h"
#include "Sphere3DOverlay.h"
#include "TextOverlay.h"
unsigned int Overlays::_nextOverlayID = 1;
Overlays::Overlays() {
}
Overlays::~Overlays() {
}
void Overlays::init(QGLWidget* parent) {
_parent = parent;
}
void Overlays::render2D() {
foreach(Overlay* thisOverlay, _overlays2D) {
thisOverlay->render();
}
}
void Overlays::render3D() {
foreach(Overlay* thisOverlay, _overlays3D) {
thisOverlay->render();
}
}
// TODO: make multi-threaded safe
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
unsigned int thisID = 0;
bool created = false;
bool is3D = false;
Overlay* thisOverlay = NULL;
if (type == "image") {
thisOverlay = new ImageOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
} else if (type == "text") {
thisOverlay = new TextOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
} else if (type == "cube") {
thisOverlay = new Cube3DOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "sphere") {
thisOverlay = new Sphere3DOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "line3d") {
thisOverlay = new Line3DOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
}
if (created) {
thisID = _nextOverlayID;
_nextOverlayID++;
if (is3D) {
_overlays3D[thisID] = thisOverlay;
} else {
_overlays2D[thisID] = thisOverlay;
}
}
return thisID;
}
// TODO: make multi-threaded safe
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
Overlay* thisOverlay = NULL;
if (_overlays2D.contains(id)) {
thisOverlay = _overlays2D[id];
} else if (_overlays3D.contains(id)) {
thisOverlay = _overlays3D[id];
}
if (thisOverlay) {
thisOverlay->setProperties(properties);
return true;
}
return false;
}
// TODO: make multi-threaded safe
void Overlays::deleteOverlay(unsigned int id) {
if (_overlays2D.contains(id)) {
_overlays2D.erase(_overlays2D.find(id));
} else if (_overlays3D.contains(id)) {
_overlays3D.erase(_overlays3D.find(id));
}
}
unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
QMapIterator<unsigned int, Overlay*> i(_overlays2D);
i.toBack();
while (i.hasPrevious()) {
i.previous();
unsigned int thisID = i.key();
Overlay2D* thisOverlay = static_cast<Overlay2D*>(i.value());
if (thisOverlay->getVisible() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
return thisID;
}
}
return 0; // not found
}

View file

@ -0,0 +1,46 @@
//
// Overlays.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Overlays__
#define __interface__Overlays__
#include <QScriptValue>
#include "Overlay.h"
class Overlays : public QObject {
Q_OBJECT
public:
Overlays();
~Overlays();
void init(QGLWidget* parent);
void render3D();
void render2D();
public slots:
/// adds an overlay with the specific properties
unsigned int addOverlay(const QString& type, const QScriptValue& properties);
/// edits an overlay updating only the included properties, will return the identified OverlayID in case of
/// successful edit, if the input id is for an unknown overlay this function will have no effect
bool editOverlay(unsigned int id, const QScriptValue& properties);
/// deletes a particle
void deleteOverlay(unsigned int id);
/// returns the top most overlay at the screen point, or 0 if not overlay at that point
unsigned int getOverlayAtPoint(const glm::vec2& point);
private:
QMap<unsigned int, Overlay*> _overlays2D;
QMap<unsigned int, Overlay*> _overlays3D;
static unsigned int _nextOverlayID;
QGLWidget* _parent;
};
#endif /* defined(__interface__Overlays__) */

View file

@ -0,0 +1,45 @@
//
// Sphere3DOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Sphere3DOverlay.h"
Sphere3DOverlay::Sphere3DOverlay() {
}
Sphere3DOverlay::~Sphere3DOverlay() {
}
void Sphere3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glDisable(GL_LIGHTING);
glPushMatrix();
glTranslatef(_position.x + _size * 0.5f,
_position.y + _size * 0.5f,
_position.z + _size * 0.5f);
glLineWidth(_lineWidth);
const int slices = 15;
if (_isSolid) {
glutSolidSphere(_size, slices, slices);
} else {
glutWireSphere(_size, slices, slices);
}
glPopMatrix();
}

View file

@ -0,0 +1,23 @@
//
// Sphere3DOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Sphere3DOverlay__
#define __interface__Sphere3DOverlay__
#include "Volume3DOverlay.h"
class Sphere3DOverlay : public Volume3DOverlay {
Q_OBJECT
public:
Sphere3DOverlay();
~Sphere3DOverlay();
virtual void render();
};
#endif /* defined(__interface__Sphere3DOverlay__) */

View file

@ -0,0 +1,82 @@
//
// TextOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "TextOverlay.h"
#include "TextRenderer.h"
TextOverlay::TextOverlay() :
_leftMargin(DEFAULT_MARGIN),
_topMargin(DEFAULT_MARGIN)
{
}
TextOverlay::~TextOverlay() {
}
void TextOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glBegin(GL_QUADS);
glVertex2f(_bounds.left(), _bounds.top());
glVertex2f(_bounds.right(), _bounds.top());
glVertex2f(_bounds.right(), _bounds.bottom());
glVertex2f(_bounds.left(), _bounds.bottom());
glEnd();
//TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
// EffectType effect = NO_EFFECT, int effectThickness = 1);
TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50);
const int leftAdjust = -1; // required to make text render relative to left edge of bounds
const int topAdjust = -2; // required to make text render relative to top edge of bounds
int x = _bounds.left() + _leftMargin + leftAdjust;
int y = _bounds.top() + _topMargin + topAdjust;
glColor3f(1.0f, 1.0f, 1.0f);
QStringList lines = _text.split("\n");
int lineOffset = 0;
foreach(QString thisLine, lines) {
if (lineOffset == 0) {
lineOffset = textRenderer.calculateHeight(qPrintable(thisLine));
}
lineOffset += textRenderer.draw(x, y + lineOffset, qPrintable(thisLine));
const int lineGap = 2;
lineOffset += lineGap;
}
}
void TextOverlay::setProperties(const QScriptValue& properties) {
Overlay2D::setProperties(properties);
QScriptValue text = properties.property("text");
if (text.isValid()) {
setText(text.toVariant().toString());
}
if (properties.property("leftMargin").isValid()) {
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
}
if (properties.property("topMargin").isValid()) {
setTopMargin(properties.property("topMargin").toVariant().toInt());
}
}

View file

@ -0,0 +1,59 @@
//
// TextOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__TextOverlay__
#define __interface__TextOverlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <QUrl>
#include <SharedUtil.h>
#include "Overlay.h"
#include "Overlay2D.h"
const int DEFAULT_MARGIN = 10;
class TextOverlay : public Overlay2D {
Q_OBJECT
public:
TextOverlay();
~TextOverlay();
virtual void render();
// getters
const QString& getText() const { return _text; }
int getLeftMargin() const { return _leftMargin; }
int getTopMargin() const { return _topMargin; }
// setters
void setText(const QString& text) { _text = text; }
void setLeftMargin(int margin) { _leftMargin = margin; }
void setTopMargin(int margin) { _topMargin = margin; }
virtual void setProperties(const QScriptValue& properties);
private:
QString _text;
int _leftMargin;
int _topMargin;
};
#endif /* defined(__interface__TextOverlay__) */

View file

@ -8,6 +8,8 @@
#include <QFont>
#include <QPaintEngine>
#include <QtDebug>
#include <QString>
#include <QStringList>
#include "InterfaceConfig.h"
#include "TextRenderer.h"
@ -30,10 +32,25 @@ TextRenderer::~TextRenderer() {
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
}
void TextRenderer::draw(int x, int y, const char* str) {
int TextRenderer::calculateHeight(const char* str) {
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
}
return maxHeight;
}
int TextRenderer::draw(int x, int y, const char* str) {
glEnable(GL_TEXTURE_2D);
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
@ -41,19 +58,23 @@ void TextRenderer::draw(int x, int y, const char* str) {
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
int left = x + glyph.bounds().x();
int right = x + glyph.bounds().x() + glyph.bounds().width();
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
float scale = 1.0 / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
glBegin(GL_QUADS);
glTexCoord2f(ls, bt);
glVertex2f(left, bottom);
@ -64,12 +85,13 @@ void TextRenderer::draw(int x, int y, const char* str) {
glTexCoord2f(ls, tt);
glVertex2f(left, top);
glEnd();
x += glyph.width();
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
return maxHeight;
}
int TextRenderer::computeWidth(char ch)

View file

@ -20,6 +20,16 @@
// a special "character" that renders as a solid block
const char SOLID_BLOCK_CHAR = 127;
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
class Glyph;
class TextRenderer {
@ -33,7 +43,11 @@ public:
const QFontMetrics& metrics() const { return _metrics; }
void draw(int x, int y, const char* str);
// returns the height of the tallest character
int calculateHeight(const char* str);
// also returns the height of the tallest character
int draw(int x, int y, const char* str);
int computeWidth(char ch);
int computeWidth(const char* str);

View file

@ -0,0 +1,47 @@
//
// Volume3DOverlay.cpp
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Volume3DOverlay.h"
const float DEFAULT_SIZE = 1.0f;
const bool DEFAULT_IS_SOLID = false;
Volume3DOverlay::Volume3DOverlay() :
_size(DEFAULT_SIZE),
_isSolid(DEFAULT_IS_SOLID)
{
}
Volume3DOverlay::~Volume3DOverlay() {
}
void Volume3DOverlay::setProperties(const QScriptValue& properties) {
Base3DOverlay::setProperties(properties);
if (properties.property("size").isValid()) {
setSize(properties.property("size").toVariant().toFloat());
}
if (properties.property("isSolid").isValid()) {
setIsSolid(properties.property("isSolid").toVariant().toBool());
}
if (properties.property("isWire").isValid()) {
setIsSolid(!properties.property("isWire").toVariant().toBool());
}
if (properties.property("solid").isValid()) {
setIsSolid(properties.property("solid").toVariant().toBool());
}
if (properties.property("wire").isValid()) {
setIsSolid(!properties.property("wire").toVariant().toBool());
}
}

View file

@ -0,0 +1,42 @@
//
// Volume3DOverlay.h
// interface
//
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Volume3DOverlay__
#define __interface__Volume3DOverlay__
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <QScriptValue>
#include "Base3DOverlay.h"
class Volume3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
Volume3DOverlay();
~Volume3DOverlay();
// getters
float getSize() const { return _size; }
bool getIsSolid() const { return _isSolid; }
// setters
void setSize(float size) { _size = size; }
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
virtual void setProperties(const QScriptValue& properties);
protected:
float _size;
bool _isSolid;
};
#endif /* defined(__interface__Volume3DOverlay__) */

View file

@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
enum KeyState {
NO_KEY_DOWN = 0,

View file

@ -23,7 +23,6 @@ OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packe
}
OctreeScriptingInterface::~OctreeScriptingInterface() {
qDebug() << "OctreeScriptingInterface::~OctreeScriptingInterface() this=" << this;
cleanupManagedObjects();
}

View file

@ -45,6 +45,8 @@ public:
_alwaysDisplay(alwaysDisplay),
_runningTotal(runningTotal),
_totalCalls(totalCalls) { }
quint64 elapsed() const { return (usecTimestampNow() - _start); };
~PerformanceWarning();