mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge branch 'master' of https://github.com/highfidelity/hifi into correct-target-frame-rate
This commit is contained in:
commit
d51785a259
65 changed files with 633 additions and 1546 deletions
|
@ -266,3 +266,72 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
tree->setWantEditLogging(wantEditLogging);
|
||||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||
}
|
||||
|
||||
|
||||
// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
|
||||
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
|
||||
// Since this version uses a single shared QMap for all senders, there could be some lock contention
|
||||
// on this QWriteLocker
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
}
|
||||
|
||||
void EntityServer::trackViewerGone(const QUuid& viewerNode) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats.remove(viewerNode);
|
||||
}
|
||||
|
||||
QString EntityServer::serverSubclassStats() {
|
||||
QLocale locale(QLocale::English);
|
||||
QString statsString;
|
||||
|
||||
// display memory usage stats
|
||||
statsString += "<b>Entity Server Memory Statistics</b>\r\n";
|
||||
statsString += QString().sprintf("EntityTreeElement size... %ld bytes\r\n", sizeof(EntityTreeElement));
|
||||
statsString += QString().sprintf(" EntityItem size... %ld bytes\r\n", sizeof(EntityItem));
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += "<b>Entity Server Sending to Viewer Statistics</b>\r\n";
|
||||
statsString += "----- Viewer Node ID ----------------- ----- Entity ID ---------------------- "
|
||||
"---------- Last Sent To ---------- ---------- Last Edited -----------\r\n";
|
||||
|
||||
int viewers = 0;
|
||||
const int COLUMN_WIDTH = 24;
|
||||
|
||||
{
|
||||
QReadLocker locker(&_viewerSendingStatsLock);
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
for (auto viewerID : _viewerSendingStats.keys()) {
|
||||
statsString += viewerID.toString() + "\r\n";
|
||||
|
||||
auto viewerData = _viewerSendingStats[viewerID];
|
||||
for (auto entityID : viewerData.keys()) {
|
||||
ViewerSendingStats stats = viewerData[entityID];
|
||||
|
||||
quint64 elapsedSinceSent = now - stats.lastSent;
|
||||
double sentMsecsAgo = (double)(elapsedSinceSent / USECS_PER_MSEC);
|
||||
|
||||
quint64 elapsedSinceEdit = now - stats.lastEdited;
|
||||
double editMsecsAgo = (double)(elapsedSinceEdit / USECS_PER_MSEC);
|
||||
|
||||
statsString += " "; // the viewerID spacing
|
||||
statsString += entityID.toString();
|
||||
statsString += " ";
|
||||
statsString += QString("%1 msecs ago")
|
||||
.arg(locale.toString((double)sentMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString("%1 msecs ago")
|
||||
.arg(locale.toString((double)editMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += "\r\n";
|
||||
}
|
||||
viewers++;
|
||||
}
|
||||
}
|
||||
if (viewers < 1) {
|
||||
statsString += " no viewers... \r\n";
|
||||
}
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
return statsString;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
#include "EntityTree.h"
|
||||
|
||||
/// Handles assignments of type EntityServer - sending entities to various clients.
|
||||
|
||||
struct ViewerSendingStats {
|
||||
quint64 lastSent;
|
||||
quint64 lastEdited;
|
||||
};
|
||||
|
||||
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -44,6 +50,10 @@ public:
|
|||
|
||||
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) override;
|
||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||
virtual QString serverSubclassStats();
|
||||
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode);
|
||||
virtual void trackViewerGone(const QUuid& viewerNode);
|
||||
|
||||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
@ -57,6 +67,9 @@ private slots:
|
|||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -179,15 +179,9 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
_currentPacketIsColor = getWantColor();
|
||||
_currentPacketIsCompressed = getWantCompression();
|
||||
OCTREE_PACKET_FLAGS flags = 0;
|
||||
if (_currentPacketIsColor) {
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
}
|
||||
if (_currentPacketIsCompressed) {
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
}
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
|
||||
_octreePacket->reset();
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <CoverageMap.h>
|
||||
#include <NodeData.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <OctreeElementBag.h>
|
||||
|
@ -55,7 +54,6 @@ public:
|
|||
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
|
||||
|
||||
OctreeElementBag elementBag;
|
||||
CoverageMap map;
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
|
||||
|
@ -79,9 +77,7 @@ public:
|
|||
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
|
||||
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
|
||||
bool getCurrentPacketFormatMatches() {
|
||||
return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
|
||||
}
|
||||
bool getCurrentPacketFormatMatches() { return (getCurrentPacketIsCompressed() == true); } // FIXME
|
||||
|
||||
bool hasLodChanged() const { return _lodChanged; }
|
||||
|
||||
|
|
|
@ -321,8 +321,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
bool wantColor = nodeData->getWantColor();
|
||||
bool wantCompression = nodeData->getWantCompression();
|
||||
|
||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
||||
// then let's just send that waiting packet.
|
||||
|
@ -333,10 +331,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
nodeData->resetOctreePacket();
|
||||
}
|
||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
_packetData.changeSettings(wantCompression, targetSize);
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
_packetData.changeSettings(targetSize);
|
||||
}
|
||||
|
||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
|
@ -350,7 +346,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
|
||||
nodeData->dumpOutOfView();
|
||||
}
|
||||
nodeData->map.erase();
|
||||
}
|
||||
|
||||
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
|
||||
|
@ -451,22 +446,25 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
*/
|
||||
|
||||
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
||||
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
|
||||
|
||||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(),
|
||||
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
||||
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
|
||||
boundaryLevelAdjust, octreeSizeScale,
|
||||
nodeData->getLastTimeBagEmpty(),
|
||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
|
||||
&nodeData->extraEncodeData);
|
||||
|
||||
// Our trackSend() function is implemented by the server subclass, and will be called back
|
||||
// during the encodeTreeBitstream() as new entities/data elements are sent
|
||||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUUID);
|
||||
};
|
||||
|
||||
// TODO: should this include the lock time or not? This stat is sent down to the client,
|
||||
// it seems like it may be a good idea to include the lock time as part of the encode time
|
||||
// are reported to client. Since you can encode without the lock
|
||||
|
@ -550,10 +548,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
|
||||
quint64 packetSendingEnd = usecTimestampNow();
|
||||
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
||||
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
} else {
|
||||
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
|
||||
// but we've finalized the _packetData, so we want to start a new section, we will do that by
|
||||
|
@ -563,7 +558,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// a larger compressed size then uncompressed size
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||
}
|
||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||
_packetData.changeSettings(targetSize); // will do reset
|
||||
|
||||
}
|
||||
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
||||
|
@ -628,7 +623,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->elementBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
||||
}
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
|
|
|
@ -821,6 +821,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
.arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
|
||||
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += serverSubclassStats();
|
||||
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += "</pre>\r\n";
|
||||
statsString += "</doc></html>";
|
||||
|
||||
|
@ -1179,6 +1184,8 @@ void OctreeServer::nodeKilled(SharedNodePointer node) {
|
|||
if (usecsElapsed > 1000) {
|
||||
qDebug() << qPrintable(_safeServerName) << "server nodeKilled() took: " << usecsElapsed << " usecs for node:" << *node;
|
||||
}
|
||||
|
||||
trackViewerGone(node->getUUID());
|
||||
}
|
||||
|
||||
void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
|
||||
|
|
|
@ -79,6 +79,9 @@ public:
|
|||
virtual void beforeRun() { }
|
||||
virtual bool hasSpecialPacketsToSend(const SharedNodePointer& node) { return false; }
|
||||
virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; }
|
||||
virtual QString serverSubclassStats() { return QString(); }
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) { }
|
||||
virtual void trackViewerGone(const QUuid& viewerNode) { }
|
||||
|
||||
static float SKIP_TIME; // use this for trackXXXTime() calls for non-times
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ function randVector(a, b) {
|
|||
|
||||
var startTimeInSeconds = new Date().getTime() / 1000;
|
||||
|
||||
var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.0, y: 0.4, z: 0.2 };
|
||||
var NATURAL_SIZE_OF_BUTTERFLY = { x:0.5, y: 0.2, z: 0.1 };
|
||||
|
||||
var lifeTime = 3600; // One hour lifespan
|
||||
var range = 7.0; // Over what distance in meters do you want the flock to fly around
|
||||
|
@ -65,8 +65,8 @@ function addButterfly() {
|
|||
var color = { red: 100, green: 100, blue: 100 };
|
||||
var size = 0;
|
||||
|
||||
var MINSIZE = 0.06;
|
||||
var RANGESIZE = 0.2;
|
||||
var MINSIZE = 0.01;
|
||||
var RANGESIZE = 0.05;
|
||||
var maxSize = MINSIZE + RANGESIZE;
|
||||
|
||||
size = MINSIZE + Math.random() * RANGESIZE;
|
||||
|
@ -74,7 +74,7 @@ function addButterfly() {
|
|||
var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize));
|
||||
|
||||
var GRAVITY = -0.2;
|
||||
var newFrameRate = 20 + Math.random() * 30;
|
||||
var newFrameRate = 29 + Math.random() * 30;
|
||||
var properties = {
|
||||
type: "Model",
|
||||
lifetime: lifeTime,
|
||||
|
@ -86,17 +86,13 @@ function addButterfly() {
|
|||
dimensions: dimensions,
|
||||
color: color,
|
||||
animation: {
|
||||
url: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx",
|
||||
firstFrame: 0,
|
||||
url: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx",
|
||||
fps: newFrameRate,
|
||||
currentFrame: 0,
|
||||
hold: false,
|
||||
lastFrame: 10000,
|
||||
loop: true,
|
||||
running: true,
|
||||
startAutomatically:false
|
||||
},
|
||||
modelURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx"
|
||||
modelURL: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx"
|
||||
};
|
||||
butterflies.push(Entities.addEntity(properties));
|
||||
}
|
||||
|
|
|
@ -47,7 +47,12 @@ function makeBasketball() {
|
|||
modelURL: basketballURL,
|
||||
restitution: 1.0,
|
||||
damping: 0.00001,
|
||||
shapeType: "sphere"
|
||||
shapeType: "sphere",
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
originalPosition = position;
|
||||
}
|
||||
|
|
|
@ -15,12 +15,20 @@ function createDoll() {
|
|||
|
||||
var scriptURL = Script.resolvePath("doll.js");
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var naturalDimensions = { x: 1.63, y: 1.67, z: 0.26};
|
||||
var naturalDimensions = {
|
||||
x: 1.63,
|
||||
y: 1.67,
|
||||
z: 0.26
|
||||
};
|
||||
|
||||
var desiredDimensions = Vec3.multiply(naturalDimensions, 0.15);
|
||||
|
||||
|
||||
var doll = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "doll",
|
||||
|
@ -39,7 +47,12 @@ function createDoll() {
|
|||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
collisionsWillMove: true
|
||||
collisionsWillMove: true,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
return doll;
|
||||
}
|
||||
|
|
|
@ -18,14 +18,27 @@ var scriptURL = Script.resolvePath('flashlight.js');
|
|||
|
||||
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var flashlight = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: modelURL,
|
||||
position: center,
|
||||
dimensions: { x: 0.08, y: 0.30, z: 0.08},
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.30,
|
||||
z: 0.08
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
shapeType: 'box',
|
||||
script: scriptURL
|
||||
});
|
||||
script: scriptURL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
|
@ -35,7 +35,12 @@ var pingPongGun = Entities.addEntity({
|
|||
z: 0.47
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: COLLISION_SOUND_URL
|
||||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function cleanUp() {
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
{ "from": "Hydra.RB", "to": "Standard.RB" },
|
||||
{ "from": "Hydra.RS", "to": "Standard.RS" },
|
||||
|
||||
{ "from": [ "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": [ "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L0" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R0" ], "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
#include <ObjectMotionState.h>
|
||||
#include <OctalCode.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <gl/OffscreenGlCanvas.h>
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <PathUtils.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PhysicsEngine.h>
|
||||
|
@ -616,7 +616,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
// enable mouse tracking; otherwise, we only get drag events
|
||||
_glWidget->setMouseTracking(true);
|
||||
|
||||
_offscreenContext = new OffscreenGlCanvas();
|
||||
_offscreenContext = new OffscreenGLCanvas();
|
||||
_offscreenContext->create(_glWidget->context()->contextHandle());
|
||||
_offscreenContext->makeCurrent();
|
||||
initializeGL();
|
||||
|
@ -3074,10 +3074,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_octreeQuery.setWantLowResMoving(true);
|
||||
_octreeQuery.setWantColor(true);
|
||||
_octreeQuery.setWantDelta(true);
|
||||
_octreeQuery.setWantOcclusionCulling(false);
|
||||
_octreeQuery.setWantCompression(true);
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
#include "ui/ToolWindow.h"
|
||||
#include "UndoStackScriptingInterface.h"
|
||||
|
||||
class OffscreenGlCanvas;
|
||||
class OffscreenGLCanvas;
|
||||
class GLCanvas;
|
||||
class FaceTracker;
|
||||
class MainWindow;
|
||||
|
@ -420,7 +420,7 @@ private:
|
|||
|
||||
bool _dependencyManagerIsSetup;
|
||||
|
||||
OffscreenGlCanvas* _offscreenContext { nullptr };
|
||||
OffscreenGLCanvas* _offscreenContext { nullptr };
|
||||
DisplayPluginPointer _displayPlugin;
|
||||
InputPluginList _activeInputPlugins;
|
||||
|
||||
|
|
|
@ -9,54 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// FIXME ordering of headers
|
||||
#include "Application.h"
|
||||
#include "GLCanvas.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QWindow>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
|
||||
static QGLFormat& getDesiredGLFormat() {
|
||||
// Specify an OpenGL 3.3 format using the Core profile.
|
||||
// That is, no old-school fixed pipeline functionality
|
||||
static QGLFormat glFormat;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
glFormat.setVersion(4, 1);
|
||||
glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0
|
||||
glFormat.setSampleBuffers(false);
|
||||
glFormat.setDepth(false);
|
||||
glFormat.setStencil(false);
|
||||
});
|
||||
return glFormat;
|
||||
}
|
||||
|
||||
GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()) {
|
||||
#ifdef Q_OS_LINUX
|
||||
// Cause GLCanvas::eventFilter to be called.
|
||||
// It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux.
|
||||
qApp->installEventFilter(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
int GLCanvas::getDeviceWidth() const {
|
||||
return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
int GLCanvas::getDeviceHeight() const {
|
||||
return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
void GLCanvas::initializeGL() {
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAcceptDrops(true);
|
||||
// Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate.
|
||||
setAutoBufferSwap(false);
|
||||
}
|
||||
|
||||
void GLCanvas::paintGL() {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
|
@ -74,68 +35,8 @@ void GLCanvas::resizeGL(int width, int height) {
|
|||
}
|
||||
|
||||
bool GLCanvas::event(QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::Resize:
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::DragEnter:
|
||||
case QEvent::Drop:
|
||||
if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::Paint:
|
||||
// Ignore paint events that occur after we've decided to quit
|
||||
if (qApp->isAboutToQuit()) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (QEvent::Paint == event->type() && qApp->isAboutToQuit()) {
|
||||
return true;
|
||||
}
|
||||
return QGLWidget::event(event);
|
||||
}
|
||||
|
||||
|
||||
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
|
||||
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
|
||||
// receive keyPress events for the Alt (and Meta) key in a reliable manner.
|
||||
//
|
||||
// This filter catches events before QMenuBar can steal the keyboard focus.
|
||||
// The idea was borrowed from
|
||||
// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html
|
||||
|
||||
bool GLCanvas::eventFilter(QObject*, QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::ShortcutOverride:
|
||||
{
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
keyPressEvent(keyEvent);
|
||||
} else if (event->type() == QEvent::KeyRelease) {
|
||||
keyReleaseEvent(keyEvent);
|
||||
} else {
|
||||
QGLWidget::event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
return GLWidget::event(event);
|
||||
}
|
||||
|
|
|
@ -12,31 +12,15 @@
|
|||
#ifndef hifi_GLCanvas_h
|
||||
#define hifi_GLCanvas_h
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGLWidget>
|
||||
#include <QTimer>
|
||||
#include <gl/GLWidget.h>
|
||||
|
||||
/// customized canvas that simply forwards requests/events to the singleton application
|
||||
class GLCanvas : public QGLWidget {
|
||||
class GLCanvas : public GLWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GLCanvas();
|
||||
|
||||
int getDeviceWidth() const;
|
||||
int getDeviceHeight() const;
|
||||
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
|
||||
|
||||
protected:
|
||||
|
||||
virtual void initializeGL();
|
||||
virtual void paintGL();
|
||||
virtual void resizeGL(int width, int height);
|
||||
virtual bool event(QEvent* event);
|
||||
|
||||
private slots:
|
||||
bool eventFilter(QObject*, QEvent* event);
|
||||
|
||||
virtual void paintGL() override;
|
||||
virtual void resizeGL(int width, int height) override;
|
||||
virtual bool event(QEvent* event) override;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -77,9 +77,9 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
}
|
||||
|
||||
const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters
|
||||
Setting::Handle<float> avatarRenderDistanceHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON);
|
||||
void AvatarManager::setRenderDistanceHighLimit(float newValue) {
|
||||
avatarRenderDistanceHighLimit.set(newValue);
|
||||
Setting::Handle<float> avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON);
|
||||
void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) {
|
||||
avatarRenderDistanceInverseHighLimit.set(newValue);
|
||||
_renderDistanceController.setControlledValueHighLimit(newValue);
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ void AvatarManager::init() {
|
|||
|
||||
const float target_fps = qApp->getTargetFrameRate();
|
||||
_renderDistanceController.setMeasuredValueSetpoint(target_fps);
|
||||
_renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceHighLimit.get());
|
||||
_renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get());
|
||||
_renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE);
|
||||
// Advice for tuning parameters:
|
||||
// See PIDController.h. There's a section on tuning in the reference.
|
||||
|
|
|
@ -70,16 +70,16 @@ public:
|
|||
|
||||
// Expose results and parameter-tuning operations to other systems, such as stats and javascript.
|
||||
Q_INVOKABLE float getRenderDistance() { return _renderDistance; }
|
||||
Q_INVOKABLE float getRenderDistanceLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
|
||||
Q_INVOKABLE float getRenderDistanceHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); }
|
||||
Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
|
||||
Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); }
|
||||
Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; }
|
||||
Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); }
|
||||
Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); }
|
||||
Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceHighLimit(float newValue);
|
||||
Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue);
|
||||
|
||||
public slots:
|
||||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||
|
|
|
@ -43,9 +43,11 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_longTermAverageLoudness(-1.0f),
|
||||
_audioAttack(0.0f),
|
||||
_audioJawOpen(0.0f),
|
||||
_trailingAudioJawOpen(0.0f),
|
||||
_mouth2(0.0f),
|
||||
_mouth3(0.0f),
|
||||
_mouth4(0.0f),
|
||||
_mouthTime(0.0f),
|
||||
_renderLookatVectors(false),
|
||||
_renderLookatTarget(false),
|
||||
_saccade(0.0f, 0.0f, 0.0f),
|
||||
|
@ -246,6 +248,16 @@ void Head::calculateMouthShapes() {
|
|||
const float JAW_OPEN_SCALE = 0.015f;
|
||||
const float JAW_OPEN_RATE = 0.9f;
|
||||
const float JAW_CLOSE_RATE = 0.90f;
|
||||
const float TIMESTEP_CONSTANT = 0.0032f;
|
||||
const float MMMM_POWER = 0.10f;
|
||||
const float SMILE_POWER = 0.10f;
|
||||
const float FUNNEL_POWER = 0.35f;
|
||||
const float MMMM_SPEED = 2.685f;
|
||||
const float SMILE_SPEED = 1.0f;
|
||||
const float FUNNEL_SPEED = 2.335f;
|
||||
const float STOP_GAIN = 5.0f;
|
||||
|
||||
// From the change in loudness, decide how much to open or close the jaw
|
||||
float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE;
|
||||
if (audioDelta > _audioJawOpen) {
|
||||
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE;
|
||||
|
@ -253,21 +265,14 @@ void Head::calculateMouthShapes() {
|
|||
_audioJawOpen *= JAW_CLOSE_RATE;
|
||||
}
|
||||
_audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f);
|
||||
_trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, 0.99f);
|
||||
|
||||
// _mouth2 = "mmmm" shape
|
||||
// _mouth3 = "funnel" shape
|
||||
// _mouth4 = "smile" shape
|
||||
const float FUNNEL_PERIOD = 0.985f;
|
||||
const float FUNNEL_RANDOM_PERIOD = 0.01f;
|
||||
const float MMMM_POWER = 0.25f;
|
||||
const float MMMM_PERIOD = 0.91f;
|
||||
const float MMMM_RANDOM_PERIOD = 0.15f;
|
||||
const float SMILE_PERIOD = 0.925f;
|
||||
const float SMILE_RANDOM_PERIOD = 0.05f;
|
||||
|
||||
_mouth3 = glm::mix(_audioJawOpen, _mouth3, FUNNEL_PERIOD + randFloat() * FUNNEL_RANDOM_PERIOD);
|
||||
_mouth2 = glm::mix(_audioJawOpen * MMMM_POWER, _mouth2, MMMM_PERIOD + randFloat() * MMMM_RANDOM_PERIOD);
|
||||
_mouth4 = glm::mix(_audioJawOpen, _mouth4, SMILE_PERIOD + randFloat() * SMILE_RANDOM_PERIOD);
|
||||
// Advance time at a rate proportional to loudness, and move the mouth shapes through
|
||||
// a cycle at differing speeds to create a continuous random blend of shapes.
|
||||
_mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT;
|
||||
_mouth2 = (sinf(_mouthTime * MMMM_SPEED) + 1.0f) * MMMM_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
_mouth3 = (sinf(_mouthTime * FUNNEL_SPEED) + 1.0f) * FUNNEL_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
_mouth4 = (sinf(_mouthTime * SMILE_SPEED) + 1.0f) * SMILE_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
}
|
||||
|
||||
void Head::applyEyelidOffset(glm::quat headOrientation) {
|
||||
|
|
|
@ -124,9 +124,11 @@ private:
|
|||
float _longTermAverageLoudness;
|
||||
float _audioAttack;
|
||||
float _audioJawOpen;
|
||||
float _trailingAudioJawOpen;
|
||||
float _mouth2;
|
||||
float _mouth3;
|
||||
float _mouth4;
|
||||
float _mouthTime;
|
||||
bool _renderLookatVectors;
|
||||
bool _renderLookatTarget;
|
||||
glm::vec3 _saccade;
|
||||
|
|
|
@ -204,7 +204,7 @@ void PreferencesDialog::loadPreferences() {
|
|||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS());
|
||||
ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS());
|
||||
ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get<AvatarManager>()->getRenderDistanceHighLimit());
|
||||
ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get<AvatarManager>()->getRenderDistanceInverseHighLimit());
|
||||
}
|
||||
|
||||
void PreferencesDialog::savePreferences() {
|
||||
|
@ -295,5 +295,5 @@ void PreferencesDialog::savePreferences() {
|
|||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value());
|
||||
lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value());
|
||||
DependencyManager::get<AvatarManager>()->setRenderDistanceHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value());
|
||||
DependencyManager::get<AvatarManager>()->setRenderDistanceInverseHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value());
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "AnimPose.h"
|
||||
#include <GLMHelpers.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
||||
glm::quat(),
|
||||
|
@ -18,7 +19,9 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
|||
|
||||
AnimPose::AnimPose(const glm::mat4& mat) {
|
||||
scale = extractScale(mat);
|
||||
rot = glmExtractRotation(mat);
|
||||
// quat_cast doesn't work so well with scaled matrices, so cancel it out.
|
||||
glm::mat4 tmp = glm::scale(mat, 1.0f / scale);
|
||||
rot = glm::normalize(glm::quat_cast(tmp));
|
||||
trans = extractTranslation(mat);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <QCoreApplication>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <gl/GlWindow.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <QMainWindow>
|
||||
#include <QGLWidget>
|
||||
#include <GLMHelpers.h>
|
||||
#include <gl/GlWindow.h>
|
||||
#include <QEvent>
|
||||
#include <QResizeEvent>
|
||||
|
||||
|
|
|
@ -8,15 +8,6 @@
|
|||
|
||||
#include "InterleavedStereoDisplayPlugin.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
#include <gl/GlWindow.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <MatrixStack.h>
|
||||
|
||||
#include <gpu/GLBackend.h>
|
||||
|
||||
static const char * INTERLEAVED_TEXTURED_VS = R"VS(#version 410 core
|
||||
#pragma line __LINE__
|
||||
|
||||
|
@ -81,4 +72,4 @@ void InterleavedStereoDisplayPlugin::display(
|
|||
_program->Bind();
|
||||
Uniform<ivec2>(*_program, "textureSize").SetValue(sceneSize);
|
||||
WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,7 @@
|
|||
//
|
||||
|
||||
#include "SideBySideStereoDisplayPlugin.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QScreen>
|
||||
|
||||
#include <gl/GlWindow.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <MatrixStack.h>
|
||||
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo");
|
||||
|
||||
|
|
|
@ -311,6 +311,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit);
|
||||
}
|
||||
|
||||
// if any part of our entity was sent, call trackSend
|
||||
if (appendState != OctreeElement::NONE) {
|
||||
params.trackSend(getID(), getLastEdited());
|
||||
}
|
||||
|
||||
return appendState;
|
||||
}
|
||||
|
||||
|
|
|
@ -790,7 +790,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
//
|
||||
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer) {
|
||||
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
|
||||
|
||||
// FIXME - remove non-compressed OctreePacketData and handle compressed edit packets
|
||||
OctreePacketData ourDataPacket(buffer.size(), false); // create a packetData object to add out packet details too.
|
||||
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
|
||||
|
||||
bool success = true; // assume the best
|
||||
|
|
|
@ -257,7 +257,7 @@ inline glmQuat glmQuat_convertFromScriptValue(const QScriptValue& v, bool& isVal
|
|||
}
|
||||
|
||||
inline xColor xColor_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
|
||||
xColor newValue;
|
||||
xColor newValue { 255, 255, 255 };
|
||||
isValid = false; /// assume it can't be converted
|
||||
QScriptValue r = v.property("red");
|
||||
QScriptValue g = v.property("green");
|
||||
|
|
|
@ -87,6 +87,11 @@ public:
|
|||
_recycler = recycler;
|
||||
}
|
||||
|
||||
size_t depth() {
|
||||
Lock lock(_mutex);
|
||||
return _submits.size();
|
||||
}
|
||||
|
||||
// Submit a new resource from the producer context
|
||||
// returns the number of prior submissions that were
|
||||
// never consumed before becoming available.
|
||||
|
@ -124,7 +129,7 @@ public:
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// If fetch returns a non-zero value, it's the responsibility of the
|
||||
// client to release it at some point
|
||||
void release(T t, GLsync readSync = 0) {
|
||||
|
@ -175,6 +180,7 @@ private:
|
|||
|
||||
// May be called on any thread, but must be inside a locked section
|
||||
void pop(Deque& deque) {
|
||||
Lock lock(_mutex);
|
||||
auto& item = deque.front();
|
||||
_trash.push_front(item);
|
||||
deque.pop_front();
|
||||
|
|
|
@ -1,15 +1,37 @@
|
|||
#include "GLHelpers.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
QSurfaceFormat getDefaultOpenGlSurfaceFormat() {
|
||||
QSurfaceFormat format;
|
||||
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
|
||||
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
|
||||
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
|
||||
format.setVersion(4, 1);
|
||||
#include <QtGui/QSurfaceFormat>
|
||||
#include <QtOpenGL/QGL>
|
||||
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
||||
static QSurfaceFormat format;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
|
||||
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
|
||||
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
|
||||
format.setVersion(4, 1);
|
||||
#ifdef DEBUG
|
||||
format.setOption(QSurfaceFormat::DebugContext);
|
||||
format.setOption(QSurfaceFormat::DebugContext);
|
||||
#endif
|
||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
return format;
|
||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
});
|
||||
return format;
|
||||
}
|
||||
|
||||
const QGLFormat& getDefaultGLFormat() {
|
||||
// Specify an OpenGL 3.3 format using the Core profile.
|
||||
// That is, no old-school fixed pipeline functionality
|
||||
static QGLFormat glFormat;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
glFormat.setVersion(4, 1);
|
||||
glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0
|
||||
glFormat.setSampleBuffers(false);
|
||||
glFormat.setDepth(false);
|
||||
glFormat.setStencil(false);
|
||||
});
|
||||
return glFormat;
|
||||
}
|
||||
|
|
|
@ -10,14 +10,15 @@
|
|||
#ifndef hifi_GLHelpers_h
|
||||
#define hifi_GLHelpers_h
|
||||
|
||||
#include <QSurfaceFormat>
|
||||
|
||||
// 16 bits of depth precision
|
||||
#define DEFAULT_GL_DEPTH_BUFFER_BITS 16
|
||||
// 8 bits of stencil buffer (typically you really only need 1 bit for functionality
|
||||
// but GL implementations usually just come with buffer sizes in multiples of 8)
|
||||
#define DEFAULT_GL_STENCIL_BUFFER_BITS 8
|
||||
|
||||
QSurfaceFormat getDefaultOpenGlSurfaceFormat();
|
||||
class QSurfaceFormat;
|
||||
class QGLFormat;
|
||||
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
|
||||
const QGLFormat& getDefaultGLFormat();
|
||||
#endif
|
||||
|
|
114
libraries/gl/src/gl/GLWidget.cpp
Normal file
114
libraries/gl/src/gl/GLWidget.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015/12/03
|
||||
// Derived from interface/src/GLCanvas.cpp created by Stephen Birarda on 8/14/13.
|
||||
// Copyright 2013-2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "GLWidget.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include "GLHelpers.h"
|
||||
|
||||
GLWidget::GLWidget() : QGLWidget(getDefaultGLFormat()) {
|
||||
#ifdef Q_OS_LINUX
|
||||
// Cause GLWidget::eventFilter to be called.
|
||||
// It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux.
|
||||
qApp->installEventFilter(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
int GLWidget::getDeviceWidth() const {
|
||||
return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
int GLWidget::getDeviceHeight() const {
|
||||
return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
void GLWidget::initializeGL() {
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAcceptDrops(true);
|
||||
// Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate.
|
||||
setAutoBufferSwap(false);
|
||||
}
|
||||
|
||||
void GLWidget::paintEvent(QPaintEvent* event) {
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void GLWidget::resizeEvent(QResizeEvent* event) {
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
||||
bool GLWidget::event(QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::Resize:
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::DragEnter:
|
||||
case QEvent::Drop:
|
||||
if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QGLWidget::event(event);
|
||||
}
|
||||
|
||||
|
||||
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
|
||||
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
|
||||
// receive keyPress events for the Alt (and Meta) key in a reliable manner.
|
||||
//
|
||||
// This filter catches events before QMenuBar can steal the keyboard focus.
|
||||
// The idea was borrowed from
|
||||
// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html
|
||||
|
||||
bool GLWidget::eventFilter(QObject*, QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::ShortcutOverride:
|
||||
{
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
keyPressEvent(keyEvent);
|
||||
} else if (event->type() == QEvent::KeyRelease) {
|
||||
keyReleaseEvent(keyEvent);
|
||||
} else {
|
||||
QGLWidget::event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
36
libraries/gl/src/gl/GLWidget.h
Normal file
36
libraries/gl/src/gl/GLWidget.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/12/03
|
||||
// Derived from interface/src/GLCanvas.h created by Stephen Birarda on 8/14/13.
|
||||
// Copyright 2013-2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_GLWidget_h
|
||||
#define hifi_GLWidget_h
|
||||
|
||||
#include <QGLWidget>
|
||||
|
||||
/// customized canvas that simply forwards requests/events to the singleton application
|
||||
class GLWidget : public QGLWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GLWidget();
|
||||
int getDeviceWidth() const;
|
||||
int getDeviceHeight() const;
|
||||
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
|
||||
|
||||
protected:
|
||||
virtual void initializeGL() override;
|
||||
virtual bool event(QEvent* event) override;
|
||||
virtual void paintEvent(QPaintEvent* event) override;
|
||||
virtual void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private slots:
|
||||
virtual bool eventFilter(QObject*, QEvent* event) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_GLCanvas_h
|
|
@ -6,17 +6,18 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "GlWindow.h"
|
||||
#include "GLWindow.h"
|
||||
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtGui/QOpenGLDebugLogger>
|
||||
|
||||
#include "GLHelpers.h"
|
||||
|
||||
GlWindow::GlWindow(QOpenGLContext* shareContext) : GlWindow(getDefaultOpenGlSurfaceFormat(), shareContext) {
|
||||
void GLWindow::createContext(QOpenGLContext* shareContext) {
|
||||
createContext(getDefaultOpenGLSurfaceFormat(), shareContext);
|
||||
}
|
||||
|
||||
GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) {
|
||||
void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) {
|
||||
setSurfaceType(QSurface::OpenGLSurface);
|
||||
setFormat(format);
|
||||
_context = new QOpenGLContext;
|
||||
|
@ -27,13 +28,15 @@ GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) {
|
|||
_context->create();
|
||||
}
|
||||
|
||||
GlWindow::~GlWindow() {
|
||||
_context->doneCurrent();
|
||||
_context->deleteLater();
|
||||
_context = nullptr;
|
||||
GLWindow::~GLWindow() {
|
||||
if (_context) {
|
||||
_context->doneCurrent();
|
||||
_context->deleteLater();
|
||||
_context = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool GlWindow::makeCurrent() {
|
||||
bool GLWindow::makeCurrent() {
|
||||
bool makeCurrentResult = _context->makeCurrent(this);
|
||||
Q_ASSERT(makeCurrentResult);
|
||||
|
||||
|
@ -49,11 +52,16 @@ bool GlWindow::makeCurrent() {
|
|||
return makeCurrentResult;
|
||||
}
|
||||
|
||||
void GlWindow::doneCurrent() {
|
||||
void GLWindow::doneCurrent() {
|
||||
_context->doneCurrent();
|
||||
}
|
||||
|
||||
void GlWindow::swapBuffers() {
|
||||
void GLWindow::swapBuffers() {
|
||||
_context->swapBuffers(this);
|
||||
}
|
||||
|
||||
QOpenGLContext* GLWindow::context() const {
|
||||
return _context;
|
||||
}
|
||||
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_GlWindow_h
|
||||
#define hifi_GlWindow_h
|
||||
#ifndef hifi_GLWindow_h
|
||||
#define hifi_GLWindow_h
|
||||
|
||||
#include <mutex>
|
||||
#include <QtGui/QWindow>
|
||||
|
@ -16,14 +16,15 @@
|
|||
class QOpenGLContext;
|
||||
class QOpenGLDebugLogger;
|
||||
|
||||
class GlWindow : public QWindow {
|
||||
class GLWindow : public QWindow {
|
||||
public:
|
||||
GlWindow(QOpenGLContext* shareContext = nullptr);
|
||||
GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr);
|
||||
virtual ~GlWindow();
|
||||
virtual ~GLWindow();
|
||||
void createContext(QOpenGLContext* shareContext = nullptr);
|
||||
void createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr);
|
||||
bool makeCurrent();
|
||||
void doneCurrent();
|
||||
void swapBuffers();
|
||||
QOpenGLContext* context() const;
|
||||
private:
|
||||
std::once_flag _reportOnce;
|
||||
QOpenGLContext* _context{ nullptr };
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// OffscreenGlCanvas.cpp
|
||||
// OffscreenGLCanvas.cpp
|
||||
// interface/src/renderer
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2014/04/09.
|
||||
|
@ -10,7 +10,7 @@
|
|||
//
|
||||
|
||||
|
||||
#include "OffscreenGlCanvas.h"
|
||||
#include "OffscreenGLCanvas.h"
|
||||
|
||||
#include <QtGui/QOffscreenSurface>
|
||||
#include <QtGui/QOpenGLDebugLogger>
|
||||
|
@ -18,10 +18,10 @@
|
|||
|
||||
#include "GLHelpers.h"
|
||||
|
||||
OffscreenGlCanvas::OffscreenGlCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface){
|
||||
OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface){
|
||||
}
|
||||
|
||||
OffscreenGlCanvas::~OffscreenGlCanvas() {
|
||||
OffscreenGLCanvas::~OffscreenGLCanvas() {
|
||||
#ifdef DEBUG
|
||||
if (_logger) {
|
||||
makeCurrent();
|
||||
|
@ -32,12 +32,12 @@ OffscreenGlCanvas::~OffscreenGlCanvas() {
|
|||
_context->doneCurrent();
|
||||
}
|
||||
|
||||
void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) {
|
||||
void OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
||||
if (nullptr != sharedContext) {
|
||||
sharedContext->doneCurrent();
|
||||
_context->setShareContext(sharedContext);
|
||||
}
|
||||
_context->setFormat(getDefaultOpenGlSurfaceFormat());
|
||||
_context->setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
_context->create();
|
||||
|
||||
_offscreenSurface->setFormat(_context->format());
|
||||
|
@ -45,7 +45,7 @@ void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) {
|
|||
|
||||
}
|
||||
|
||||
bool OffscreenGlCanvas::makeCurrent() {
|
||||
bool OffscreenGLCanvas::makeCurrent() {
|
||||
bool result = _context->makeCurrent(_offscreenSurface);
|
||||
Q_ASSERT(result);
|
||||
|
||||
|
@ -72,7 +72,7 @@ bool OffscreenGlCanvas::makeCurrent() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void OffscreenGlCanvas::doneCurrent() {
|
||||
void OffscreenGLCanvas::doneCurrent() {
|
||||
_context->doneCurrent();
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// OffscreenGlCanvas.h
|
||||
// OffscreenGLCanvas.h
|
||||
// interface/src/renderer
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2014/04/09.
|
||||
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#pragma once
|
||||
#ifndef hifi_OffscreenGlCanvas_h
|
||||
#define hifi_OffscreenGlCanvas_h
|
||||
#ifndef hifi_OffscreenGLCanvas_h
|
||||
#define hifi_OffscreenGLCanvas_h
|
||||
|
||||
#include <mutex>
|
||||
#include <QObject>
|
||||
|
@ -19,10 +19,10 @@ class QOpenGLContext;
|
|||
class QOffscreenSurface;
|
||||
class QOpenGLDebugLogger;
|
||||
|
||||
class OffscreenGlCanvas : public QObject {
|
||||
class OffscreenGLCanvas : public QObject {
|
||||
public:
|
||||
OffscreenGlCanvas();
|
||||
~OffscreenGlCanvas();
|
||||
OffscreenGLCanvas();
|
||||
~OffscreenGLCanvas();
|
||||
void create(QOpenGLContext* sharedContext = nullptr);
|
||||
bool makeCurrent();
|
||||
void doneCurrent();
|
||||
|
@ -40,4 +40,4 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
#endif // hifi_OffscreenGlCanvas_h
|
||||
#endif // hifi_OffscreenGLCanvas_h
|
|
@ -23,7 +23,7 @@
|
|||
#include <NumericalConstants.h>
|
||||
|
||||
#include "GLEscrow.h"
|
||||
#include "OffscreenGlCanvas.h"
|
||||
#include "OffscreenGLCanvas.h"
|
||||
|
||||
// FIXME move to threaded rendering with Qt 5.5
|
||||
//#define QML_THREADED
|
||||
|
@ -64,12 +64,12 @@ static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
|
|||
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
|
||||
#endif
|
||||
|
||||
class OffscreenQmlRenderer : public OffscreenGlCanvas {
|
||||
class OffscreenQmlRenderer : public OffscreenGLCanvas {
|
||||
friend class OffscreenQmlSurface;
|
||||
public:
|
||||
|
||||
OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
||||
OffscreenGlCanvas::create(shareContext);
|
||||
OffscreenGLCanvas::create(shareContext);
|
||||
#ifdef QML_THREADED
|
||||
// Qt 5.5
|
||||
_renderControl->prepareThread(_renderThread);
|
||||
|
|
|
@ -1,542 +0,0 @@
|
|||
//
|
||||
// CoverageMap.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeLogging.h"
|
||||
#include "CoverageMap.h"
|
||||
|
||||
int CoverageMap::_mapCount = 0;
|
||||
int CoverageMap::_checkMapRootCalls = 0;
|
||||
int CoverageMap::_notAllInView = 0;
|
||||
bool CoverageMap::wantDebugging = false;
|
||||
|
||||
const int MAX_POLYGONS_PER_REGION = 50;
|
||||
|
||||
const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.0f,-1.0f), glm::vec2(2.0f,2.0f));
|
||||
|
||||
// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space.
|
||||
//
|
||||
// (0,0) (windowWidth, 0)
|
||||
// -1,1 1,1
|
||||
// +-----------------------+
|
||||
// | | |
|
||||
// | | |
|
||||
// | -1,0 | |
|
||||
// |-----------+-----------|
|
||||
// | 0,0 |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// +-----------------------+
|
||||
// -1,-1 1,-1
|
||||
// (0,windowHeight) (windowWidth,windowHeight)
|
||||
//
|
||||
|
||||
// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide
|
||||
// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically
|
||||
// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough"
|
||||
// then we can calculate a reasonable polygon area
|
||||
const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500;
|
||||
const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10;
|
||||
const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS);
|
||||
const float CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) *
|
||||
(TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS);
|
||||
|
||||
CoverageMap::CoverageMap(BoundingBox boundingBox, bool isRoot, bool managePolygons) :
|
||||
_isRoot(isRoot),
|
||||
_myBoundingBox(boundingBox),
|
||||
_managePolygons(managePolygons),
|
||||
_topHalf (boundingBox.topHalf() , false, managePolygons, TOP_HALF ),
|
||||
_bottomHalf (boundingBox.bottomHalf(), false, managePolygons, BOTTOM_HALF ),
|
||||
_leftHalf (boundingBox.leftHalf() , false, managePolygons, LEFT_HALF ),
|
||||
_rightHalf (boundingBox.rightHalf() , false, managePolygons, RIGHT_HALF ),
|
||||
_remainder (boundingBox, isRoot, managePolygons, REMAINDER )
|
||||
{
|
||||
_mapCount++;
|
||||
init();
|
||||
};
|
||||
|
||||
CoverageMap::~CoverageMap() {
|
||||
erase();
|
||||
};
|
||||
|
||||
void CoverageMap::printStats() {
|
||||
qCDebug(octree, "CoverageMap::printStats()...");
|
||||
qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE);
|
||||
qCDebug(octree, "_mapCount=%d",_mapCount);
|
||||
qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls);
|
||||
qCDebug(octree, "_notAllInView=%d",_notAllInView);
|
||||
qCDebug(octree, "_maxPolygonsUsed=%d",CoverageRegion::_maxPolygonsUsed);
|
||||
qCDebug(octree, "_totalPolygons=%d",CoverageRegion::_totalPolygons);
|
||||
qCDebug(octree, "_occlusionTests=%d",CoverageRegion::_occlusionTests);
|
||||
qCDebug(octree, "_regionSkips=%d",CoverageRegion::_regionSkips);
|
||||
qCDebug(octree, "_tooSmallSkips=%d",CoverageRegion::_tooSmallSkips);
|
||||
qCDebug(octree, "_regionFullSkips=%d",CoverageRegion::_regionFullSkips);
|
||||
qCDebug(octree, "_outOfOrderPolygon=%d",CoverageRegion::_outOfOrderPolygon);
|
||||
qCDebug(octree, "_clippedPolygons=%d",CoverageRegion::_clippedPolygons);
|
||||
}
|
||||
|
||||
void CoverageMap::erase() {
|
||||
// tell our regions to erase()
|
||||
_topHalf.erase();
|
||||
_bottomHalf.erase();
|
||||
_leftHalf.erase();
|
||||
_rightHalf.erase();
|
||||
_remainder.erase();
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (_childMaps[i]) {
|
||||
delete _childMaps[i];
|
||||
_childMaps[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isRoot && wantDebugging) {
|
||||
qCDebug(octree, "CoverageMap last to be deleted...");
|
||||
printStats();
|
||||
|
||||
CoverageRegion::_maxPolygonsUsed = 0;
|
||||
CoverageRegion::_totalPolygons = 0;
|
||||
CoverageRegion::_occlusionTests = 0;
|
||||
CoverageRegion::_regionSkips = 0;
|
||||
CoverageRegion::_tooSmallSkips = 0;
|
||||
CoverageRegion::_regionFullSkips = 0;
|
||||
CoverageRegion::_outOfOrderPolygon = 0;
|
||||
CoverageRegion::_clippedPolygons = 0;
|
||||
_mapCount = 0;
|
||||
_checkMapRootCalls = 0;
|
||||
_notAllInView = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverageMap::init() {
|
||||
memset(_childMaps,0,sizeof(_childMaps));
|
||||
}
|
||||
|
||||
// 0 = bottom, right
|
||||
// 1 = bottom, left
|
||||
// 2 = top, right
|
||||
// 3 = top, left
|
||||
BoundingBox CoverageMap::getChildBoundingBox(int childIndex) {
|
||||
const int LEFT_BIT = 1;
|
||||
const int TOP_BIT = 2;
|
||||
// initialize to our corner, and half our size
|
||||
BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f);
|
||||
// if our "left" bit is set, then add size.x to the corner
|
||||
if ((childIndex & LEFT_BIT) == LEFT_BIT) {
|
||||
result.corner.x += result.size.x;
|
||||
}
|
||||
// if our "top" bit is set, then add size.y to the corner
|
||||
if ((childIndex & TOP_BIT) == TOP_BIT) {
|
||||
result.corner.y += result.size.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int CoverageMap::getPolygonCount() const {
|
||||
return (_topHalf.getPolygonCount() +
|
||||
_bottomHalf.getPolygonCount() +
|
||||
_leftHalf.getPolygonCount() +
|
||||
_rightHalf.getPolygonCount() +
|
||||
_remainder.getPolygonCount());
|
||||
}
|
||||
|
||||
OctreeProjectedPolygon* CoverageMap::getPolygon(int index) const {
|
||||
int base = 0;
|
||||
if ((index - base) < _topHalf.getPolygonCount()) {
|
||||
return _topHalf.getPolygon((index - base));
|
||||
}
|
||||
base += _topHalf.getPolygonCount();
|
||||
|
||||
if ((index - base) < _bottomHalf.getPolygonCount()) {
|
||||
return _bottomHalf.getPolygon((index - base));
|
||||
}
|
||||
base += _bottomHalf.getPolygonCount();
|
||||
|
||||
if ((index - base) < _leftHalf.getPolygonCount()) {
|
||||
return _leftHalf.getPolygon((index - base));
|
||||
}
|
||||
base += _leftHalf.getPolygonCount();
|
||||
|
||||
if ((index - base) < _rightHalf.getPolygonCount()) {
|
||||
return _rightHalf.getPolygon((index - base));
|
||||
}
|
||||
base += _rightHalf.getPolygonCount();
|
||||
|
||||
if ((index - base) < _remainder.getPolygonCount()) {
|
||||
return _remainder.getPolygon((index - base));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
|
||||
CoverageMapStorageResult CoverageMap::checkMap(OctreeProjectedPolygon* polygon, bool storeIt) {
|
||||
|
||||
if (_isRoot) {
|
||||
_checkMapRootCalls++;
|
||||
}
|
||||
|
||||
// short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is
|
||||
// not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later.
|
||||
if (!polygon->getAllInView()) {
|
||||
_notAllInView++;
|
||||
return DOESNT_FIT;
|
||||
}
|
||||
|
||||
BoundingBox polygonBox(polygon->getBoundingBox());
|
||||
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
|
||||
|
||||
CoverageMapStorageResult result = NOT_STORED;
|
||||
CoverageRegion* storeIn = &_remainder;
|
||||
|
||||
// Check each half of the box independently
|
||||
const bool useRegions = true; // for now we will continue to use regions
|
||||
if (useRegions) {
|
||||
if (_topHalf.contains(polygonBox)) {
|
||||
result = _topHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_topHalf;
|
||||
} else if (_bottomHalf.contains(polygonBox)) {
|
||||
result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_bottomHalf;
|
||||
} else if (_leftHalf.contains(polygonBox)) {
|
||||
result = _leftHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_leftHalf;
|
||||
} else if (_rightHalf.contains(polygonBox)) {
|
||||
result = _rightHalf.checkRegion(polygon, polygonBox, storeIt);
|
||||
storeIn = &_rightHalf;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got this far, there are one of two possibilities, either a polygon doesn't fit
|
||||
// in one of the halves, or it did fit, but it wasn't occluded by anything only in that
|
||||
// half. In either of these cases, we want to check our remainder region to see if its
|
||||
// occluded by anything there
|
||||
if (!(result == STORED || result == OCCLUDED)) {
|
||||
result = _remainder.checkRegion(polygon, polygonBox, storeIt);
|
||||
}
|
||||
|
||||
// It's possible that this first set of checks might have resulted in an out of order polygon
|
||||
// in which case we just return..
|
||||
if (result == STORED || result == OCCLUDED) {
|
||||
|
||||
/*
|
||||
if (result == STORED)
|
||||
qCDebug(octree, "CoverageMap2::checkMap()... STORED\n");
|
||||
else
|
||||
qCDebug(octree, "CoverageMap2::checkMap()... OCCLUDED\n");
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// if we made it here, then it means the polygon being stored is not occluded
|
||||
// at this level of the quad tree, so we can continue to insert it into the map.
|
||||
// First we check to see if it fits in any of our sub maps
|
||||
const bool useChildMaps = true; // for now we will continue to use child maps
|
||||
if (useChildMaps) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
BoundingBox childMapBoundingBox = getChildBoundingBox(i);
|
||||
if (childMapBoundingBox.contains(polygon->getBoundingBox())) {
|
||||
// if no child map exists yet, then create it
|
||||
if (!_childMaps[i]) {
|
||||
_childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons);
|
||||
}
|
||||
result = _childMaps[i]->checkMap(polygon, storeIt);
|
||||
|
||||
/*
|
||||
switch (result) {
|
||||
case STORED:
|
||||
qCDebug(octree, "checkMap() = STORED\n");
|
||||
break;
|
||||
case NOT_STORED:
|
||||
qCDebug(octree, "checkMap() = NOT_STORED\n");
|
||||
break;
|
||||
case OCCLUDED:
|
||||
qCDebug(octree, "checkMap() = OCCLUDED\n");
|
||||
break;
|
||||
default:
|
||||
qCDebug(octree, "checkMap() = ????? \n");
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we got this far, then the polygon is in our bounding box, but doesn't fit in
|
||||
// any of our child bounding boxes, so we should add it here.
|
||||
if (storeIt) {
|
||||
if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) {
|
||||
if (storeIn->getPolygonCount() < MAX_POLYGONS_PER_REGION) {
|
||||
storeIn->storeInArray(polygon);
|
||||
return STORED;
|
||||
} else {
|
||||
CoverageRegion::_regionFullSkips++;
|
||||
return NOT_STORED;
|
||||
}
|
||||
} else {
|
||||
CoverageRegion::_tooSmallSkips++;
|
||||
return NOT_STORED;
|
||||
}
|
||||
} else {
|
||||
return NOT_STORED;
|
||||
}
|
||||
}
|
||||
return DOESNT_FIT;
|
||||
}
|
||||
|
||||
|
||||
CoverageRegion::CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons, RegionName regionName) :
|
||||
_isRoot(isRoot),
|
||||
_myBoundingBox(boundingBox),
|
||||
_managePolygons(managePolygons),
|
||||
_regionName(regionName)
|
||||
{
|
||||
init();
|
||||
};
|
||||
|
||||
CoverageRegion::~CoverageRegion() {
|
||||
erase();
|
||||
};
|
||||
|
||||
void CoverageRegion::init() {
|
||||
_polygonCount = 0;
|
||||
_polygonArraySize = 0;
|
||||
_polygons = NULL;
|
||||
_polygonDistances = NULL;
|
||||
_polygonSizes = NULL;
|
||||
}
|
||||
|
||||
|
||||
void CoverageRegion::erase() {
|
||||
|
||||
/**
|
||||
if (_polygonCount) {
|
||||
qCDebug(octree, "CoverageRegion::erase()...\n");
|
||||
qCDebug(octree, "_polygonCount=%d\n",_polygonCount);
|
||||
_myBoundingBox.printDebugDetails(getRegionName());
|
||||
//for (int i = 0; i < _polygonCount; i++) {
|
||||
// qCDebug(octree, "_polygons[%d]=",i);
|
||||
// _polygons[i]->getBoundingBox().printDebugDetails();
|
||||
//}
|
||||
}
|
||||
**/
|
||||
// If we're in charge of managing the polygons, then clean them up first
|
||||
if (_polygons && _managePolygons) {
|
||||
for (int i = 0; i < _polygonCount; i++) {
|
||||
delete _polygons[i];
|
||||
_polygons[i] = NULL; // do we need to do this?
|
||||
}
|
||||
}
|
||||
|
||||
// Now, clean up our local storage
|
||||
_polygonCount = 0;
|
||||
_polygonArraySize = 0;
|
||||
if (_polygons) {
|
||||
delete[] _polygons;
|
||||
_polygons = NULL;
|
||||
}
|
||||
if (_polygonDistances) {
|
||||
delete[] _polygonDistances;
|
||||
_polygonDistances = NULL;
|
||||
}
|
||||
if (_polygonSizes) {
|
||||
delete[] _polygonSizes;
|
||||
_polygonSizes = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverageRegion::growPolygonArray() {
|
||||
OctreeProjectedPolygon** newPolygons = new OctreeProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE];
|
||||
|
||||
|
||||
if (_polygons) {
|
||||
memcpy(newPolygons, _polygons, sizeof(OctreeProjectedPolygon*) * _polygonCount);
|
||||
delete[] _polygons;
|
||||
memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount);
|
||||
delete[] _polygonDistances;
|
||||
memcpy(newSizes, _polygonSizes, sizeof(float) * _polygonCount);
|
||||
delete[] _polygonSizes;
|
||||
}
|
||||
_polygons = newPolygons;
|
||||
_polygonDistances = newDistances;
|
||||
_polygonSizes = newSizes;
|
||||
_polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE;
|
||||
}
|
||||
|
||||
const char* CoverageRegion::getRegionName() const {
|
||||
switch (_regionName) {
|
||||
case TOP_HALF:
|
||||
return "TOP_HALF";
|
||||
case BOTTOM_HALF:
|
||||
return "BOTTOM_HALF";
|
||||
case LEFT_HALF:
|
||||
return "LEFT_HALF";
|
||||
case RIGHT_HALF:
|
||||
return "RIGHT_HALF";
|
||||
default:
|
||||
case REMAINDER:
|
||||
return "REMAINDER";
|
||||
}
|
||||
return "REMAINDER";
|
||||
}
|
||||
|
||||
int CoverageRegion::_maxPolygonsUsed = 0;
|
||||
int CoverageRegion::_totalPolygons = 0;
|
||||
int CoverageRegion::_occlusionTests = 0;
|
||||
int CoverageRegion::_regionSkips = 0;
|
||||
int CoverageRegion::_tooSmallSkips = 0;
|
||||
int CoverageRegion::_regionFullSkips = 0;
|
||||
int CoverageRegion::_outOfOrderPolygon = 0;
|
||||
int CoverageRegion::_clippedPolygons = 0;
|
||||
|
||||
|
||||
bool CoverageRegion::mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray) {
|
||||
for (int i = 0; i < _polygonCount; i++) {
|
||||
OctreeProjectedPolygon* otherPolygon = _polygons[i];
|
||||
if (otherPolygon->canMerge(*seed)) {
|
||||
otherPolygon->merge(*seed);
|
||||
|
||||
if (seedInArray) {
|
||||
int* IGNORED_ADDRESS = NULL;
|
||||
// remove this otherOtherPolygon for our polygon array
|
||||
_polygonCount = removeFromSortedArrays((void*)seed,
|
||||
(void**)_polygons, _polygonDistances, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
_totalPolygons--;
|
||||
}
|
||||
|
||||
// clean up
|
||||
if (_managePolygons) {
|
||||
delete seed;
|
||||
}
|
||||
|
||||
// Now run again using our newly merged polygon as the seed
|
||||
mergeItemsInArray(otherPolygon, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// just handles storage in the array, doesn't test for occlusion or
|
||||
// determining if this is the correct map to store in!
|
||||
void CoverageRegion::storeInArray(OctreeProjectedPolygon* polygon) {
|
||||
|
||||
_currentCoveredBounds.explandToInclude(polygon->getBoundingBox());
|
||||
|
||||
|
||||
// Before we actually store this polygon in the array, check to see if this polygon can be merged to any of the existing
|
||||
// polygons already in our array.
|
||||
if (mergeItemsInArray(polygon, false)) {
|
||||
return; // exit early
|
||||
}
|
||||
|
||||
// only after we attempt to merge!
|
||||
_totalPolygons++;
|
||||
|
||||
if (_polygonArraySize < _polygonCount + 1) {
|
||||
growPolygonArray();
|
||||
}
|
||||
|
||||
// As an experiment we're going to see if we get an improvement by storing the polygons in coverage area sorted order
|
||||
// this means the bigger polygons are earlier in the array. We should have a higher probability of being occluded earlier
|
||||
// in the list. We still check to see if the polygon is "in front" of the target polygon before we test occlusion. Since
|
||||
// sometimes things come out of order.
|
||||
const bool SORT_BY_SIZE = false;
|
||||
const int IGNORED = 0;
|
||||
int* IGNORED_ADDRESS = NULL;
|
||||
if (SORT_BY_SIZE) {
|
||||
// This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to
|
||||
// be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the
|
||||
// insertion point in this array, and shift the array accordingly
|
||||
float area = polygon->getBoundingBox().area();
|
||||
float reverseArea = 4.0f - area;
|
||||
_polygonCount = insertIntoSortedArrays((void*)polygon, reverseArea, IGNORED,
|
||||
(void**)_polygons, _polygonSizes, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
} else {
|
||||
_polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED,
|
||||
(void**)_polygons, _polygonDistances, IGNORED_ADDRESS,
|
||||
_polygonCount, _polygonArraySize);
|
||||
}
|
||||
|
||||
// Debugging and Optimization Tuning code.
|
||||
if (_polygonCount > _maxPolygonsUsed) {
|
||||
_maxPolygonsUsed = _polygonCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
CoverageMapStorageResult CoverageRegion::checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt) {
|
||||
|
||||
CoverageMapStorageResult result = DOESNT_FIT;
|
||||
|
||||
if (_isRoot || _myBoundingBox.contains(polygonBox)) {
|
||||
result = NOT_STORED; // if we got here, then we DO fit...
|
||||
|
||||
// only actually check the polygons if this polygon is in the covered bounds for this region
|
||||
if (!_currentCoveredBounds.contains(polygonBox)) {
|
||||
_regionSkips += _polygonCount;
|
||||
} else {
|
||||
// check to make sure this polygon isn't occluded by something at this level
|
||||
for (int i = 0; i < _polygonCount; i++) {
|
||||
OctreeProjectedPolygon* polygonAtThisLevel = _polygons[i];
|
||||
|
||||
// Check to make sure that the polygon in question is "behind" the polygon in the list
|
||||
// otherwise, we don't need to test it's occlusion (although, it means we've potentially
|
||||
// added an item previously that may be occluded??? Is that possible? Maybe not, because two
|
||||
// voxels can't have the exact same outline. So one occludes the other, they can't both occlude
|
||||
// each other.
|
||||
|
||||
_occlusionTests++;
|
||||
if (polygonAtThisLevel->occludes(*polygon)) {
|
||||
// if the polygonAtThisLevel is actually behind the one we're inserting, then we don't
|
||||
// want to report our inserted one as occluded, but we do want to add our inserted one.
|
||||
if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) {
|
||||
_outOfOrderPolygon++;
|
||||
if (storeIt) {
|
||||
if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) {
|
||||
if (getPolygonCount() < MAX_POLYGONS_PER_REGION) {
|
||||
storeInArray(polygon);
|
||||
return STORED;
|
||||
} else {
|
||||
CoverageRegion::_regionFullSkips++;
|
||||
return NOT_STORED;
|
||||
}
|
||||
} else {
|
||||
_tooSmallSkips++;
|
||||
return NOT_STORED;
|
||||
}
|
||||
} else {
|
||||
return NOT_STORED;
|
||||
}
|
||||
}
|
||||
// this polygon is occluded by a closer polygon, so don't store it, and let the caller know
|
||||
return OCCLUDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// CoverageMap.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CoverageMap_h
|
||||
#define hifi_CoverageMap_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "OctreeProjectedPolygon.h"
|
||||
|
||||
typedef enum {STORED, OCCLUDED, DOESNT_FIT, NOT_STORED} CoverageMapStorageResult;
|
||||
typedef enum {TOP_HALF, BOTTOM_HALF, LEFT_HALF, RIGHT_HALF, REMAINDER} RegionName;
|
||||
|
||||
class CoverageRegion {
|
||||
|
||||
public:
|
||||
|
||||
CoverageRegion(BoundingBox boundingBox, bool isRoot, bool managePolygons = true, RegionName regionName = REMAINDER);
|
||||
~CoverageRegion();
|
||||
|
||||
CoverageMapStorageResult checkRegion(OctreeProjectedPolygon* polygon, const BoundingBox& polygonBox, bool storeIt);
|
||||
void storeInArray(OctreeProjectedPolygon* polygon);
|
||||
|
||||
bool contains(const BoundingBox& box) const { return _myBoundingBox.contains(box); };
|
||||
void erase(); // erase the coverage region
|
||||
|
||||
static int _maxPolygonsUsed;
|
||||
static int _totalPolygons;
|
||||
static int _occlusionTests;
|
||||
static int _regionSkips;
|
||||
static int _tooSmallSkips;
|
||||
static int _regionFullSkips;
|
||||
static int _outOfOrderPolygon;
|
||||
static int _clippedPolygons;
|
||||
|
||||
|
||||
const char* getRegionName() const;
|
||||
|
||||
int getPolygonCount() const { return _polygonCount; };
|
||||
OctreeProjectedPolygon* getPolygon(int index) const { return _polygons[index]; };
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
|
||||
BoundingBox _myBoundingBox;
|
||||
BoundingBox _currentCoveredBounds; // area in this region currently covered by some polygon
|
||||
bool _managePolygons; // will the coverage map delete the polygons on destruct
|
||||
RegionName _regionName;
|
||||
int _polygonCount; // how many polygons at this level
|
||||
int _polygonArraySize; // how much room is there to store polygons at this level
|
||||
OctreeProjectedPolygon** _polygons;
|
||||
|
||||
// we will use one or the other of these depending on settings in the code.
|
||||
float* _polygonDistances;
|
||||
float* _polygonSizes;
|
||||
void growPolygonArray();
|
||||
static const int DEFAULT_GROW_SIZE = 100;
|
||||
|
||||
bool mergeItemsInArray(OctreeProjectedPolygon* seed, bool seedInArray);
|
||||
|
||||
};
|
||||
|
||||
class CoverageMap {
|
||||
|
||||
public:
|
||||
static const int NUMBER_OF_CHILDREN = 4;
|
||||
static const bool NOT_ROOT=false;
|
||||
static const bool IS_ROOT=true;
|
||||
static const BoundingBox ROOT_BOUNDING_BOX;
|
||||
static const float MINIMUM_POLYGON_AREA_TO_STORE;
|
||||
|
||||
CoverageMap(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, bool managePolygons = true);
|
||||
~CoverageMap();
|
||||
|
||||
CoverageMapStorageResult checkMap(OctreeProjectedPolygon* polygon, bool storeIt = true);
|
||||
|
||||
BoundingBox getChildBoundingBox(int childIndex);
|
||||
|
||||
void erase(); // erase the coverage map
|
||||
void printStats();
|
||||
|
||||
static bool wantDebugging;
|
||||
|
||||
int getPolygonCount() const;
|
||||
OctreeProjectedPolygon* getPolygon(int index) const;
|
||||
CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; };
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT
|
||||
BoundingBox _myBoundingBox;
|
||||
CoverageMap* _childMaps[NUMBER_OF_CHILDREN];
|
||||
bool _managePolygons; // will the coverage map delete the polygons on destruct
|
||||
|
||||
// We divide the map into 5 regions representing each possible half of the map, and the whole map
|
||||
// this allows us to keep the list of polygons shorter
|
||||
CoverageRegion _topHalf;
|
||||
CoverageRegion _bottomHalf;
|
||||
CoverageRegion _leftHalf;
|
||||
CoverageRegion _rightHalf;
|
||||
CoverageRegion _remainder;
|
||||
|
||||
static int _mapCount;
|
||||
static int _checkMapRootCalls;
|
||||
static int _notAllInView;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_CoverageMap_h
|
|
@ -1,251 +0,0 @@
|
|||
//
|
||||
// CoverageMapV2.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreeLogging.h"
|
||||
#include "CoverageMapV2.h"
|
||||
|
||||
int CoverageMapV2::_mapCount = 0;
|
||||
int CoverageMapV2::_checkMapRootCalls = 0;
|
||||
int CoverageMapV2::_notAllInView = 0;
|
||||
bool CoverageMapV2::wantDebugging = false;
|
||||
|
||||
const BoundingBox CoverageMapV2::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.0f,-1.0f), glm::vec2(2.0f,2.0f));
|
||||
|
||||
// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space.
|
||||
//
|
||||
// (0,0) (windowWidth, 0)
|
||||
// -1,1 1,1
|
||||
// +-----------------------+
|
||||
// | | |
|
||||
// | | |
|
||||
// | -1,0 | |
|
||||
// |-----------+-----------|
|
||||
// | 0,0 |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// +-----------------------+
|
||||
// -1,-1 1,-1
|
||||
// (0,windowHeight) (windowWidth,windowHeight)
|
||||
//
|
||||
|
||||
// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide
|
||||
// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically
|
||||
// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough"
|
||||
// then we can calculate a reasonable polygon area
|
||||
const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500;
|
||||
const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10;
|
||||
const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS);
|
||||
const float CoverageMapV2::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) *
|
||||
(TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS);
|
||||
const float CoverageMapV2::NOT_COVERED = FLT_MAX;
|
||||
const float CoverageMapV2::MINIMUM_OCCLUSION_CHECK_AREA = MINIMUM_POLYGON_AREA_TO_STORE/10.0f; // one quarter the size of poly
|
||||
|
||||
|
||||
CoverageMapV2::CoverageMapV2(BoundingBox boundingBox, bool isRoot, bool isCovered, float coverageDistance) :
|
||||
_isRoot(isRoot),
|
||||
_myBoundingBox(boundingBox),
|
||||
_isCovered(isCovered),
|
||||
_coveredDistance(coverageDistance)
|
||||
{
|
||||
_mapCount++;
|
||||
init();
|
||||
};
|
||||
|
||||
CoverageMapV2::~CoverageMapV2() {
|
||||
erase();
|
||||
};
|
||||
|
||||
void CoverageMapV2::erase() {
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (_childMaps[i]) {
|
||||
delete _childMaps[i];
|
||||
_childMaps[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isRoot && wantDebugging) {
|
||||
qCDebug(octree, "CoverageMapV2 last to be deleted...");
|
||||
qCDebug(octree, "MINIMUM_POLYGON_AREA_TO_STORE=%f", (double)MINIMUM_POLYGON_AREA_TO_STORE);
|
||||
qCDebug(octree, "_mapCount=%d",_mapCount);
|
||||
qCDebug(octree, "_checkMapRootCalls=%d",_checkMapRootCalls);
|
||||
qCDebug(octree, "_notAllInView=%d",_notAllInView);
|
||||
_mapCount = 0;
|
||||
_checkMapRootCalls = 0;
|
||||
_notAllInView = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverageMapV2::init() {
|
||||
memset(_childMaps,0,sizeof(_childMaps));
|
||||
}
|
||||
|
||||
// 0 = bottom, left
|
||||
// 1 = bottom, right
|
||||
// 2 = top, left
|
||||
// 3 = top, right
|
||||
BoundingBox CoverageMapV2::getChildBoundingBox(int childIndex) {
|
||||
const int RIGHT_BIT = 1;
|
||||
const int TOP_BIT = 2;
|
||||
// initialize to our corner, and half our size
|
||||
BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f);
|
||||
// if our "right" bit is set, then add size.x to the corner
|
||||
if ((childIndex & RIGHT_BIT) == RIGHT_BIT) {
|
||||
result.corner.x += result.size.x;
|
||||
}
|
||||
// if our "top" bit is set, then add size.y to the corner
|
||||
if ((childIndex & TOP_BIT) == TOP_BIT) {
|
||||
result.corner.y += result.size.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT
|
||||
CoverageMapV2StorageResult CoverageMapV2::checkMap(const OctreeProjectedPolygon* polygon, bool storeIt) {
|
||||
assert(_isRoot); // you can only call this on the root map!!!
|
||||
_checkMapRootCalls++;
|
||||
|
||||
// short circuit: if we're the root node (only case we're here), and we're covered, and this polygon is deeper than our
|
||||
// covered depth, then this polygon is occluded!
|
||||
if (_isCovered && _coveredDistance < polygon->getDistance()) {
|
||||
return V2_OCCLUDED;
|
||||
}
|
||||
|
||||
// short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is
|
||||
// not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later.
|
||||
if (!polygon->getAllInView()) {
|
||||
_notAllInView++;
|
||||
return V2_DOESNT_FIT;
|
||||
}
|
||||
|
||||
// Here's where we recursively check the polygon against the coverage map. We need to maintain two pieces of state.
|
||||
// The first state is: have we seen at least one "fully occluded" map items. If we haven't then we don't track the covered
|
||||
// state of the polygon.
|
||||
// The second piece of state is: Are all of our "fully occluded" map items "covered". If even one of these occluded map
|
||||
// items is not covered, then our polygon is not covered.
|
||||
bool seenOccludedMapNodes = false;
|
||||
bool allOccludedMapNodesCovered = false;
|
||||
|
||||
recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered);
|
||||
|
||||
// Ok, no matter how we were called, if all our occluded map nodes are covered, then we know this polygon
|
||||
// is occluded, otherwise, we will report back to the caller about whether or not we stored the polygon
|
||||
if (allOccludedMapNodesCovered) {
|
||||
return V2_OCCLUDED;
|
||||
}
|
||||
if (storeIt) {
|
||||
return V2_STORED; // otherwise report that we STORED it
|
||||
}
|
||||
return V2_NOT_STORED; // unless we weren't asked to store it, then we didn't
|
||||
}
|
||||
|
||||
void CoverageMapV2::recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt,
|
||||
bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered) {
|
||||
|
||||
// if we are really small, then we act like we don't intersect, this allows us to stop
|
||||
// recusing as we get to the smalles edge of the polygon
|
||||
if (_myBoundingBox.area() < MINIMUM_OCCLUSION_CHECK_AREA) {
|
||||
return; // stop recursion, we're done!
|
||||
}
|
||||
|
||||
// Determine if this map node intersects the polygon and/or is fully covered by the polygon
|
||||
// There are a couple special cases: If we're the root, we are assumed to intersect with all
|
||||
// polygons. Also, any map node that is fully occluded also intersects.
|
||||
bool nodeIsCoveredByPolygon = polygon->occludes(_myBoundingBox);
|
||||
bool nodeIsIntersectedByPolygon = nodeIsCoveredByPolygon || _isRoot || polygon->intersects(_myBoundingBox);
|
||||
|
||||
// If we don't intersect, then we can just return, we're done recursing
|
||||
if (!nodeIsIntersectedByPolygon) {
|
||||
return; // stop recursion, we're done!
|
||||
}
|
||||
|
||||
// At this point, we know our node intersects with the polygon. If this node is covered, then we want to treat it
|
||||
// as if the node was fully covered, because this allows us to short circuit further recursion...
|
||||
if (_isCovered && _coveredDistance < polygon->getDistance()) {
|
||||
nodeIsCoveredByPolygon = true; // fake it till you make it
|
||||
}
|
||||
|
||||
// If this node in the map is fully covered by our polygon, then we don't need to recurse any further, but
|
||||
// we do need to do some bookkeeping.
|
||||
if (nodeIsCoveredByPolygon) {
|
||||
// If this is the very first fully covered node we've seen, then we're initialize our allOccludedMapNodesCovered
|
||||
// to be our current covered state. This has the following effect: if this node isn't already covered, then by
|
||||
// definition, we know that at least one node for this polygon isn't covered, and therefore we aren't fully covered.
|
||||
if (!seenOccludedMapNodes) {
|
||||
allOccludedMapNodesCovered = (_isCovered && _coveredDistance < polygon->getDistance());
|
||||
// We need to mark that we've seen at least one node of our polygon! ;)
|
||||
seenOccludedMapNodes = true;
|
||||
} else {
|
||||
// If this is our second or later node of our polygon, then we need to track our allOccludedMapNodesCovered state
|
||||
allOccludedMapNodesCovered = allOccludedMapNodesCovered &&
|
||||
(_isCovered && _coveredDistance < polygon->getDistance());
|
||||
}
|
||||
|
||||
// if we're in store mode then we want to record that this node is covered.
|
||||
if (storeIt) {
|
||||
_isCovered = true;
|
||||
// store the minimum distance of our previous known distance, or our current polygon's distance. This is because
|
||||
// we know that we're at least covered at this distance, but if we had previously identified that we're covered
|
||||
// at a shallower distance, then we want to maintain that distance
|
||||
_coveredDistance = std::min(polygon->getDistance(), _coveredDistance);
|
||||
|
||||
// Note: this might be a good chance to delete child maps, but we're not going to do that at this point because
|
||||
// we're trying to maintain the known distances in the lower portion of the tree.
|
||||
}
|
||||
|
||||
// and since this node of the quad map is covered, we can safely stop recursion. because we know all smaller map
|
||||
// nodes will also be covered.
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, then it means we know that this node is not fully covered by the polygon, but it does intersect
|
||||
// with the polygon.
|
||||
|
||||
// Another case is that we aren't yet marked as covered, and so we should recurse and process smaller quad tree nodes.
|
||||
// Note: we use this to determine if we can collapse the child quad trees and mark this node as covered
|
||||
bool allChildrenOccluded = true;
|
||||
float maxChildCoveredDepth = NOT_COVERED;
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
BoundingBox childMapBoundingBox = getChildBoundingBox(i);
|
||||
// if no child map exists yet, then create it
|
||||
if (!_childMaps[i]) {
|
||||
// children get created with the coverage state of their parent.
|
||||
_childMaps[i] = new CoverageMapV2(childMapBoundingBox, NOT_ROOT, _isCovered, _coveredDistance);
|
||||
}
|
||||
|
||||
_childMaps[i]->recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered);
|
||||
|
||||
// if so far, all of our children are covered, then record our furthest coverage distance
|
||||
if (allChildrenOccluded && _childMaps[i]->_isCovered) {
|
||||
maxChildCoveredDepth = std::max(maxChildCoveredDepth, _childMaps[i]->_coveredDistance);
|
||||
} else {
|
||||
// otherwise, at least one of our children is not covered, so not all are covered
|
||||
allChildrenOccluded = false;
|
||||
}
|
||||
}
|
||||
// if all the children are covered, this makes our quad tree "shallower" because it records that
|
||||
// entire quad is covered, it uses the "furthest" z-order so that if a shalower polygon comes through
|
||||
// we won't assume its occluded
|
||||
if (allChildrenOccluded && storeIt) {
|
||||
_isCovered = true;
|
||||
_coveredDistance = maxChildCoveredDepth;
|
||||
}
|
||||
|
||||
// normal exit case... return...
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
//
|
||||
// CoverageMapV2.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 06/11/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CoverageMapV2_h
|
||||
#define hifi_CoverageMapV2_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "OctreeProjectedPolygon.h"
|
||||
|
||||
typedef enum {
|
||||
V2_DOESNT_FIT, V2_STORED, V2_NOT_STORED,
|
||||
V2_INTERSECT, V2_NO_INTERSECT,
|
||||
V2_OCCLUDED, V2_NOT_OCCLUDED
|
||||
} CoverageMapV2StorageResult;
|
||||
|
||||
class CoverageMapV2 {
|
||||
|
||||
public:
|
||||
static const int NUMBER_OF_CHILDREN = 4;
|
||||
static const bool NOT_ROOT = false;
|
||||
static const bool IS_ROOT = true;
|
||||
static const BoundingBox ROOT_BOUNDING_BOX;
|
||||
static const float MINIMUM_POLYGON_AREA_TO_STORE;
|
||||
static const float NOT_COVERED;
|
||||
static const float MINIMUM_OCCLUSION_CHECK_AREA;
|
||||
static bool wantDebugging;
|
||||
|
||||
CoverageMapV2(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT,
|
||||
bool isCovered = false, float coverageDistance = NOT_COVERED);
|
||||
~CoverageMapV2();
|
||||
|
||||
CoverageMapV2StorageResult checkMap(const OctreeProjectedPolygon* polygon, bool storeIt = true);
|
||||
|
||||
BoundingBox getChildBoundingBox(int childIndex);
|
||||
const BoundingBox& getBoundingBox() const { return _myBoundingBox; };
|
||||
CoverageMapV2* getChild(int childIndex) const { return _childMaps[childIndex]; };
|
||||
bool isCovered() const { return _isCovered; };
|
||||
|
||||
void erase(); // erase the coverage map
|
||||
|
||||
void render();
|
||||
|
||||
|
||||
private:
|
||||
void recurseMap(const OctreeProjectedPolygon* polygon, bool storeIt,
|
||||
bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered);
|
||||
|
||||
void init();
|
||||
|
||||
bool _isRoot;
|
||||
BoundingBox _myBoundingBox;
|
||||
CoverageMapV2* _childMaps[NUMBER_OF_CHILDREN];
|
||||
|
||||
bool _isCovered;
|
||||
float _coveredDistance;
|
||||
|
||||
static int _mapCount;
|
||||
static int _checkMapRootCalls;
|
||||
static int _notAllInView;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_CoverageMapV2_h
|
|
@ -41,7 +41,6 @@
|
|||
#include <PathUtils.h>
|
||||
#include <Gzip.h>
|
||||
|
||||
#include "CoverageMap.h"
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElementBag.h"
|
||||
#include "Octree.h"
|
||||
|
@ -951,9 +950,9 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
||||
// if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// reason couldn't be written... so reset them here... This isn't true for the non-color included case
|
||||
if (suppressEmptySubtrees() && params.includeColor && childBytesWritten == 2) {
|
||||
if (suppressEmptySubtrees() && childBytesWritten == 2) {
|
||||
childBytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT...
|
||||
}
|
||||
|
@ -1103,31 +1102,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf.
|
||||
// leaf occlusion is handled down below when we check child nodes
|
||||
if (params.wantOcclusionCulling && !element->isLeaf()) {
|
||||
OctreeProjectedPolygon* voxelPolygon =
|
||||
new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(element->getAACube()));
|
||||
|
||||
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
|
||||
// culling and proceed as normal
|
||||
if (voxelPolygon->getAllInView()) {
|
||||
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false);
|
||||
delete voxelPolygon; // cleanup
|
||||
if (result == OCCLUDED) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedOccluded(element);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::OCCLUDED;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
} else {
|
||||
// If this shadow wasn't "all in view" then we ignored it for occlusion culling, but
|
||||
// we do need to clean up memory and proceed as normal...
|
||||
delete voxelPolygon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
||||
|
@ -1190,20 +1164,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
if (params.wantOcclusionCulling) {
|
||||
if (childElement) {
|
||||
float distance = params.viewFrustum ? childElement->distanceToCamera(*params.viewFrustum) : 0;
|
||||
|
||||
currentCount = insertOctreeElementIntoSortedArrays(childElement, distance, i,
|
||||
sortedChildren, (float*)&distancesToChildren,
|
||||
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
|
||||
}
|
||||
} else {
|
||||
sortedChildren[i] = childElement;
|
||||
indexOfChildren[i] = i;
|
||||
distancesToChildren[i] = 0.0f;
|
||||
currentCount++;
|
||||
}
|
||||
sortedChildren[i] = childElement;
|
||||
indexOfChildren[i] = i;
|
||||
distancesToChildren[i] = 0.0f;
|
||||
currentCount++;
|
||||
|
||||
// track stats
|
||||
// must check childElement here, because it could be we got here with no childElement
|
||||
|
@ -1255,36 +1219,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
bool childIsOccluded = false; // assume it's not occluded
|
||||
|
||||
// If the user also asked for occlusion culling, check if this element is occluded
|
||||
if (params.wantOcclusionCulling && childElement->isLeaf()) {
|
||||
// Don't check occlusion here, just add them to our distance ordered array...
|
||||
|
||||
// FIXME params.ViewFrustum is used here, but later it is checked against nullptr.
|
||||
OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(
|
||||
params.viewFrustum->getProjectedPolygon(childElement->getAACube()));
|
||||
|
||||
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion
|
||||
// culling and proceed as normal
|
||||
if (voxelPolygon->getAllInView()) {
|
||||
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true);
|
||||
|
||||
// In all cases where the shadow wasn't stored, we need to free our own memory.
|
||||
// In the case where it is stored, the CoverageMap will free memory for us later.
|
||||
if (result != STORED) {
|
||||
delete voxelPolygon;
|
||||
}
|
||||
|
||||
// If while attempting to add this voxel's shadow, we determined it was occluded, then
|
||||
// we don't need to process it further and we can exit early.
|
||||
if (result == OCCLUDED) {
|
||||
childIsOccluded = true;
|
||||
}
|
||||
} else {
|
||||
delete voxelPolygon;
|
||||
}
|
||||
} // wants occlusion culling & isLeaf()
|
||||
|
||||
|
||||
bool shouldRender = !params.viewFrustum
|
||||
? true
|
||||
: childElement->calculateShouldRender(params.viewFrustum,
|
||||
|
@ -1359,7 +1293,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data
|
||||
element->initializeExtraEncodeData(params);
|
||||
|
||||
// write the child element data... NOTE: includeColor means include element data
|
||||
// write the child element data...
|
||||
// NOTE: the format of the bitstream is generally this:
|
||||
// [octalcode]
|
||||
// [bitmask for existence of child data]
|
||||
|
@ -1369,65 +1303,63 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// N x [ ... tree for children ...]
|
||||
//
|
||||
// This section of the code, is writing the "N x [child data]" portion of this bitstream
|
||||
if (params.includeColor) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenDataBits, i)) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenDataBits, i)) {
|
||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
||||
|
||||
// the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already
|
||||
// processed and sent the data bits for. Let our tree subclass determine if it really wants to send the
|
||||
// data for this child at this point
|
||||
if (childElement && element->shouldIncludeChildData(i, params)) {
|
||||
// the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already
|
||||
// processed and sent the data bits for. Let our tree subclass determine if it really wants to send the
|
||||
// data for this child at this point
|
||||
if (childElement && element->shouldIncludeChildData(i, params)) {
|
||||
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
|
||||
// a childElement may "partially" write it's data. for example, the model server where the entire
|
||||
// contents of the element may be larger than can fit in a single MTU/packetData. In this case,
|
||||
// we want to allow the appendElementData() to respond that it produced partial data, which should be
|
||||
// written, but that the childElement needs to be reprocessed in an additional pass or passes
|
||||
// to be completed.
|
||||
LevelDetails childDataLevelKey = packetData->startLevel();
|
||||
// a childElement may "partially" write it's data. for example, the model server where the entire
|
||||
// contents of the element may be larger than can fit in a single MTU/packetData. In this case,
|
||||
// we want to allow the appendElementData() to respond that it produced partial data, which should be
|
||||
// written, but that the childElement needs to be reprocessed in an additional pass or passes
|
||||
// to be completed.
|
||||
LevelDetails childDataLevelKey = packetData->startLevel();
|
||||
|
||||
OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params);
|
||||
OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params);
|
||||
|
||||
// allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state
|
||||
element->updateEncodedData(i, childAppendState, params);
|
||||
// allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state
|
||||
element->updateEncodedData(i, childAppendState, params);
|
||||
|
||||
// Continue this level so long as some part of this child element was appended.
|
||||
bool childFit = (childAppendState != OctreeElement::NONE);
|
||||
// Continue this level so long as some part of this child element was appended.
|
||||
bool childFit = (childAppendState != OctreeElement::NONE);
|
||||
|
||||
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
|
||||
// the data type wants to bail on this element level completely
|
||||
if (!childFit && mustIncludeAllChildData()) {
|
||||
continueThisLevel = false;
|
||||
break;
|
||||
}
|
||||
// some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit
|
||||
// the data type wants to bail on this element level completely
|
||||
if (!childFit && mustIncludeAllChildData()) {
|
||||
continueThisLevel = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
|
||||
// this child data
|
||||
if (childFit) {
|
||||
actualChildrenDataBits += (1 << (7 - i));
|
||||
continueThisLevel = packetData->endLevel(childDataLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(childDataLevelKey);
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
// If the child was partially or fully appended, then mark the actualChildrenDataBits as including
|
||||
// this child data
|
||||
if (childFit) {
|
||||
actualChildrenDataBits += (1 << (7 - i));
|
||||
continueThisLevel = packetData->endLevel(childDataLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(childDataLevelKey);
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
// If this child was partially appended, then consider this element to be partially appended
|
||||
if (childAppendState == OctreeElement::PARTIAL) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
// If this child was partially appended, then consider this element to be partially appended
|
||||
if (childAppendState == OctreeElement::PARTIAL) {
|
||||
elementAppendState = OctreeElement::PARTIAL;
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (params.stats && (childAppendState != OctreeElement::NONE)) {
|
||||
params.stats->colorSent(childElement);
|
||||
}
|
||||
// don't need to check childElement here, because we can't get here with no childElement
|
||||
if (params.stats && (childAppendState != OctreeElement::NONE)) {
|
||||
params.stats->colorSent(childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1506,15 +1438,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was!
|
||||
int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits));
|
||||
|
||||
// we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the
|
||||
// final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes,
|
||||
// and then later reshuffle these sections of our output buffer back into normal order. This allows us to make
|
||||
// a single recursive pass in distance sorted order, but retain standard order in our encoded packet
|
||||
int recursiveSliceSizes[NUMBER_OF_CHILDREN];
|
||||
const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN];
|
||||
int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset();
|
||||
int allSlicesSize = 0;
|
||||
|
||||
// for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
|
||||
// add them to our distance ordered array of children
|
||||
for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) {
|
||||
|
@ -1524,9 +1447,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
|
||||
int thisLevel = currentEncodeLevel;
|
||||
// remember this for reshuffling
|
||||
recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize();
|
||||
|
||||
int childTreeBytesOut = 0;
|
||||
|
||||
// NOTE: some octree styles (like models and particles) will store content in parent elements, and child
|
||||
|
@ -1546,10 +1466,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
// remember this for reshuffling
|
||||
recursiveSliceSizes[originalIndex] = childTreeBytesOut;
|
||||
allSlicesSize += childTreeBytesOut;
|
||||
|
||||
// if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space,
|
||||
// basically, the children below don't contain any info.
|
||||
|
||||
|
@ -1566,17 +1482,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// so, if the child returns 2 bytes out, we can actually consider that an empty tree also!!
|
||||
//
|
||||
// we can make this act like no bytes out, by just resetting the bytes out in this case
|
||||
if (suppressEmptySubtrees() && params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) {
|
||||
if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) {
|
||||
childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
||||
|
||||
}
|
||||
// We used to try to collapse trees that didn't contain any data, but this does appear to create a problem
|
||||
// in detecting element deletion. So, I've commented this out but left it in here as a warning to anyone else
|
||||
// about not attempting to add this optimization back in, without solving the element deletion case.
|
||||
// We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree
|
||||
//if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) {
|
||||
// childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees
|
||||
//}
|
||||
|
||||
bytesAtThisLevel += childTreeBytesOut;
|
||||
|
||||
|
@ -1596,7 +1505,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
|
||||
if (params.stats && childrenExistInPacketBits == 0) {
|
||||
params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor);
|
||||
params.stats->childBitsRemoved(params.includeExistsBits);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
|
@ -1613,33 +1522,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
} // end if (childTreeBytesOut == 0)
|
||||
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
|
||||
} // end for
|
||||
|
||||
// reshuffle here...
|
||||
if (continueThisLevel && params.wantOcclusionCulling) {
|
||||
unsigned char tempReshuffleBuffer[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
|
||||
unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination
|
||||
|
||||
// iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree
|
||||
// details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them
|
||||
// back into original distance order
|
||||
for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) {
|
||||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
int thisSliceSize = recursiveSliceSizes[originalIndex];
|
||||
const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex];
|
||||
|
||||
memcpy(tempBufferTo, thisSliceStarts, thisSliceSize);
|
||||
tempBufferTo += thisSliceSize;
|
||||
}
|
||||
}
|
||||
|
||||
// now that all slices are back in the correct order, copy them to the correct output buffer
|
||||
continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize);
|
||||
if (!continueThisLevel) {
|
||||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update recursive slice!!!";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
}
|
||||
} // end keepDiggingDeeper
|
||||
|
||||
// If we made it this far, then we've written all of our child data... if this element is the root
|
||||
|
@ -1918,7 +1800,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr
|
|||
|
||||
unsigned char* dataAt = entireFileDataSection;
|
||||
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
|
||||
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
||||
SharedNodePointer(), wantImportProgress, gotVersion);
|
||||
|
||||
readBitstreamToTree(dataAt, dataLength, args);
|
||||
|
@ -1957,7 +1839,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr
|
|||
unsigned char* dataAt = fileChunk;
|
||||
unsigned long dataLength = chunkLength;
|
||||
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0,
|
||||
ReadBitstreamToTreeParams args(NO_EXISTS_BITS, NULL, 0,
|
||||
SharedNodePointer(), wantImportProgress, gotVersion);
|
||||
|
||||
readBitstreamToTree(dataAt, dataLength, args);
|
||||
|
@ -2105,7 +1987,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element)
|
|||
bool lastPacketWritten = false;
|
||||
|
||||
while (OctreeElementPointer subTree = elementBag.extract()) {
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, NO_EXISTS_BITS);
|
||||
withReadLock([&] {
|
||||
params.extraEncodeData = &extraEncodeData;
|
||||
bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params);
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include "OctreePacketData.h"
|
||||
#include "OctreeSceneStats.h"
|
||||
|
||||
class CoverageMap;
|
||||
class ReadBitstreamToTreeParams;
|
||||
class Octree;
|
||||
class OctreeElement;
|
||||
|
@ -53,12 +52,8 @@ typedef QHash<uint, AACube> CubeList;
|
|||
|
||||
const bool NO_EXISTS_BITS = false;
|
||||
const bool WANT_EXISTS_BITS = true;
|
||||
const bool NO_COLOR = false;
|
||||
const bool WANT_COLOR = true;
|
||||
const bool COLLAPSE_EMPTY_TREE = true;
|
||||
const bool DONT_COLLAPSE = false;
|
||||
const bool NO_OCCLUSION_CULLING = false;
|
||||
const bool WANT_OCCLUSION_CULLING = true;
|
||||
|
||||
const int DONT_CHOP = 0;
|
||||
const int NO_BOUNDARY_ADJUST = 0;
|
||||
|
@ -75,18 +70,15 @@ public:
|
|||
int maxEncodeLevel;
|
||||
int maxLevelReached;
|
||||
const ViewFrustum* viewFrustum;
|
||||
bool includeColor;
|
||||
bool includeExistsBits;
|
||||
int chopLevels;
|
||||
bool deltaViewFrustum;
|
||||
const ViewFrustum* lastViewFrustum;
|
||||
bool wantOcclusionCulling;
|
||||
int boundaryLevelAdjust;
|
||||
float octreeElementSizeScale;
|
||||
quint64 lastViewFrustumSent;
|
||||
bool forceSendScene;
|
||||
OctreeSceneStats* stats;
|
||||
CoverageMap* map;
|
||||
JurisdictionMap* jurisdictionMap;
|
||||
OctreeElementExtraEncodeData* extraEncodeData;
|
||||
|
||||
|
@ -108,13 +100,10 @@ public:
|
|||
EncodeBitstreamParams(
|
||||
int maxEncodeLevel = INT_MAX,
|
||||
const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM,
|
||||
bool includeColor = WANT_COLOR,
|
||||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
int chopLevels = 0,
|
||||
bool deltaViewFrustum = false,
|
||||
const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM,
|
||||
bool wantOcclusionCulling = NO_OCCLUSION_CULLING,
|
||||
CoverageMap* map = IGNORE_COVERAGE_MAP,
|
||||
int boundaryLevelAdjust = NO_BOUNDARY_ADJUST,
|
||||
float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
quint64 lastViewFrustumSent = IGNORE_LAST_SENT,
|
||||
|
@ -125,18 +114,15 @@ public:
|
|||
maxEncodeLevel(maxEncodeLevel),
|
||||
maxLevelReached(0),
|
||||
viewFrustum(viewFrustum),
|
||||
includeColor(includeColor),
|
||||
includeExistsBits(includeExistsBits),
|
||||
chopLevels(chopLevels),
|
||||
deltaViewFrustum(deltaViewFrustum),
|
||||
lastViewFrustum(lastViewFrustum),
|
||||
wantOcclusionCulling(wantOcclusionCulling),
|
||||
boundaryLevelAdjust(boundaryLevelAdjust),
|
||||
octreeElementSizeScale(octreeElementSizeScale),
|
||||
lastViewFrustumSent(lastViewFrustumSent),
|
||||
forceSendScene(forceSendScene),
|
||||
stats(stats),
|
||||
map(map),
|
||||
jurisdictionMap(jurisdictionMap),
|
||||
extraEncodeData(extraEncodeData),
|
||||
stopReason(UNKNOWN)
|
||||
|
@ -176,6 +162,8 @@ public:
|
|||
case OCCLUDED: return QString("OCCLUDED"); break;
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(const QUuid& dataID, quint64 itemLastEdited)> trackSend { [](const QUuid&, quint64){} };
|
||||
};
|
||||
|
||||
class ReadElementBufferToTreeArgs {
|
||||
|
@ -188,7 +176,6 @@ public:
|
|||
|
||||
class ReadBitstreamToTreeParams {
|
||||
public:
|
||||
bool includeColor;
|
||||
bool includeExistsBits;
|
||||
OctreeElementPointer destinationElement;
|
||||
QUuid sourceUUID;
|
||||
|
@ -199,14 +186,12 @@ public:
|
|||
int entitiesPerPacket = 0;
|
||||
|
||||
ReadBitstreamToTreeParams(
|
||||
bool includeColor = WANT_COLOR,
|
||||
bool includeExistsBits = WANT_EXISTS_BITS,
|
||||
OctreeElementPointer destinationElement = NULL,
|
||||
QUuid sourceUUID = QUuid(),
|
||||
SharedNodePointer sourceNode = SharedNodePointer(),
|
||||
bool wantImportProgress = false,
|
||||
PacketVersion bitstreamVersion = 0) :
|
||||
includeColor(includeColor),
|
||||
includeExistsBits(includeExistsBits),
|
||||
destinationElement(destinationElement),
|
||||
sourceUUID(sourceUUID),
|
||||
|
@ -313,7 +298,7 @@ public:
|
|||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
// Note: this assumes the fileFormat is the HIO individual voxels code files
|
||||
void loadOctreeFile(const char* fileName, bool wantColorRandomizer);
|
||||
void loadOctreeFile(const char* fileName);
|
||||
|
||||
// Octree exporters
|
||||
void writeToFile(const char* filename, OctreeElementPointer element = NULL, QString persistAsFileType = "svo");
|
||||
|
|
|
@ -51,10 +51,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_octreeQuery.setWantLowResMoving(true);
|
||||
_octreeQuery.setWantColor(true);
|
||||
_octreeQuery.setWantDelta(true);
|
||||
_octreeQuery.setWantOcclusionCulling(false);
|
||||
_octreeQuery.setWantCompression(true); // TODO: should be on by default
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
|
|
|
@ -23,12 +23,13 @@ AtomicUIntStat OctreePacketData::_totalBytesOfValues { 0 };
|
|||
AtomicUIntStat OctreePacketData::_totalBytesOfPositions { 0 };
|
||||
AtomicUIntStat OctreePacketData::_totalBytesOfRawData { 0 };
|
||||
|
||||
OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) {
|
||||
changeSettings(enableCompression, targetSize); // does reset...
|
||||
OctreePacketData::OctreePacketData(int targetSize, bool enableCompression) {
|
||||
changeSettings(targetSize); // does reset...
|
||||
_enableCompression = enableCompression; // FIXME
|
||||
}
|
||||
|
||||
void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) {
|
||||
_enableCompression = enableCompression;
|
||||
void OctreePacketData::changeSettings(unsigned int targetSize) {
|
||||
_enableCompression = true; // FIXME
|
||||
_targetSize = std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize);
|
||||
reset();
|
||||
}
|
||||
|
|
|
@ -83,11 +83,11 @@ private:
|
|||
/// Handles packing of the data portion of PacketType_OCTREE_DATA messages.
|
||||
class OctreePacketData {
|
||||
public:
|
||||
OctreePacketData(bool enableCompression = false, int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
OctreePacketData(int maxFinalizedSize = MAX_OCTREE_PACKET_DATA_SIZE, bool enableCompression = true);
|
||||
~OctreePacketData();
|
||||
|
||||
/// change compression and target size settings
|
||||
void changeSettings(bool enableCompression = false, unsigned int targetSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
void changeSettings(unsigned int targetSize = MAX_OCTREE_PACKET_DATA_SIZE);
|
||||
|
||||
/// reset completely, all data is discarded
|
||||
void reset();
|
||||
|
@ -262,7 +262,7 @@ private:
|
|||
bool append(unsigned char byte);
|
||||
|
||||
unsigned int _targetSize;
|
||||
bool _enableCompression;
|
||||
bool _enableCompression { true }; // FIXME - these will always be compressed, so remove this option
|
||||
|
||||
unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
int _bytesInUse;
|
||||
|
|
|
@ -41,10 +41,7 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// bitMask of less than byte wide items
|
||||
unsigned char bitItems = 0;
|
||||
if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); }
|
||||
if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); }
|
||||
if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); }
|
||||
if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); }
|
||||
if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); }
|
||||
|
||||
*destinationBuffer++ = bitItems;
|
||||
|
||||
|
@ -84,10 +81,7 @@ int OctreeQuery::parseData(NLPacket& packet) {
|
|||
unsigned char bitItems = 0;
|
||||
bitItems = (unsigned char)*sourceBuffer++;
|
||||
_wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
|
||||
_wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT);
|
||||
_wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT);
|
||||
_wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT);
|
||||
_wantCompression = oneAtBit(bitItems, WANT_COMPRESSION);
|
||||
|
||||
// desired Max Octree PPS
|
||||
memcpy(&_maxQueryPPS, sourceBuffer, sizeof(_maxQueryPPS));
|
||||
|
|
|
@ -35,10 +35,10 @@ typedef unsigned long long quint64;
|
|||
|
||||
// First bitset
|
||||
const int WANT_LOW_RES_MOVING_BIT = 0;
|
||||
const int WANT_COLOR_AT_BIT = 1;
|
||||
const int UNUSED_BIT_1 = 1; // unused... available for new feature
|
||||
const int WANT_DELTA_AT_BIT = 2;
|
||||
const int WANT_OCCLUSION_CULLING_BIT = 3;
|
||||
const int WANT_COMPRESSION = 4; // 5th bit
|
||||
const int UNUSED_BIT_3 = 3; // unused... available for new feature
|
||||
const int UNUSED_BIT_4 = 4; // 5th bit, unused... available for new feature
|
||||
|
||||
class OctreeQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -71,21 +71,15 @@ public:
|
|||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
|
||||
// related to Octree Sending strategies
|
||||
bool getWantColor() const { return _wantColor; }
|
||||
bool getWantDelta() const { return _wantDelta; }
|
||||
bool getWantLowResMoving() const { return _wantLowResMoving; }
|
||||
bool getWantOcclusionCulling() const { return _wantOcclusionCulling; }
|
||||
bool getWantCompression() const { return _wantCompression; }
|
||||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
public slots:
|
||||
void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; }
|
||||
void setWantColor(bool wantColor) { _wantColor = wantColor; }
|
||||
void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; }
|
||||
void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; }
|
||||
void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; }
|
||||
void setMaxQueryPacketsPerSecond(int maxQueryPPS) { _maxQueryPPS = maxQueryPPS; }
|
||||
void setOctreeSizeScale(float octreeSizeScale) { _octreeElementSizeScale = octreeSizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
|
@ -101,11 +95,8 @@ protected:
|
|||
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
|
||||
|
||||
// octree server sending items
|
||||
bool _wantColor = true;
|
||||
bool _wantDelta = true;
|
||||
bool _wantLowResMoving = true;
|
||||
bool _wantOcclusionCulling = false;
|
||||
bool _wantCompression = false;
|
||||
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust = 0; /// used for LOD calculations
|
||||
|
|
|
@ -115,7 +115,7 @@ void OctreeRenderer::processDatagram(NLPacket& packet, SharedNodePointer sourceN
|
|||
|
||||
if (sectionLength) {
|
||||
// ask the VoxelTree to read the bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
|
||||
ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL,
|
||||
sourceUUID, sourceNode, false, packet.getVersion());
|
||||
quint64 startUncompress, startLock = usecTimestampNow();
|
||||
quint64 startReadBitsteam, endReadBitsteam;
|
||||
|
|
|
@ -371,14 +371,12 @@ void OctreeSceneStats::existsInPacketBitsWritten() {
|
|||
_existsInPacketBitsWritten++;
|
||||
}
|
||||
|
||||
void OctreeSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) {
|
||||
void OctreeSceneStats::childBitsRemoved(bool includesExistsBits) {
|
||||
_existsInPacketBitsWritten--;
|
||||
if (includesExistsBits) {
|
||||
_existsBitsWritten--;
|
||||
}
|
||||
if (includesColors) {
|
||||
_colorBitsWritten--;
|
||||
}
|
||||
_colorBitsWritten--;
|
||||
_treesRemoved++;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ public:
|
|||
void existsInPacketBitsWritten();
|
||||
|
||||
/// Fix up tracking statistics in case where bitmasks were removed for some reason
|
||||
void childBitsRemoved(bool includesExistsBits, bool includesColors);
|
||||
void childBitsRemoved(bool includesExistsBits);
|
||||
|
||||
/// Pack the details of the statistics into a buffer for sending as a network packet
|
||||
int packIntoPacket();
|
||||
|
|
|
@ -928,7 +928,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
//virtual
|
||||
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
_needsUpdateClusterMatrices = true;
|
||||
_rig->updateAnimations(deltaTime, parentTransform);
|
||||
_rig->updateAnimations(deltaTime, parentTransform);
|
||||
}
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
// update the world space transforms for all joints
|
||||
|
|
|
@ -674,7 +674,6 @@ void ScriptEngine::run() {
|
|||
}
|
||||
|
||||
_isRunning = true;
|
||||
_isFinished = false;
|
||||
if (_wantSignals) {
|
||||
emit runningStateChanged();
|
||||
}
|
||||
|
|
|
@ -279,18 +279,20 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
|
|||
|
||||
glm::quat glmExtractRotation(const glm::mat4& matrix) {
|
||||
glm::vec3 scale = extractScale(matrix);
|
||||
float maxScale = std::max(std::max(scale.x, scale.y), scale.z);
|
||||
if (maxScale > 1.01f || maxScale <= 0.99f) {
|
||||
// quat_cast doesn't work so well with scaled matrices, so cancel it out.
|
||||
glm::mat4 tmp = glm::scale(matrix, 1.0f / scale);
|
||||
return glm::normalize(glm::quat_cast(tmp));
|
||||
} else {
|
||||
return glm::normalize(glm::quat_cast(matrix));
|
||||
}
|
||||
// quat_cast doesn't work so well with scaled matrices, so cancel it out.
|
||||
glm::mat4 tmp = glm::scale(matrix, 1.0f / scale);
|
||||
return glm::normalize(glm::quat_cast(tmp));
|
||||
}
|
||||
|
||||
glm::vec3 extractScale(const glm::mat4& matrix) {
|
||||
return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2]));
|
||||
glm::mat3 m(matrix);
|
||||
float det = glm::determinant(m);
|
||||
if (det < 0) {
|
||||
// left handed matrix, flip sign to compensate.
|
||||
return glm::vec3(-glm::length(m[0]), glm::length(m[1]), glm::length(m[2]));
|
||||
} else {
|
||||
return glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2]));
|
||||
}
|
||||
}
|
||||
|
||||
float extractUniformScale(const glm::mat4& matrix) {
|
||||
|
|
|
@ -264,29 +264,6 @@ unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLev
|
|||
return newCode;
|
||||
}
|
||||
|
||||
unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode,
|
||||
bool includeColorSpace) {
|
||||
|
||||
int oldCodeLength = numberOfThreeBitSectionsInCode(originalOctalCode);
|
||||
int newParentCodeLength = numberOfThreeBitSectionsInCode(newParentOctalCode);
|
||||
int newCodeLength = newParentCodeLength + oldCodeLength;
|
||||
int bufferLength = newCodeLength + (includeColorSpace ? SIZE_OF_COLOR_DATA : 0);
|
||||
unsigned char* newCode = new unsigned char[bufferLength];
|
||||
*newCode = newCodeLength; // set the length byte
|
||||
|
||||
// copy parent code section first
|
||||
for (int sectionFromParent = 0; sectionFromParent < newParentCodeLength; sectionFromParent++) {
|
||||
char sectionValue = getOctalCodeSectionValue(newParentOctalCode, sectionFromParent);
|
||||
setOctalCodeSectionValue(newCode, sectionFromParent, sectionValue);
|
||||
}
|
||||
// copy original code section next
|
||||
for (int sectionFromOriginal = 0; sectionFromOriginal < oldCodeLength; sectionFromOriginal++) {
|
||||
char sectionValue = getOctalCodeSectionValue(originalOctalCode, sectionFromOriginal);
|
||||
setOctalCodeSectionValue(newCode, sectionFromOriginal + newParentCodeLength, sectionValue);
|
||||
}
|
||||
return newCode;
|
||||
}
|
||||
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) {
|
||||
if (!possibleAncestor || !possibleDescendent) {
|
||||
return false;
|
||||
|
|
|
@ -36,8 +36,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2;
|
|||
int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH);
|
||||
|
||||
unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels);
|
||||
unsigned char* rebaseOctalCode(const unsigned char* originalOctalCode, const unsigned char* newParentOctalCode,
|
||||
bool includeColorSpace = false);
|
||||
|
||||
const int CHECK_NODE_ONLY = -1;
|
||||
bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent,
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtOpenGL/QGLWidget>
|
||||
#include <GLMHelpers.h>
|
||||
#include <gl/GlWindow.h>
|
||||
#include <QEvent>
|
||||
#include <QtGui/QResizeEvent>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
|
|
|
@ -194,10 +194,7 @@ void AnimTests::testVariant() {
|
|||
auto floatVarNegative = AnimVariant(-1.0f);
|
||||
auto vec3Var = AnimVariant(glm::vec3(1.0f, -2.0f, 3.0f));
|
||||
auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, -3.0f, 4.0f));
|
||||
auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f),
|
||||
glm::vec4(5.0f, 6.0f, -7.0f, 8.0f),
|
||||
glm::vec4(9.0f, 10.0f, 11.0f, 12.0f),
|
||||
glm::vec4(13.0f, 14.0f, 15.0f, 16.0f)));
|
||||
|
||||
QVERIFY(defaultVar.isBool());
|
||||
QVERIFY(defaultVar.getBool() == false);
|
||||
|
||||
|
@ -232,12 +229,6 @@ void AnimTests::testVariant() {
|
|||
QVERIFY(q.x == 2.0f);
|
||||
QVERIFY(q.y == -3.0f);
|
||||
QVERIFY(q.z == 4.0f);
|
||||
|
||||
QVERIFY(mat4Var.isMat4());
|
||||
auto m = mat4Var.getMat4();
|
||||
QVERIFY(m[0].x == 1.0f);
|
||||
QVERIFY(m[1].z == -7.0f);
|
||||
QVERIFY(m[3].w == 16.0f);
|
||||
}
|
||||
|
||||
void AnimTests::testAccumulateTime() {
|
||||
|
@ -323,3 +314,83 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram
|
|||
QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop");
|
||||
triggers.clear();
|
||||
}
|
||||
|
||||
|
||||
void AnimTests::testAnimPose() {
|
||||
const float PI = (float)M_PI;
|
||||
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f));
|
||||
const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
std::vector<glm::vec3> scaleVec = {
|
||||
glm::vec3(1),
|
||||
glm::vec3(2.0f, 1.0f, 1.0f),
|
||||
glm::vec3(1.0f, 0.5f, 1.0f),
|
||||
glm::vec3(1.0f, 1.0f, 1.5f),
|
||||
glm::vec3(2.0f, 0.5f, 1.5f),
|
||||
glm::vec3(-2.0f, 0.5f, 1.5f),
|
||||
glm::vec3(2.0f, -0.5f, 1.5f),
|
||||
glm::vec3(2.0f, 0.5f, -1.5f),
|
||||
glm::vec3(-2.0f, -0.5f, -1.5f),
|
||||
};
|
||||
|
||||
std::vector<glm::quat> rotVec = {
|
||||
glm::quat(),
|
||||
ROT_X_90,
|
||||
ROT_Y_180,
|
||||
ROT_Z_30,
|
||||
ROT_X_90 * ROT_Y_180 * ROT_Z_30,
|
||||
-ROT_Y_180
|
||||
};
|
||||
|
||||
std::vector<glm::vec3> transVec = {
|
||||
glm::vec3(),
|
||||
glm::vec3(10.0f, 0.0f, 0.0f),
|
||||
glm::vec3(0.0f, 5.0f, 0.0f),
|
||||
glm::vec3(0.0f, 0.0f, 7.5f),
|
||||
glm::vec3(10.0f, 5.0f, 7.5f),
|
||||
glm::vec3(-10.0f, 5.0f, 7.5f),
|
||||
glm::vec3(10.0f, -5.0f, 7.5f),
|
||||
glm::vec3(10.0f, 5.0f, -7.5f)
|
||||
};
|
||||
|
||||
const float EPSILON = 0.001f;
|
||||
|
||||
for (auto& scale : scaleVec) {
|
||||
for (auto& rot : rotVec) {
|
||||
for (auto& trans : transVec) {
|
||||
|
||||
// build a matrix the old fashioned way.
|
||||
glm::mat4 scaleMat = glm::scale(glm::mat4(), scale);
|
||||
glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans);
|
||||
glm::mat4 rawMat = rotTransMat * scaleMat;
|
||||
|
||||
// use an anim pose to build a matrix by parts.
|
||||
AnimPose pose(scale, rot, trans);
|
||||
glm::mat4 poseMat = pose;
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, EPSILON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& scale : scaleVec) {
|
||||
for (auto& rot : rotVec) {
|
||||
for (auto& trans : transVec) {
|
||||
|
||||
// build a matrix the old fashioned way.
|
||||
glm::mat4 scaleMat = glm::scale(glm::mat4(), scale);
|
||||
glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans);
|
||||
glm::mat4 rawMat = rotTransMat * scaleMat;
|
||||
|
||||
// use an anim pose to decompse a matrix into parts
|
||||
AnimPose pose(rawMat);
|
||||
|
||||
// now build a new matrix from those parts.
|
||||
glm::mat4 poseMat = pose;
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, EPSILON);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ private slots:
|
|||
void testLoader();
|
||||
void testVariant();
|
||||
void testAccumulateTime();
|
||||
void testAnimPose();
|
||||
};
|
||||
|
||||
#endif // hifi_AnimTests_h
|
||||
|
|
|
@ -128,7 +128,7 @@ protected:
|
|||
public:
|
||||
QTestWindow() {
|
||||
setSurfaceType(QSurface::OpenGLSurface);
|
||||
QSurfaceFormat format = getDefaultOpenGlSurfaceFormat();
|
||||
QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
|
||||
setFormat(format);
|
||||
_context = new QOpenGLContext;
|
||||
_context->setFormat(format);
|
||||
|
|
|
@ -241,7 +241,6 @@
|
|||
gravity: BOW_GRAVITY,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/bow_fall.L.wav",
|
||||
script: bowScriptURL,
|
||||
userData: JSON.stringify({
|
||||
resetMe: {
|
||||
|
@ -656,6 +655,7 @@
|
|||
y: -3.5,
|
||||
z: 0
|
||||
},
|
||||
restitution: 0,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.01,
|
||||
|
@ -1027,6 +1027,7 @@
|
|||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
restitution: 0,
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.21,
|
||||
|
@ -1189,6 +1190,7 @@
|
|||
collisionsWillMove: true,
|
||||
collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/SpryPntCnDrp1.L.wav",
|
||||
shapeType: 'box',
|
||||
restitution: 0,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -3.0,
|
||||
|
|
|
@ -220,7 +220,6 @@ MasterReset = function() {
|
|||
gravity: BOW_GRAVITY,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
collisionSoundURL: "http://hifi-public.s3.amazonaws.com/sounds/bow_fall.L.wav",
|
||||
script: bowScriptURL,
|
||||
userData: JSON.stringify({
|
||||
resetMe: {
|
||||
|
@ -636,6 +635,7 @@ MasterReset = function() {
|
|||
y: -3.5,
|
||||
z: 0
|
||||
},
|
||||
restitution: 0,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.01,
|
||||
|
@ -1007,6 +1007,7 @@ MasterReset = function() {
|
|||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
restitution: 0,
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.21,
|
||||
|
@ -1174,6 +1175,7 @@ MasterReset = function() {
|
|||
y: -3.0,
|
||||
z: 0
|
||||
},
|
||||
restitution: 0,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -1,
|
||||
|
|
Loading…
Reference in a new issue