Merge branch 'master' of https://github.com/highfidelity/hifi into lemon
|
@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
|
|||
reverbTime = _zoneReverbSettings[i].reverbTime;
|
||||
wetLevel = _zoneReverbSettings[i].wetLevel;
|
||||
|
||||
// Modulate wet level with distance to wall
|
||||
float MIN_ATTENUATION_DISTANCE = 2.0f;
|
||||
float MAX_ATTENUATION = -12; // dB
|
||||
glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter());
|
||||
float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z);
|
||||
if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) {
|
||||
wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -308,7 +308,7 @@
|
|||
"name": "reverb",
|
||||
"type": "table",
|
||||
"label": "Reverb Settings",
|
||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.",
|
||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
||||
"numbered": true,
|
||||
"columns": [
|
||||
{
|
||||
|
@ -325,9 +325,9 @@
|
|||
},
|
||||
{
|
||||
"name": "wet_level",
|
||||
"label": "Wet Level",
|
||||
"label": "Wet/Dry Mix",
|
||||
"can_set": true,
|
||||
"placeholder": "(in db)"
|
||||
"placeholder": "(in percent)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -10,11 +10,50 @@
|
|||
|
||||
var EventBridge;
|
||||
|
||||
openEventBridge = function(callback) {
|
||||
new QWebChannel(qt.webChannelTransport, function(channel) {
|
||||
console.log("uid " + EventBridgeUid);
|
||||
EventBridge = channel.objects[EventBridgeUid];
|
||||
callback(EventBridge);
|
||||
EventBridgeConnectionProxy = function(parent) {
|
||||
this.parent = parent;
|
||||
this.realSignal = this.parent.realBridge.scriptEventReceived
|
||||
this.webWindowId = this.parent.webWindow.windowId;
|
||||
}
|
||||
|
||||
EventBridgeConnectionProxy.prototype.connect = function(callback) {
|
||||
var that = this;
|
||||
this.realSignal.connect(function(id, message) {
|
||||
if (id === that.webWindowId) { callback(message); }
|
||||
});
|
||||
}
|
||||
|
||||
EventBridgeProxy = function(webWindow) {
|
||||
this.webWindow = webWindow;
|
||||
this.realBridge = this.webWindow.eventBridge;
|
||||
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
|
||||
}
|
||||
|
||||
EventBridgeProxy.prototype.emitWebEvent = function(data) {
|
||||
this.realBridge.emitWebEvent(data);
|
||||
}
|
||||
|
||||
openEventBridge = function(callback) {
|
||||
EVENT_BRIDGE_URI = "ws://localhost:51016";
|
||||
socket = new WebSocket(this.EVENT_BRIDGE_URI);
|
||||
|
||||
socket.onclose = function() {
|
||||
console.error("web channel closed");
|
||||
};
|
||||
|
||||
socket.onerror = function(error) {
|
||||
console.error("web channel error: " + error);
|
||||
};
|
||||
|
||||
socket.onopen = function() {
|
||||
channel = new QWebChannel(socket, function(channel) {
|
||||
console.log("Document url is " + document.URL);
|
||||
var webWindow = channel.objects[document.URL.toLowerCase()];
|
||||
console.log("WebWindow is " + webWindow)
|
||||
eventBridgeProxy = new EventBridgeProxy(webWindow);
|
||||
EventBridge = eventBridgeProxy;
|
||||
if (callback) { callback(eventBridgeProxy); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,17 +4,21 @@
|
|||
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="eventBridgeLoader.js"></script>
|
||||
|
||||
<script>
|
||||
var myBridge;
|
||||
|
||||
window.onload = function() {
|
||||
openEventBridge(function() {
|
||||
EventBridge.scriptEventReceived.connect(function(message) {
|
||||
openEventBridge(function(eventBridge) {
|
||||
myBridge = eventBridge;
|
||||
myBridge.scriptEventReceived.connect(function(message) {
|
||||
console.log("HTML side received message: " + message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testClick = function() {
|
||||
EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]);
|
||||
myBridge.emitWebEvent("HTML side sending message - button click");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -8,14 +8,26 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
|
|||
print("JS Side event received: " + data);
|
||||
});
|
||||
|
||||
var titles = ["A", "B", "C"];
|
||||
var titleIndex = 0;
|
||||
|
||||
Script.setInterval(function() {
|
||||
var message = [ Math.random(), Math.random() ];
|
||||
print("JS Side sending: " + message);
|
||||
webWindow.emitScriptEvent(message);
|
||||
}, 5 * 1000);
|
||||
webWindow.eventBridge.emitScriptEvent("JS Event sent");
|
||||
var size = webWindow.size;
|
||||
var position = webWindow.position;
|
||||
print("Window url: " + webWindow.url)
|
||||
print("Window visible: " + webWindow.visible)
|
||||
print("Window size: " + size.x + "x" + size.y)
|
||||
print("Window pos: " + position.x + "x" + position.y)
|
||||
webWindow.setVisible(!webWindow.visible);
|
||||
webWindow.setTitle(titles[titleIndex]);
|
||||
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
|
||||
titleIndex += 1;
|
||||
titleIndex %= titles.length;
|
||||
}, 2 * 1000);
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
Script.setTimeout(function() {
|
||||
print("Closing script");
|
||||
webWindow.close();
|
||||
webWindow.deleteLater();
|
||||
});
|
||||
|
||||
Script.stop();
|
||||
}, 15 * 1000)
|
||||
|
|
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 51 KiB |
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
|
||||
import "windows" as Windows
|
||||
import "controls" as Controls
|
||||
|
@ -16,22 +15,11 @@ Windows.Window {
|
|||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
property alias source: webview.url
|
||||
property alias webChannel: webview.webChannel
|
||||
// A unique identifier to let the HTML JS find the event bridge
|
||||
// object (our C++ wrapper)
|
||||
property string uid;
|
||||
|
||||
// This is for JS/QML communication, which is unused in a WebWindow,
|
||||
// but not having this here results in spurious warnings about a
|
||||
// missing signal
|
||||
signal sendToScript(var message);
|
||||
|
||||
Controls.WebView {
|
||||
id: webview
|
||||
url: "about:blank"
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
|
||||
Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
|
||||
}
|
||||
} // dialog
|
||||
|
|
|
@ -37,33 +37,14 @@ Windows.Window {
|
|||
Repeater {
|
||||
model: 4
|
||||
Tab {
|
||||
// Force loading of the content even if the tab is not visible
|
||||
// (required for letting the C++ code access the webview)
|
||||
active: true
|
||||
enabled: false
|
||||
enabled: false;
|
||||
// we need to store the original url here for future identification
|
||||
property string originalUrl: "";
|
||||
|
||||
onEnabledChanged: toolWindow.updateVisiblity();
|
||||
Controls.WebView {
|
||||
id: webView;
|
||||
// we need to store the original url here for future identification
|
||||
// A unique identifier to let the HTML JS find the event bridge
|
||||
// object (our C++ wrapper)
|
||||
property string uid;
|
||||
anchors.fill: parent
|
||||
enabled: false
|
||||
|
||||
// This is for JS/QML communication, which is unused in a WebWindow,
|
||||
// but not having this here results in spurious warnings about a
|
||||
// missing signal
|
||||
signal sendToScript(var message);
|
||||
|
||||
onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
|
||||
onEnabledChanged: toolWindow.updateVisiblity();
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
|
||||
webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,23 +113,20 @@ Windows.Window {
|
|||
|
||||
var tab = tabView.getTab(index);
|
||||
tab.title = "";
|
||||
tab.enabled = false;
|
||||
tab.originalUrl = "";
|
||||
tab.item.url = "about:blank";
|
||||
tab.item.enabled = false;
|
||||
tab.enabled = false;
|
||||
}
|
||||
|
||||
function addWebTab(properties) {
|
||||
if (!properties.source) {
|
||||
console.warn("Attempted to open Web Tool Pane without URL");
|
||||
console.warn("Attempted to open Web Tool Pane without URL")
|
||||
return;
|
||||
}
|
||||
|
||||
var existingTabIndex = findIndexForUrl(properties.source);
|
||||
if (existingTabIndex >= 0) {
|
||||
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source);
|
||||
var tab = tabView.getTab(existingTabIndex);
|
||||
return tab.item;
|
||||
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
|
||||
return tabView.getTab(existingTabIndex);
|
||||
}
|
||||
|
||||
var freeTabIndex = findFreeTab();
|
||||
|
@ -157,22 +135,25 @@ Windows.Window {
|
|||
return;
|
||||
}
|
||||
|
||||
var newTab = tabView.getTab(freeTabIndex);
|
||||
newTab.title = properties.title || "Unknown";
|
||||
newTab.originalUrl = properties.source;
|
||||
newTab.item.url = properties.source;
|
||||
newTab.active = true;
|
||||
|
||||
if (properties.width) {
|
||||
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
|
||||
tabView.width = Math.min(Math.max(tabView.width, properties.width),
|
||||
toolWindow.maxSize.x);
|
||||
}
|
||||
|
||||
if (properties.height) {
|
||||
tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
|
||||
tabView.height = Math.min(Math.max(tabView.height, properties.height),
|
||||
toolWindow.maxSize.y);
|
||||
}
|
||||
|
||||
var tab = tabView.getTab(freeTabIndex);
|
||||
tab.title = properties.title || "Unknown";
|
||||
tab.enabled = true;
|
||||
tab.originalUrl = properties.source;
|
||||
|
||||
var result = tab.item;
|
||||
result.enabled = true;
|
||||
result.url = properties.source;
|
||||
return result;
|
||||
console.log("Updating visibility based on child tab added");
|
||||
newTab.enabledChanged.connect(updateVisiblity)
|
||||
updateVisiblity();
|
||||
return newTab
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ WebEngineView {
|
|||
request.openIn(newWindow.webView)
|
||||
}
|
||||
|
||||
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
|
||||
// See https://bugreports.qt.io/browse/QTBUG-49521
|
||||
//profile: desktop.browserProfile
|
||||
|
||||
profile: desktop.browserProfile
|
||||
}
|
||||
|
|
|
@ -231,6 +231,13 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
|
||||
_leftEyePosition = _rightEyePosition = getPosition();
|
||||
_eyePosition = calculateAverageEyePosition();
|
||||
|
||||
if (!billboard && _owningAvatar) {
|
||||
auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
|
||||
if (skeletonModel) {
|
||||
skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Head::calculateMouthShapes() {
|
||||
|
|
|
@ -897,7 +897,9 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
// Scale by proportional differences between avatar and human.
|
||||
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale;
|
||||
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
|
||||
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
|
||||
if (avatarEyeSeparation > 0.0f) {
|
||||
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
// And now we can finally add that offset to the camera.
|
||||
|
|
|
@ -611,7 +611,7 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j
|
|||
return loadNode(rootVal.toObject(), jsonUrl);
|
||||
}
|
||||
|
||||
void AnimNodeLoader::onRequestDone(const QByteArray& data) {
|
||||
void AnimNodeLoader::onRequestDone(const QByteArray data) {
|
||||
auto node = load(data, _url);
|
||||
if (node) {
|
||||
emit success(node);
|
||||
|
|
|
@ -36,7 +36,7 @@ protected:
|
|||
static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl);
|
||||
|
||||
protected slots:
|
||||
void onRequestDone(const QByteArray& data);
|
||||
void onRequestDone(const QByteArray data);
|
||||
void onRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() {
|
|||
_zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime());
|
||||
reverbChanged = true;
|
||||
}
|
||||
//if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) {
|
||||
// _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel());
|
||||
// reverbChanged = true;
|
||||
//}
|
||||
if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) {
|
||||
_zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel());
|
||||
reverbChanged = true;
|
||||
}
|
||||
|
||||
if (_reverbOptions != &_zoneReverbOptions) {
|
||||
_reverbOptions = &_zoneReverbOptions;
|
||||
|
|
|
@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
|
|||
return _originalTexturesMap;
|
||||
}
|
||||
|
||||
QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
|
||||
// Legacy: a ,\n-delimited list of filename:"texturepath"
|
||||
if (*textures.cbegin() != '{') {
|
||||
textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error);
|
||||
QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
|
||||
return _originalTexturesMap;
|
||||
}
|
||||
QJsonObject texturesAsJsonObject = texturesAsJson.object();
|
||||
return texturesAsJsonObject.toVariantMap();
|
||||
return texturesJson.object().toVariantMap();
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::remapTextures() {
|
||||
|
|
|
@ -48,6 +48,8 @@ public:
|
|||
|
||||
virtual ~RenderablePolyVoxEntityItem();
|
||||
|
||||
void initializePolyVox();
|
||||
|
||||
virtual void somethingChangedNotification() {
|
||||
// This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing
|
||||
// this entity comes from the entity-server. It gets called even if nothing has actually changed
|
||||
|
@ -114,17 +116,28 @@ public:
|
|||
virtual void setYPNeighborID(const EntityItemID& yPNeighborID);
|
||||
virtual void setZPNeighborID(const EntityItemID& zPNeighborID);
|
||||
|
||||
virtual void rebakeMesh();
|
||||
|
||||
virtual void updateRegistrationPoint(const glm::vec3& value);
|
||||
|
||||
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
|
||||
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
|
||||
std::function<void(int, int, int, uint8_t)> thunk);
|
||||
|
||||
void setMesh(model::MeshPointer mesh);
|
||||
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
|
||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||
|
||||
uint8_t getVoxelInternal(int x, int y, int z);
|
||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||
|
||||
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
|
||||
|
||||
private:
|
||||
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
|
||||
// may not match _voxelVolumeSize.
|
||||
|
||||
model::MeshPointer _mesh;
|
||||
bool _meshDirty; // does collision-shape need to be recomputed?
|
||||
mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive};
|
||||
bool _meshDirty { true }; // does collision-shape need to be recomputed?
|
||||
bool _meshInitialized { false };
|
||||
|
||||
NetworkTexturePointer _xTexture;
|
||||
NetworkTexturePointer _yTexture;
|
||||
|
@ -135,44 +148,35 @@ private:
|
|||
static gpu::PipelinePointer _pipeline;
|
||||
|
||||
ShapeInfo _shapeInfo;
|
||||
mutable QReadWriteLock _shapeInfoLock;
|
||||
|
||||
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
|
||||
mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData
|
||||
bool _volDataDirty = false; // does getMesh need to be called?
|
||||
int _onCount; // how many non-zero voxels are in _volData
|
||||
|
||||
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
|
||||
int x, int y, int z) const;
|
||||
uint8_t getVoxelInternal(int x, int y, int z);
|
||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||
bool _neighborsNeedUpdate { false };
|
||||
|
||||
bool updateOnCount(int x, int y, int z, uint8_t toValue);
|
||||
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
|
||||
|
||||
// these are run off the main thread
|
||||
void decompressVolumeData();
|
||||
void decompressVolumeDataAsync();
|
||||
void compressVolumeDataAndSendEditPacket();
|
||||
void compressVolumeDataAndSendEditPacketAsync();
|
||||
void getMesh();
|
||||
void getMeshAsync();
|
||||
virtual void getMesh(); // recompute mesh
|
||||
void computeShapeInfoWorker();
|
||||
void computeShapeInfoWorkerAsync();
|
||||
|
||||
QSemaphore _threadRunning{1};
|
||||
|
||||
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
|
||||
EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis
|
||||
EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis
|
||||
EntityItemWeakPointer _yNNeighbor;
|
||||
EntityItemWeakPointer _zNNeighbor;
|
||||
EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis
|
||||
EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis
|
||||
EntityItemWeakPointer _yPNeighbor;
|
||||
EntityItemWeakPointer _zPNeighbor;
|
||||
void clearOutOfDateNeighbors();
|
||||
void cacheNeighbors();
|
||||
void copyUpperEdgesFromNeighbors();
|
||||
void bonkNeighbors();
|
||||
};
|
||||
|
||||
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
|
||||
int x, int y, int z);
|
||||
|
||||
#endif // hifi_RenderablePolyVoxEntityItem_h
|
||||
|
|
|
@ -101,12 +101,15 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
|
|||
prepareEntityForDelete(entity);
|
||||
} else {
|
||||
if (expiry < _nextExpiry) {
|
||||
// remeber the smallest _nextExpiry so we know when to start the next search
|
||||
// remember the smallest _nextExpiry so we know when to start the next search
|
||||
_nextExpiry = expiry;
|
||||
}
|
||||
++itemItr;
|
||||
}
|
||||
}
|
||||
if (_mortalEntities.size() < 1) {
|
||||
_nextExpiry = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,44 +64,47 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) :
|
|||
}
|
||||
|
||||
void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
||||
QWriteLocker(&this->_voxelDataLock);
|
||||
withWriteLock([&] {
|
||||
assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x);
|
||||
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
|
||||
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
|
||||
|
||||
assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x);
|
||||
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
|
||||
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
|
||||
_voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z));
|
||||
if (_voxelVolumeSize.x < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1";
|
||||
_voxelVolumeSize.x = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max";
|
||||
_voxelVolumeSize.x = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
|
||||
_voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z));
|
||||
if (_voxelVolumeSize.x < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1";
|
||||
_voxelVolumeSize.x = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max";
|
||||
_voxelVolumeSize.x = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
if (_voxelVolumeSize.y < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1";
|
||||
_voxelVolumeSize.y = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max";
|
||||
_voxelVolumeSize.y = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
|
||||
if (_voxelVolumeSize.y < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1";
|
||||
_voxelVolumeSize.y = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max";
|
||||
_voxelVolumeSize.y = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
|
||||
if (_voxelVolumeSize.z < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1";
|
||||
_voxelVolumeSize.z = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max";
|
||||
_voxelVolumeSize.z = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
if (_voxelVolumeSize.z < 1) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1";
|
||||
_voxelVolumeSize.z = 1;
|
||||
}
|
||||
if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) {
|
||||
qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max";
|
||||
_voxelVolumeSize.z = MAX_VOXEL_DIMENSION;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const {
|
||||
QWriteLocker locker(&this->_voxelDataLock);
|
||||
return _voxelVolumeSize;
|
||||
glm::vec3 PolyVoxEntityItem::getVoxelVolumeSize() const {
|
||||
glm::vec3 voxelVolumeSize;
|
||||
withReadLock([&] {
|
||||
voxelVolumeSize = _voxelVolumeSize;
|
||||
});
|
||||
return voxelVolumeSize;
|
||||
}
|
||||
|
||||
|
||||
|
@ -226,12 +229,16 @@ void PolyVoxEntityItem::debugDump() const {
|
|||
}
|
||||
|
||||
void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) {
|
||||
QWriteLocker(&this->_voxelDataLock);
|
||||
_voxelData = voxelData;
|
||||
_voxelDataDirty = true;
|
||||
withWriteLock([&] {
|
||||
_voxelData = voxelData;
|
||||
_voxelDataDirty = true;
|
||||
});
|
||||
}
|
||||
|
||||
const QByteArray PolyVoxEntityItem::getVoxelData() const {
|
||||
QReadLocker(&this->_voxelDataLock);
|
||||
return _voxelData;
|
||||
QByteArray voxelDataCopy;
|
||||
withReadLock([&] {
|
||||
voxelDataCopy = _voxelData;
|
||||
});
|
||||
return voxelDataCopy;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual void debugDump() const;
|
||||
|
||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
||||
virtual const glm::vec3& getVoxelVolumeSize() const;
|
||||
virtual glm::vec3 getVoxelVolumeSize() const;
|
||||
|
||||
virtual void setVoxelData(QByteArray voxelData);
|
||||
virtual const QByteArray getVoxelData() const;
|
||||
|
@ -128,12 +128,14 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
|
||||
virtual void rebakeMesh() {};
|
||||
|
||||
void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); }
|
||||
virtual void getMesh() {}; // recompute mesh
|
||||
|
||||
protected:
|
||||
glm::vec3 _voxelVolumeSize; // this is always 3 bytes
|
||||
|
||||
mutable QReadWriteLock _voxelDataLock;
|
||||
QByteArray _voxelData;
|
||||
bool _voxelDataDirty;
|
||||
bool _voxelDataDirty; // _voxelData has changed, things that depend on it should be updated
|
||||
|
||||
PolyVoxSurfaceStyle _voxelSurfaceStyle;
|
||||
|
||||
|
|
|
@ -293,7 +293,7 @@ void NetworkGeometry::requestModel(const QUrl& url) {
|
|||
connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError);
|
||||
}
|
||||
|
||||
void NetworkGeometry::mappingRequestDone(const QByteArray& data) {
|
||||
void NetworkGeometry::mappingRequestDone(const QByteArray data) {
|
||||
assert(_state == RequestMappingState);
|
||||
|
||||
// parse the mapping file
|
||||
|
@ -325,7 +325,7 @@ void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
|
|||
emit onFailure(*this, MappingRequestError);
|
||||
}
|
||||
|
||||
void NetworkGeometry::modelRequestDone(const QByteArray& data) {
|
||||
void NetworkGeometry::modelRequestDone(const QByteArray data) {
|
||||
assert(_state == RequestModelState);
|
||||
|
||||
_state = ParsingModelState;
|
||||
|
|
|
@ -113,10 +113,10 @@ public slots:
|
|||
void textureLoaded(const QWeakPointer<NetworkTexture>& networkTexture);
|
||||
|
||||
protected slots:
|
||||
void mappingRequestDone(const QByteArray& data);
|
||||
void mappingRequestDone(const QByteArray data);
|
||||
void mappingRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
void modelRequestDone(const QByteArray& data);
|
||||
void modelRequestDone(const QByteArray data);
|
||||
void modelRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
void modelParseSuccess(FBXGeometry* geometry);
|
||||
|
|
|
@ -81,6 +81,8 @@ AccountManager::AccountManager() :
|
|||
|
||||
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
|
||||
|
||||
qRegisterMetaType<AccountManagerAuth::Type>();
|
||||
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
}
|
||||
|
||||
|
@ -215,12 +217,13 @@ void AccountManager::sendRequest(const QString& path,
|
|||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
Q_ARG(AccountManagerAuth::Type, AccountManagerAuth::Required),
|
||||
Q_ARG(AccountManagerAuth::Type, authType),
|
||||
Q_ARG(QNetworkAccessManager::Operation, operation),
|
||||
Q_ARG(const JSONCallbackParameters&, callbackParams),
|
||||
Q_ARG(const QByteArray&, dataByteArray),
|
||||
Q_ARG(QHttpMultiPart*, dataMultiPart),
|
||||
Q_ARG(QVariantMap, propertyMap));
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
|
|
@ -198,7 +198,7 @@ public:
|
|||
signals:
|
||||
/// Fired when the resource has been downloaded.
|
||||
/// This can be used instead of downloadFinished to access data before it is processed.
|
||||
void loaded(const QByteArray& request);
|
||||
void loaded(const QByteArray request);
|
||||
|
||||
/// Fired when the resource has finished loading.
|
||||
void finished(bool success);
|
||||
|
|
|
@ -58,8 +58,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
|
||||
// if no callbacks specified, call our owns
|
||||
if (params.isEmpty()) {
|
||||
params.jsonCallbackReceiver = this;
|
||||
params.jsonCallbackMethod = "requestFinished";
|
||||
params.errorCallbackReceiver = this;
|
||||
params.errorCallbackMethod = "requestError";
|
||||
}
|
||||
|
@ -70,10 +68,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
params, NULL, multipart);
|
||||
}
|
||||
|
||||
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
|
||||
// qCDebug(networking) << object;
|
||||
}
|
||||
|
||||
void UserActivityLogger::requestError(QNetworkReply& errorReply) {
|
||||
qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString();
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ public slots:
|
|||
void wentTo(QString destinationType, QString destinationName);
|
||||
|
||||
private slots:
|
||||
void requestFinished(QNetworkReply& requestReply);
|
||||
void requestError(QNetworkReply& errorReply);
|
||||
|
||||
private:
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
@ -27,6 +28,7 @@
|
|||
#include "ControlPacket.h"
|
||||
#include "Packet.h"
|
||||
#include "PacketList.h"
|
||||
#include "../UserActivityLogger.h"
|
||||
#include "Socket.h"
|
||||
|
||||
using namespace udt;
|
||||
|
@ -328,7 +330,39 @@ void SendQueue::run() {
|
|||
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
|
||||
|
||||
// sleep as long as we need until next packet send, if we can
|
||||
const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now());
|
||||
auto now = p_high_resolution_clock::now();
|
||||
auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - now);
|
||||
|
||||
// we're seeing SendQueues sleep for a long period of time here,
|
||||
// which can lock the NodeList if it's attempting to clear connections
|
||||
// for now we guard this by capping the time this thread and sleep for
|
||||
|
||||
const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 };
|
||||
if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) {
|
||||
qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds";
|
||||
qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count();
|
||||
qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta
|
||||
<< "NPT:" << nextPacketTimestamp.time_since_epoch().count()
|
||||
<< "NOW:" << now.time_since_epoch().count();
|
||||
|
||||
// alright, we're in a weird state
|
||||
// we want to know why this is happening so we can implement a better fix than this guard
|
||||
// send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep
|
||||
static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep";
|
||||
|
||||
// setup a json object with the details we want
|
||||
QJsonObject longSleepObject;
|
||||
longSleepObject["timeToSleep"] = qint64(timeToSleep.count());
|
||||
longSleepObject["packetSendPeriod"] = _packetSendPeriod.load();
|
||||
longSleepObject["nextPacketDelta"] = nextPacketDelta;
|
||||
longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count());
|
||||
longSleepObject["then"] = qint64(now.time_since_epoch().count());
|
||||
|
||||
// hopefully send this event using the user activity logger
|
||||
UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject);
|
||||
|
||||
timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
|
|
|
@ -57,16 +57,18 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
|||
}
|
||||
|
||||
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::removeEntityInternal(entity);
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesToAddToPhysics.remove(entity);
|
||||
if (entity->isSimulated()) {
|
||||
EntitySimulation::removeEntityInternal(entity);
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesToAddToPhysics.remove(entity);
|
||||
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
_outgoingChanges.remove(motionState);
|
||||
_entitiesToRemoveFromPhysics.insert(entity);
|
||||
} else {
|
||||
_entitiesToDelete.insert(entity);
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
|
||||
if (motionState) {
|
||||
_outgoingChanges.remove(motionState);
|
||||
_entitiesToRemoveFromPhysics.insert(entity);
|
||||
} else {
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +177,7 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState
|
|||
_entitiesToRelease.insert(entity);
|
||||
}
|
||||
|
||||
if (entity->isSimulated() && entity->isDead()) {
|
||||
if (entity->isDead()) {
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +192,7 @@ void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
|
|||
entity->setPhysicsInfo(nullptr);
|
||||
delete motionState;
|
||||
|
||||
if (entity->isSimulated() && entity->isDead()) {
|
||||
if (entity->isDead()) {
|
||||
_entitiesToDelete.insert(entity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render
|
|||
void CullSpatialSelection::configure(const Config& config) {
|
||||
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
|
||||
_freezeFrustum = config.freezeFrustum;
|
||||
_skipCulling = config.skipCulling;
|
||||
}
|
||||
|
||||
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
|
||||
|
@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
|
|||
// visibility cull if partially selected ( octree cell contianing it was partial)
|
||||
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
|
||||
|
||||
// inside & fit items: easy, just filter
|
||||
{
|
||||
PerformanceTimer perfTimer("insideFitItems");
|
||||
for (auto id : inSelection.insideItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inside & subcell items: filter & distance cull
|
||||
{
|
||||
PerformanceTimer perfTimer("insideSmallItems");
|
||||
for (auto id : inSelection.insideSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.solidAngleTest(itemBound.bound)) {
|
||||
if (_skipCulling) {
|
||||
// inside & fit items: filter only, culling is disabled
|
||||
{
|
||||
PerformanceTimer perfTimer("insideFitItems");
|
||||
for (auto id : inSelection.insideItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & fit items: filter & frustum cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialFitItems");
|
||||
for (auto id : inSelection.partialItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
// inside & subcell items: filter only, culling is disabled
|
||||
{
|
||||
PerformanceTimer perfTimer("insideSmallItems");
|
||||
for (auto id : inSelection.insideSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & subcell items:: filter & frutum cull & solidangle cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialSmallItems");
|
||||
for (auto id : inSelection.partialSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
// partial & fit items: filter only, culling is disabled
|
||||
{
|
||||
PerformanceTimer perfTimer("partialFitItems");
|
||||
for (auto id : inSelection.partialItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & subcell items: filter only, culling is disabled
|
||||
{
|
||||
PerformanceTimer perfTimer("partialSmallItems");
|
||||
for (auto id : inSelection.partialSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// inside & fit items: easy, just filter
|
||||
{
|
||||
PerformanceTimer perfTimer("insideFitItems");
|
||||
for (auto id : inSelection.insideItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inside & subcell items: filter & distance cull
|
||||
{
|
||||
PerformanceTimer perfTimer("insideSmallItems");
|
||||
for (auto id : inSelection.insideSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.solidAngleTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & fit items: filter & frustum cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialFitItems");
|
||||
for (auto id : inSelection.partialItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// partial & subcell items:: filter & frutum cull & solidangle cull
|
||||
{
|
||||
PerformanceTimer perfTimer("partialSmallItems");
|
||||
for (auto id : inSelection.partialSubcellItems) {
|
||||
auto& item = scene->getItem(id);
|
||||
if (_filter.test(item.getKey())) {
|
||||
ItemBound itemBound(id, item.getBound());
|
||||
if (test.frustumTest(itemBound.bound)) {
|
||||
if (test.solidAngleTest(itemBound.bound)) {
|
||||
outItems.emplace_back(itemBound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
details._rendered += (int)outItems.size();
|
||||
|
|
|
@ -70,14 +70,16 @@ namespace render {
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
|
||||
Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling)
|
||||
public:
|
||||
int numItems{ 0 };
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
bool freezeFrustum{ false };
|
||||
bool skipCulling{ false };
|
||||
public slots:
|
||||
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
|
||||
|
||||
void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); }
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
@ -85,6 +87,7 @@ namespace render {
|
|||
class CullSpatialSelection {
|
||||
bool _freezeFrustum{ false }; // initialized by Config
|
||||
bool _justFrozeFrustum{ false };
|
||||
bool _skipCulling{ false };
|
||||
ViewFrustum _frozenFrutstum;
|
||||
public:
|
||||
using Config = CullSpatialSelectionConfig;
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
#include <QtQml/QQmlContext>
|
||||
|
||||
#include <QtWebChannel/QWebChannel>
|
||||
|
||||
#include <QtScript/QScriptContext>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
|
@ -37,29 +35,8 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
|
|||
}
|
||||
|
||||
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
|
||||
_uid = QUuid::createUuid().toString();
|
||||
asQuickItem()->setProperty("uid", _uid);
|
||||
auto webchannelVar = qmlWindow->property("webChannel");
|
||||
_webchannel = qvariant_cast<QWebChannel*>(webchannelVar);
|
||||
Q_ASSERT(_webchannel);
|
||||
_webchannel->registerObject(_uid, this);
|
||||
}
|
||||
|
||||
void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
|
||||
} else {
|
||||
emit scriptEventReceived(scriptMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
|
||||
} else {
|
||||
emit webEventReceived(webMessage);
|
||||
}
|
||||
}
|
||||
|
||||
QString QmlWebWindowClass::getURL() const {
|
||||
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
||||
|
|
|
@ -11,13 +11,10 @@
|
|||
|
||||
#include "QmlWindowClass.h"
|
||||
|
||||
class QWebChannel;
|
||||
|
||||
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
|
||||
class QmlWebWindowClass : public QmlWindowClass {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString url READ getURL CONSTANT)
|
||||
Q_PROPERTY(QString uid READ getUid CONSTANT)
|
||||
|
||||
public:
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
|
@ -26,18 +23,9 @@ public:
|
|||
public slots:
|
||||
QString getURL() const;
|
||||
void setURL(const QString& url);
|
||||
const QString& getUid() const { return _uid; }
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
void emitWebEvent(const QVariant& webMessage);
|
||||
|
||||
signals:
|
||||
void urlChanged();
|
||||
void scriptEventReceived(const QVariant& message);
|
||||
void webEventReceived(const QVariant& message);
|
||||
|
||||
private:
|
||||
QString _uid;
|
||||
QWebChannel* _webchannel { nullptr };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
|
||||
#include "OffscreenUi.h"
|
||||
|
||||
QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
|
||||
static QWebChannel webChannel;
|
||||
static const uint16_t WEB_CHANNEL_PORT = 51016;
|
||||
static std::atomic<int> nextWindowId;
|
||||
static const char* const SOURCE_PROPERTY = "source";
|
||||
static const char* const TITLE_PROPERTY = "title";
|
||||
static const char* const WIDTH_PROPERTY = "width";
|
||||
|
@ -33,6 +37,54 @@ static const char* const HEIGHT_PROPERTY = "height";
|
|||
static const char* const VISIBILE_PROPERTY = "visible";
|
||||
static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
|
||||
|
||||
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
|
||||
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
|
||||
}
|
||||
|
||||
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
|
||||
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
|
||||
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
|
||||
}
|
||||
|
||||
class QmlWebTransport : public QWebChannelAbstractTransport {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
|
||||
// Translate from the websocket layer to the webchannel layer
|
||||
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
|
||||
QJsonParseError error;
|
||||
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
|
||||
if (error.error || !document.isObject()) {
|
||||
qWarning() << "Unable to parse incoming JSON message" << message;
|
||||
return;
|
||||
}
|
||||
emit messageReceived(document.object(), this);
|
||||
});
|
||||
}
|
||||
|
||||
virtual void sendMessage(const QJsonObject &message) override {
|
||||
// Translate from the webchannel layer to the websocket layer
|
||||
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
private:
|
||||
QWebSocket* const _webSocket;
|
||||
};
|
||||
|
||||
|
||||
void QmlWindowClass::setupServer() {
|
||||
if (!_webChannelServer) {
|
||||
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
|
||||
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
|
||||
qFatal("Failed to open web socket server.");
|
||||
}
|
||||
|
||||
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
|
||||
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
||||
QScriptContext* context, QScriptEngine* engine,
|
||||
std::function<QmlWindowClass*(QObject*)> builder)
|
||||
|
@ -116,8 +168,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
}
|
||||
|
||||
offscreenUi->returnFromUiThread([&] {
|
||||
setupServer();
|
||||
retVal = builder(newTab);
|
||||
retVal->_toolWindow = true;
|
||||
registerObject(url.toLower(), retVal);
|
||||
return QVariant();
|
||||
});
|
||||
} else {
|
||||
|
@ -125,8 +179,10 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, qmlSource),
|
||||
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
|
||||
setupServer();
|
||||
retVal = builder(object);
|
||||
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
|
||||
registerObject(url.toLower(), retVal);
|
||||
if (!title.isEmpty()) {
|
||||
retVal->setTitle(title);
|
||||
}
|
||||
|
@ -153,7 +209,10 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
|
|||
});
|
||||
}
|
||||
|
||||
QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) {
|
||||
QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
|
||||
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
|
||||
{
|
||||
qDebug() << "Created window with ID " << _windowId;
|
||||
Q_ASSERT(_qmlWindow);
|
||||
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
|
||||
// Forward messages received from QML on to the script
|
||||
|
@ -169,6 +228,14 @@ QmlWindowClass::~QmlWindowClass() {
|
|||
close();
|
||||
}
|
||||
|
||||
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
|
||||
webChannel.registerObject(name, object);
|
||||
}
|
||||
|
||||
void QmlWindowClass::deregisterObject(QObject* object) {
|
||||
webChannel.deregisterObject(object);
|
||||
}
|
||||
|
||||
QQuickItem* QmlWindowClass::asQuickItem() const {
|
||||
if (_toolWindow) {
|
||||
return DependencyManager::get<OffscreenUi>()->getToolWindow();
|
||||
|
|
|
@ -13,16 +13,38 @@
|
|||
#include <QtCore/QPointer>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtQuick/QQuickItem>
|
||||
#include <QtWebChannel/QWebChannelAbstractTransport>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
class QScriptEngine;
|
||||
class QScriptContext;
|
||||
class QmlWindowClass;
|
||||
class QWebSocketServer;
|
||||
class QWebSocket;
|
||||
|
||||
class QmlScriptEventBridge : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
|
||||
|
||||
public slots :
|
||||
void emitWebEvent(const QString& data);
|
||||
void emitScriptEvent(const QString& data);
|
||||
|
||||
signals:
|
||||
void webEventReceived(const QString& data);
|
||||
void scriptEventReceived(int windowId, const QString& data);
|
||||
|
||||
private:
|
||||
const QmlWindowClass* _webWindow { nullptr };
|
||||
QWebSocket *_socket { nullptr };
|
||||
};
|
||||
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
|
||||
class QmlWindowClass : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
|
||||
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
|
||||
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged)
|
||||
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
|
||||
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
|
||||
|
@ -47,7 +69,8 @@ public slots:
|
|||
|
||||
Q_INVOKABLE void raise();
|
||||
Q_INVOKABLE void close();
|
||||
Q_INVOKABLE QObject* getEventBridge() { return this; };
|
||||
Q_INVOKABLE int getWindowId() const { return _windowId; };
|
||||
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
|
||||
|
||||
// Scripts can use this to send a message to the QML object
|
||||
void sendToQml(const QVariant& message);
|
||||
|
@ -69,12 +92,18 @@ protected:
|
|||
static QScriptValue internalConstructor(const QString& qmlSource,
|
||||
QScriptContext* context, QScriptEngine* engine,
|
||||
std::function<QmlWindowClass*(QObject*)> function);
|
||||
static void setupServer();
|
||||
static void registerObject(const QString& name, QObject* object);
|
||||
static void deregisterObject(QObject* object);
|
||||
static QWebSocketServer* _webChannelServer;
|
||||
|
||||
QQuickItem* asQuickItem() const;
|
||||
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
|
||||
|
||||
// FIXME needs to be initialized in the ctor once we have support
|
||||
// for tool window panes in QML
|
||||
bool _toolWindow { false };
|
||||
const int _windowId;
|
||||
QPointer<QObject> _qmlWindow;
|
||||
QString _source;
|
||||
};
|
||||
|
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 78 KiB |
|
@ -44,6 +44,14 @@ ready = function() {
|
|||
var domainServer = remote.getGlobal('domainServer');
|
||||
var acMonitor = remote.getGlobal('acMonitor');
|
||||
|
||||
var pendingLines = {
|
||||
'ds': new Array(),
|
||||
'ac': new Array()
|
||||
};
|
||||
|
||||
var UPDATE_INTERVAL = 16; // Update log at ~60 fps
|
||||
var interval = setInterval(flushPendingLines, UPDATE_INTERVAL);
|
||||
|
||||
var logWatchers = {
|
||||
'ds': {
|
||||
},
|
||||
|
@ -83,7 +91,7 @@ ready = function() {
|
|||
var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 });
|
||||
|
||||
logTail.on('line', function(msg) {
|
||||
appendLogMessage(msg, stream);
|
||||
pendingLines[stream].push(msg);
|
||||
});
|
||||
|
||||
logTail.on('error', function(error) {
|
||||
|
@ -107,6 +115,7 @@ ready = function() {
|
|||
}
|
||||
|
||||
window.onbeforeunload = function(e) {
|
||||
clearInterval(interval);
|
||||
domainServer.removeListener('logs-updated', updateLogFiles);
|
||||
acMonitor.removeListener('logs-updated', updateLogFiles);
|
||||
};
|
||||
|
@ -164,14 +173,23 @@ ready = function() {
|
|||
return !filter || message.toLowerCase().indexOf(filter) >= 0;
|
||||
}
|
||||
|
||||
function appendLogMessage(msg, name) {
|
||||
function appendLogMessages(name) {
|
||||
var array = pendingLines[name];
|
||||
if (array.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (array.length > maxLogLines) {
|
||||
array = array.slice(-maxLogLines);
|
||||
}
|
||||
|
||||
console.log(name, array.length);
|
||||
|
||||
var id = name == "ds" ? "domain-server" : "assignment-client";
|
||||
var $pidLog = $('#' + id);
|
||||
|
||||
var size = ++tabStates[id].size;
|
||||
var size = tabStates[id].size + array.length;
|
||||
if (size > maxLogLines) {
|
||||
$pidLog.find('div.log-line:first').remove();
|
||||
removed = true;
|
||||
$pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove();
|
||||
}
|
||||
|
||||
var wasAtBottom = false;
|
||||
|
@ -179,17 +197,25 @@ ready = function() {
|
|||
wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height());
|
||||
}
|
||||
|
||||
var $logLine = $('<div class="log-line">').text(msg);
|
||||
if (!shouldDisplayLogMessage(msg)) {
|
||||
$logLine.hide();
|
||||
for (line in array) {
|
||||
var $logLine = $('<div class="log-line">').text(array[line]);
|
||||
if (!shouldDisplayLogMessage(array[line])) {
|
||||
$logLine.hide();
|
||||
}
|
||||
|
||||
$pidLog.append($logLine);
|
||||
}
|
||||
|
||||
$pidLog.append($logLine);
|
||||
delete pendingLines[name];
|
||||
pendingLines[name] = new Array();
|
||||
|
||||
if (wasAtBottom) {
|
||||
$pidLog.scrollTop($pidLog[0].scrollHeight);
|
||||
}
|
||||
|
||||
}
|
||||
function flushPendingLines() {
|
||||
appendLogMessages('ds');
|
||||
appendLogMessages('ac');
|
||||
}
|
||||
|
||||
// handle filtering of table rows on input change
|
||||
|
|