Merge branch '20660' of github.com:thoys/hifi into 20660

Conflicts:
	interface/src/Util.cpp
This commit is contained in:
Nex Pro 2015-08-23 16:30:46 +01:00
commit ad315cf68a
165 changed files with 2849 additions and 3062 deletions

View file

@ -213,3 +213,12 @@ endif ()
if (ANDROID OR DESKTOP_GVR)
add_subdirectory(gvr-interface)
endif ()
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
SET( HIFI_MEMORY_DEBUGGING true )
endif ()
if (HIFI_MEMORY_DEBUGGING)
if (UNIX)
MESSAGE("-- Memory debugging is enabled")
endif (UNIX)
endif ()

View file

@ -17,4 +17,8 @@ if (UNIX)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
endif (UNIX)
copy_dlls_beside_windows_executable()
include_application_version()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -129,6 +129,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
packetReceiver.registerListener(PacketType::CreateAssignment, this, "handleCreateAssignmentPacket");
packetReceiver.registerListener(PacketType::StopNode, this, "handleStopNodePacket");
}
void AssignmentClient::stopAssignmentClient() {
qDebug() << "Forced stop of assignment-client.";
@ -172,7 +173,6 @@ void AssignmentClient::aboutToQuit() {
qInstallMessageHandler(0);
}
void AssignmentClient::setUpStatusToMonitor() {
// send a stats packet every 1 seconds
connect(&_statsTimerACM, &QTimer::timeout, this, &AssignmentClient::sendStatusPacketToACM);
@ -217,7 +217,6 @@ void AssignmentClient::sendAssignmentRequest() {
qDebug() << "Failed to read local assignment server port from shared memory"
<< "- will send assignment request to previous assignment server socket.";
}
}
nodeList->sendAssignment(_requestAssignment);

View file

@ -12,6 +12,7 @@
#include <QCommandLineParser>
#include <QThread>
#include <ApplicationVersion.h>
#include <LogHandler.h>
#include <SharedUtil.h>
#include <HifiConfigVariantMap.h>
@ -40,6 +41,7 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("assignment-client");
setApplicationName(BUILD_VERSION);
// use the verbose message handler in Logging
qInstallMessageHandler(LogHandler::verboseMessageHandler);
@ -93,10 +95,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
Q_UNREACHABLE();
}
const QVariantMap argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
unsigned int numForks = 0;
if (parser.isSet(numChildsOption)) {
numForks = parser.value(numChildsOption).toInt();
@ -139,7 +139,6 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
assignmentPool = parser.value(poolOption);
}
QUuid walletUUID;
if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) {
walletUUID = argumentVariantMap.value(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION).toString();

View file

@ -203,7 +203,7 @@ void AssignmentClientMonitor::checkSpares() {
void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<NLPacket> packet) {
// read out the sender ID
QUuid senderID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID));
QUuid senderID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
auto nodeList = DependencyManager::get<NodeList>();

View file

@ -90,7 +90,7 @@ int AudioMixerClientData::parseData(NLPacket& packet) {
// grab the stream identifier for this injected audio
packet.seek(sizeof(quint16));
QUuid streamIdentifier = QUuid::fromRfc4122(packet.read(NUM_BYTES_RFC4122_UUID));
QUuid streamIdentifier = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
bool isStereo;
packet.readPrimitive(&isStereo);

View file

@ -15,7 +15,7 @@
int AvatarMixerClientData::parseData(NLPacket& packet) {
// compute the offset to the data payload
return _avatar.parseDataFromBuffer(packet.read(packet.bytesLeftToRead()));
return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead()));
}
bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() {

View file

@ -971,12 +971,7 @@ void OctreeServer::readConfiguration() {
strcpy(_persistFilename, qPrintable(persistFilename));
qDebug("persistFilename=%s", _persistFilename);
QString persistAsFileType;
if (!readOptionString(QString("persistAsFileType"), settingsSectionObject, persistAsFileType)) {
persistAsFileType = "svo";
}
_persistAsFileType = persistAsFileType;
qDebug() << "persistAsFileType=" << _persistAsFileType;
_persistAsFileType = "json.gz";
_persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL;
readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval);

View file

@ -1,11 +1,9 @@
//
// InterfaceVersion.h
// interface/src
// ApplicationVersion.h.in
// cmake/macros
//
// Created by Leonardo Murillo on 12/16/13.
// Copyright 2013 High Fidelity, Inc.
//
// Declaration of version and build data
// Created by Leonardo Murillo on 8/13/15.
// Copyright 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

View file

@ -0,0 +1,22 @@
#
# IncludeApplicationVersion.cmake
# cmake/macros
#
# Created by Leonardo Murillo on 07/14/2015.
# Copyright 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
#
macro(INCLUDE_APPLICATION_VERSION)
if (DEFINED ENV{JOB_ID})
set (BUILD_SEQ $ENV{JOB_ID})
elseif (DEFINED ENV{ghprbPullId})
set (BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}")
else ()
set(BUILD_SEQ "dev")
endif ()
configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${PROJECT_BINARY_DIR}/includes/ApplicationVersion.h")
include_directories("${PROJECT_BINARY_DIR}/includes")
endmacro(INCLUDE_APPLICATION_VERSION)

View file

@ -0,0 +1,21 @@
#
# MemoryDebugger.cmake
#
# Copyright 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
#
macro(SETUP_MEMORY_DEBUGGER)
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
SET( HIFI_MEMORY_DEBUGGING true )
endif ()
if (HIFI_MEMORY_DEBUGGING)
if (UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address")
endif (UNIX)
endif ()
endmacro(SETUP_MEMORY_DEBUGGER)

View file

@ -1,5 +1,7 @@
set(TARGET_NAME domain-server)
setup_memory_debugger()
if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32)
set(_SHOULD_SYMLINK_RESOURCES TRUE)
else ()
@ -31,4 +33,5 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
include_application_version()
copy_dlls_beside_windows_executable()

View file

@ -371,30 +371,8 @@
"name": "persistFilename",
"label": "Entities Filename",
"help": "the path to the file entities are stored in. Make sure the path exists.",
"placeholder": "resources/models.svo",
"default": "resources/models.svo",
"advanced": true
},
{
"name": "persistAsFileType",
"label": "File format for entity server's persistent data",
"help": "This defines how the entity server will save entities to disk.",
"default": "svo",
"type": "select",
"options": [
{
"value": "svo",
"label": "Entity server persists data as SVO"
},
{
"value": "json",
"label": "Entity server persists data as JSON"
},
{
"value": "json.gz",
"label": "Entity server persists data as gzipped JSON"
}
],
"placeholder": "resources/models.json.gz",
"default": "resources/models.json.gz",
"advanced": true
},
{

View file

@ -9,6 +9,7 @@
<thead>
<tr>
<th>Type</th>
<th>Version</th>
<th>UUID</th>
<th>Pool</th>
<th>Username</th>
@ -24,6 +25,7 @@
<% _.each(nodes, function(node, node_index){ %>
<tr>
<td><%- node.type %></td>
<td><%- node.version %></td>
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
<td><%- node.pool %></td>
<td><%- node.username %></td>
@ -75,4 +77,4 @@
<!--#include file="footer.html"-->
<script src='js/underscore-min.js'></script>
<script src='js/tables.js'></script>
<!--#include file="page-end.html"-->
<!--#include file="page-end.html"-->

View file

@ -24,6 +24,7 @@
#include <QUrlQuery>
#include <AccountManager.h>
#include <ApplicationVersion.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
#include <JSONBreakableMarshal.h>
@ -75,6 +76,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
setApplicationVersion(BUILD_VERSION);
QSettings::setDefaultFormat(QSettings::IniFormat);
// make sure we have a fresh AccountManager instance
@ -738,6 +740,7 @@ void DomainServer::processConnectRequestPacket(QSharedPointer<NLPacket> packet)
if (isAssignment) {
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID());
nodeData->setNodeVersion(pendingAssigneeData->getNodeVersion());
// always allow assignment clients to create and destroy entities
newNode->setCanAdjustLocks(true);
@ -1168,7 +1171,8 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packe
// add the information for that deployed assignment to the hash of pending assigned nodes
PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(),
requestAssignment.getWalletUUID());
requestAssignment.getWalletUUID(),
requestAssignment.getNodeVersion());
_pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData);
} else {
if (requestAssignment.getType() != Assignment::AgentType
@ -1478,7 +1482,7 @@ const char JSON_KEY_POOL[] = "pool";
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
const char JSON_KEY_USERNAME[] = "username";
const char JSON_KEY_VERSION[] = "version";
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
QJsonObject nodeJson;
@ -1505,6 +1509,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
nodeJson[JSON_KEY_VERSION] = nodeData->getNodeVersion();
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) {
@ -1527,7 +1532,6 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
}
const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment";
QString pathForAssignmentScript(const QUuid& assignmentUUID) {
QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION);
newPath += "/scripts/";
@ -1537,7 +1541,6 @@ QString pathForAssignmentScript(const QUuid& assignmentUUID) {
}
const QString URI_OAUTH = "/oauth";
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
const QString JSON_MIME_TYPE = "application/json";
@ -2024,8 +2027,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
}
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll());
@ -2042,7 +2043,6 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
}
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
Headers cookieHeaders;

View file

@ -50,6 +50,10 @@ public:
const NodeSet& getNodeInterestSet() const { return _nodeInterestSet; }
void setNodeInterestSet(const NodeSet& nodeInterestSet) { _nodeInterestSet = nodeInterestSet; }
void setNodeVersion(const QString& nodeVersion) { _nodeVersion = nodeVersion; }
const QString& getNodeVersion() { return _nodeVersion; }
private:
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
@ -62,6 +66,7 @@ private:
HifiSockAddr _sendingSockAddr;
bool _isAuthenticated;
NodeSet _nodeInterestSet;
QString _nodeVersion;
};
#endif // hifi_DomainServerNodeData_h

View file

@ -11,9 +11,10 @@
#include "PendingAssignedNodeData.h"
PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) :
PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion) :
_assignmentUUID(assignmentUUID),
_walletUUID(walletUUID)
_walletUUID(walletUUID),
_nodeVersion(nodeVersion)
{
}

View file

@ -18,16 +18,20 @@
class PendingAssignedNodeData : public QObject {
Q_OBJECT
public:
PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID);
PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID, const QString& nodeVersion);
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
const QUuid& getWalletUUID() const { return _walletUUID; }
const QString& getNodeVersion() const { return _nodeVersion; }
private:
QUuid _assignmentUUID;
QUuid _walletUUID;
QString _nodeVersion;
};
#endif // hifi_PendingAssignedNodeData_h

View file

@ -16,6 +16,4 @@ Script.load("notifications.js");
Script.load("users.js");
Script.load("grab.js");
Script.load("directory.js");
Script.load("mouseLook.js");
Script.load("hmdControls.js");
Script.load("dialTone.js");

View file

@ -1043,17 +1043,23 @@ function getPositionToCreateEntity() {
var placementPosition = Vec3.sum(Camera.position, offset);
var cameraPosition = Camera.position;
var HALF_TREE_SCALE = 16384;
var cameraOutOfBounds = cameraPosition.x < 0 || cameraPosition.y < 0 || cameraPosition.z < 0;
var placementOutOfBounds = placementPosition.x < 0 || placementPosition.y < 0 || placementPosition.z < 0;
var cameraOutOfBounds = Math.abs(cameraPosition.x) > HALF_TREE_SCALE
|| Math.abs(cameraPosition.y) > HALF_TREE_SCALE
|| Math.abs(cameraPosition.z) > HALF_TREE_SCALE;
var placementOutOfBounds = Math.abs(placementPosition.x) > HALF_TREE_SCALE
|| Math.abs(placementPosition.y) > HALF_TREE_SCALE
|| Math.abs(placementPosition.z) > HALF_TREE_SCALE;
if (cameraOutOfBounds && placementOutOfBounds) {
return null;
}
placementPosition.x = Math.max(0, placementPosition.x);
placementPosition.y = Math.max(0, placementPosition.y);
placementPosition.z = Math.max(0, placementPosition.z);
placementPosition.x = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.x));
placementPosition.y = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.y));
placementPosition.z = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.z));
return placementPosition;
}

View file

@ -1072,6 +1072,7 @@
<option value='0'>marching cubes</option>
<option value='1'>cubic</option>
<option value='2'>edged cubic</option>
<option value='3'>edged marching cubes</option>
</select>
</div>

View file

@ -362,8 +362,11 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
this.fractionKey = optionalPersistenceKey + '.fraction';
this.save = function () {
var screenSize = Controller.getViewportDimensions();
var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y};
Settings.setValue(this.fractionKey, JSON.stringify(fraction));
if (screenSize.x > 0 && screenSize.y > 0) {
// Guard against invalid screen size that can occur at shut-down.
var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y};
Settings.setValue(this.fractionKey, JSON.stringify(fraction));
}
}
} else {
this.save = function () { }; // Called on move. Can be overriden or extended by clients.

View file

@ -12,6 +12,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var lineEntityID = null;
var sphereEntityID = null;
var lineIsRezzed = false;
var BUTTON_SIZE = 32;
@ -56,16 +57,24 @@ function nearLinePoint(targetPosition) {
function removeLine() {
if (lineIsRezzed) {
Entities.deleteEntity(lineEntityID);
lineEntityID = null;
lineIsRezzed = false;
}
if (lineIsRezzed) {
Entities.deleteEntity(lineEntityID);
if (sphereEntityID) {
Entities.deleteEntity(sphereEntityID);
}
lineEntityID = null;
sphereEntityID = null;
lineIsRezzed = false;
}
}
function createOrUpdateLine(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
if (sphereEntityID) {
Entities.deleteEntity(sphereEntityID);
sphereEntityID = null;
}
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
var props = Entities.getEntityProperties(intersection.entityID);
@ -78,7 +87,6 @@ function createOrUpdateLine(event) {
position: MyAvatar.position,
lifetime: 15 + props.lifespan // renew lifetime
});
// Entities.setAllPoints(lineEntityID, points);
} else {
lineIsRezzed = true;
lineEntityID = Entities.addEntity({
@ -90,6 +98,15 @@ function createOrUpdateLine(event) {
lifetime: 15 // if someone crashes while pointing, don't leave the line there forever.
});
}
sphereEntityID = Entities.addEntity({
type: "Sphere",
position: intersection.intersection,
ignoreForCollisions: 1,
dimensions: { x: 0.6, y: 0.6, z: 0.6 },
color: { red: 0, green: 255, blue: 0 },
lifetime: 15 // if someone crashes while pointing, don't leave the line there forever.
});
} else {
removeLine();
}

View file

@ -0,0 +1,82 @@
//
// basketball.js
// examples
//
// Created by Philip Rosedale on August 20, 2015
// Copyright 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
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx";
var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav";
var basketball = null;
var originalPosition = null;
var hasMoved = false;
var GRAVITY = -9.8;
var DISTANCE_IN_FRONT_OF_ME = 1.0;
var START_MOVE = 0.01;
var DIAMETER = 0.30;
function makeBasketball() {
var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation,
{ x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME }));
var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0));
basketball = Entities.addEntity({
type: "Model",
position: position,
rotation: rotation,
dimensions: { x: DIAMETER,
y: DIAMETER,
z: DIAMETER },
collisionsWillMove: true,
collisionSoundURL: collisionSoundURL,
modelURL: basketballURL,
restitution: 1.0,
linearDamping: 0.00001,
shapeType: "sphere"
});
originalPosition = position;
}
function update() {
if (!basketball) {
makeBasketball();
} else {
var newProperties = Entities.getEntityProperties(basketball);
var moved = Vec3.length(Vec3.subtract(originalPosition, newProperties.position));
if (!hasMoved && (moved > START_MOVE)) {
hasMoved = true;
Entities.editEntity(basketball, { gravity: {x: 0, y: GRAVITY, z: 0 }});
}
var MAX_DISTANCE = 10.0;
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, newProperties.position));
if (distance > MAX_DISTANCE) {
deleteStuff();
}
}
}
function scriptEnding() {
deleteStuff();
}
function deleteStuff() {
if (basketball != null) {
Entities.deleteEntity(basketball);
basketball = null;
hasMoved = false;
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

201
examples/toys/grenade.js Normal file
View file

@ -0,0 +1,201 @@
//
// Grenade.js
// examples
//
// Created by Philip Rosedale on August 20, 2015
// Copyright 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
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var grenadeURL = HIFI_PUBLIC_BUCKET + "models/props/grenade/grenade.fbx";
var fuseSoundURL = HIFI_PUBLIC_BUCKET + "sounds/burningFuse.wav";
var boomSoundURL = HIFI_PUBLIC_BUCKET + "sounds/explosion.wav";
var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0);
var audioOptions = {
volume: 0.5,
loop: true
}
var injector = null;
var fuseSound = SoundCache.getSound(fuseSoundURL, audioOptions.isStereo);
var boomSound = SoundCache.getSound(boomSoundURL, audioOptions.isStereo);
var grenade = null;
var particles = null;
var properties = null;
var originalPosition = null;
var isGrenade = false;
var isBurning = false;
var animationSettings = JSON.stringify({
running: true,
loop: true
});
var explodeAnimationSettings = JSON.stringify({
running: true,
loop: false
});
var GRAVITY = -9.8;
var TIME_TO_EXPLODE = 2500;
var DISTANCE_IN_FRONT_OF_ME = 1.0;
function makeGrenade() {
var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation,
{ x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME }));
var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0));
grenade = Entities.addEntity({
type: "Model",
position: position,
rotation: rotation,
dimensions: { x: 0.09,
y: 0.20,
z: 0.09 },
collisionsWillMove: true,
modelURL: grenadeURL,
shapeType: "box"
});
properties = Entities.getEntityProperties(grenade);
audioOptions.position = position;
audioOptions.orientation = rotation;
originalPosition = position;
}
function update() {
if (!grenade) {
makeGrenade();
} else {
var newProperties = Entities.getEntityProperties(grenade);
if (!isBurning) {
// If moved, start fuse
var FUSE_START_MOVE = 0.01;
if (Vec3.length(Vec3.subtract(newProperties.position, originalPosition)) > FUSE_START_MOVE) {
isBurning = true;
// Create fuse particles
particles = Entities.addEntity({
type: "ParticleEffect",
animationSettings: animationSettings,
position: newProperties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 100,
emitStrength: 2.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
color: { red: 200, green: 0, blue: 0 },
lifespan: 10.0,
visible: true,
locked: false
});
// Start fuse sound
injector = Audio.playSound(fuseSound, audioOptions);
// Start explosion timer
Script.setTimeout(boom, TIME_TO_EXPLODE);
originalPosition = newProperties.position;
// Add gravity
Entities.editEntity(grenade, { gravity: {x: 0, y: GRAVITY, z: 0 }});
}
}
if (newProperties.type === "Model") {
if (newProperties.position != properties.position) {
audioOptions.position = newProperties.position;
}
if (newProperties.orientation != properties.orientation) {
audioOptions.orientation = newProperties.orientation;
}
properties = newProperties;
// Update sound location if playing
if (injector) {
injector.options = audioOptions;
}
if (particles) {
Entities.editEntity(particles, { position: newProperties.position });
}
} else {
grenade = null;
Script.update.disconnect(update);
Script.scriptEnding.connect(scriptEnding);
scriptEnding();
Script.stop();
}
}
}
function boom() {
injector.stop();
isBurning = false;
var audioOptions = {
position: properties.position,
volume: 0.75,
loop: false
}
Audio.playSound(boomSound, audioOptions);
Entities.addEntity({
type: "ParticleEffect",
animationSettings: explodeAnimationSettings,
position: properties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 200,
emitStrength: 3.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
color: { red: 255, green: 255, blue: 0 },
lifespan: 2.0,
visible: true,
lifetime: 2,
locked: false
});
var BLAST_RADIUS = 20.0;
var LIFT_DEPTH = 2.0;
var epicenter = properties.position;
epicenter.y -= LIFT_DEPTH;
blowShitUp(epicenter, BLAST_RADIUS);
deleteStuff();
}
function blowShitUp(position, radius) {
var stuff = Entities.findEntities(position, radius);
var numMoveable = 0;
var STRENGTH = 3.5;
var SPIN_RATE = 20.0;
for (var i = 0; i < stuff.length; i++) {
var properties = Entities.getEntityProperties(stuff[i]);
if (properties.collisionsWillMove) {
var diff = Vec3.subtract(properties.position, position);
var distance = Vec3.length(diff);
var velocity = Vec3.sum(properties.velocity, Vec3.multiply(STRENGTH * 1.0 / distance, Vec3.normalize(diff)));
var angularVelocity = { x: Math.random() * SPIN_RATE, y: Math.random() * SPIN_RATE, z: Math.random() * SPIN_RATE };
angularVelocity = Vec3.multiply( 1.0 / distance, angularVelocity);
Entities.editEntity(stuff[i], { velocity: velocity,
angularVelocity: angularVelocity });
}
}
}
function scriptEnding() {
deleteStuff();
}
function deleteStuff() {
if (grenade != null) {
Entities.deleteEntity(grenade);
grenade = null;
}
if (particles != null) {
Entities.deleteEntity(particles);
particles = null;
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -183,6 +183,24 @@ var CHECK_MARK_COLOR = {
this.onValueChanged(resetValue);
};
Slider.prototype.setMinValue = function(minValue) {
var currentValue = this.getValue();
this.minValue = minValue;
this.setValue(currentValue);
};
Slider.prototype.getMinValue = function() {
return this.minValue;
};
Slider.prototype.setMaxValue = function(maxValue) {
var currentValue = this.getValue();
this.maxValue = maxValue;
this.setValue(currentValue);
};
Slider.prototype.getMaxValue = function() {
return this.maxValue;
};
Slider.prototype.onValueChanged = function(value) {};
Slider.prototype.getHeight = function() {
@ -1396,6 +1414,14 @@ var CHECK_MARK_COLOR = {
return null;
};
Panel.prototype.getWidget = function(name) {
var item = this.items[name];
if (item != null) {
return item.widget;
}
return null;
};
Panel.prototype.update = function(name) {
var item = this.items[name];
if (item != null) {

View file

@ -12,59 +12,55 @@ Script.include("cookies.js");
var panel = new Panel(10, 100);
panel.newSlider("Num Feed Opaques", 0, 1000,
function(value) { },
function() { return Scene.getEngineNumFeedOpaqueItems(); },
function(value) { return (value); }
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
this.subPanel = panel.newSubPanel(name);
this.subPanel.newSlider("Num Feed", 0, 1,
function(value) { },
feedGetter,
function(value) { return (value); });
this.subPanel.newSlider("Num Drawn", 0, 1,
function(value) { },
drawGetter,
function(value) { return (value); });
this.subPanel.newSlider("Max Drawn", -1, 1,
capSetter,
capGetter,
function(value) { return (value); });
this.update = function () {
var numFeed = this.subPanel.get("Num Feed");
this.subPanel.set("Num Feed", numFeed);
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
var numMax = Math.max(numFeed, 1);
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques",
function () { return Scene.getEngineNumFeedOpaqueItems(); },
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
);
panel.newSlider("Num Drawn Opaques", 0, 1000,
function(value) { },
function() { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { return (value); }
var transparentsCounter = new CounterWidget(panel, "Transparents",
function () { return Scene.getEngineNumFeedTransparentItems(); },
function () { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
);
panel.newSlider("Max Drawn Opaques", -1, 1000,
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function() { return Scene.getEngineMaxDrawnOpaqueItems(); },
function(value) { return (value); }
var overlaysCounter = new CounterWidget(panel, "Overlays",
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
);
panel.newSlider("Num Feed Transparents", 0, 100,
function(value) { },
function() { return Scene.getEngineNumFeedTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Drawn Transparents", 0, 100,
function(value) { },
function() { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Max Drawn Transparents", -1, 100,
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function() { return Scene.getEngineMaxDrawnTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Feed Overlay3Ds", 0, 100,
function(value) { },
function() { return Scene.getEngineNumFeedOverlay3DItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Drawn Overlay3Ds", 0, 100,
function(value) { },
function() { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { return (value); }
);
panel.newSlider("Max Drawn Overlay3Ds", -1, 100,
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function() { return Scene.getEngineMaxDrawnOverlay3DItems(); },
function(value) { return (value); }
);
panel.newCheckbox("Display status",
function(value) { Scene.setEngineDisplayItemStatus(value); },
@ -75,31 +71,9 @@ panel.newCheckbox("Display status",
var tickTackPeriod = 500;
function updateCounters() {
var numFeedOpaques = panel.get("Num Feed Opaques");
var numFeedTransparents = panel.get("Num Feed Transparents");
var numFeedOverlay3Ds = panel.get("Num Feed Overlay3Ds");
panel.set("Num Feed Opaques", numFeedOpaques);
panel.set("Num Drawn Opaques", panel.get("Num Drawn Opaques"));
panel.set("Num Feed Transparents", numFeedTransparents);
panel.set("Num Drawn Transparents", panel.get("Num Drawn Transparents"));
panel.set("Num Feed Overlay3Ds", numFeedOverlay3Ds);
panel.set("Num Drawn Overlay3Ds", panel.get("Num Drawn Overlay3Ds"));
var numMax = Math.max(numFeedOpaques * 1.2, 1);
panel.getWidget("Num Feed Opaques").setMaxValue(numMax);
panel.getWidget("Num Drawn Opaques").setMaxValue(numMax);
panel.getWidget("Max Drawn Opaques").setMaxValue(numMax);
numMax = Math.max(numFeedTransparents * 1.2, 1);
panel.getWidget("Num Feed Transparents").setMaxValue(numMax);
panel.getWidget("Num Drawn Transparents").setMaxValue(numMax);
panel.getWidget("Max Drawn Transparents").setMaxValue(numMax);
numMax = Math.max(numFeedOverlay3Ds * 1.2, 1);
panel.getWidget("Num Feed Overlay3Ds").setMaxValue(numMax);
panel.getWidget("Num Drawn Overlay3Ds").setMaxValue(numMax);
panel.getWidget("Max Drawn Overlay3Ds").setMaxValue(numMax);
opaquesCounter.update();
transparentsCounter.update();
overlaysCounter.update();
}
Script.setInterval(updateCounters, tickTackPeriod);

View file

@ -1,32 +1,37 @@
var controlHeld = false;
var shiftHeld = false;
function attemptVoxelChange(intersection) {
var ids = Entities.findEntities(intersection.intersection, 10);
var success = false;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
if (controlHeld) {
// hold control to erase a sphere
if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 0)) {
success = true;
}
} else if (shiftHeld) {
// hold shift to set all voxels to 255
if (Entities.setAllVoxels(id, 255)) {
success = true;
}
} else {
// no modifier key means to add a sphere
if (Entities.setVoxelSphere(id, intersection.intersection, 1.0, 255)) {
success = true;
}
}
}
return success;
function floorVector(v) {
return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)};
}
function attemptVoxelChange(pickRayDir, intersection) {
var properties = Entities.getEntityProperties(intersection.entityID);
if (properties.type != "PolyVox") {
return false;
}
var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection);
voxelPosition = Vec3.subtract(voxelPosition, {x: 0.5, y: 0.5, z: 0.5});
var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir);
pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace);
if (controlHeld) {
// hold control to erase a voxel
var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0);
} else if (shiftHeld) {
// hold shift to set all voxels to 255
return Entities.setAllVoxels(intersection.entityID, 255);
} else {
// no modifier key to add a voxel
var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255);
}
// Entities.setVoxelSphere(id, intersection.intersection, radius, 0)
}
function mousePressEvent(event) {
if (!event.isLeftButton) {
@ -36,10 +41,8 @@ function mousePressEvent(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking
// we've used a picking ray to decide where to add the new sphere of voxels. If we pick nothing
// or if we pick a non-PolyVox entity, we fall through to the next picking attempt.
if (intersection.intersects) {
if (attemptVoxelChange(intersection)) {
if (attemptVoxelChange(pickRay.direction, intersection)) {
return;
}
}
@ -48,7 +51,7 @@ function mousePressEvent(event) {
// bounding box, instead.
intersection = Entities.findRayIntersection(pickRay, false); // bounding box picking
if (intersection.intersects) {
attemptVoxelChange(intersection);
attemptVoxelChange(pickRay.direction, intersection);
}
}

View file

@ -88,4 +88,6 @@ if (ANDROID)
endif (ANDROID)
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -1,9 +1,11 @@
set(TARGET_NAME ice-server)
setup_memory_debugger()
# setup the project and link required Qt modules
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared)
copy_dlls_beside_windows_executable()
copy_dlls_beside_windows_executable()

View file

@ -14,20 +14,12 @@ endforeach()
find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5LinguistToolsMacros)
if (DEFINED ENV{JOB_ID})
set(BUILD_SEQ $ENV{JOB_ID})
elseif (DEFINED ENV{ghprbPullId})
set(BUILD_SEQ "PR: $ENV{ghprbPullId} - Commit: $ENV{ghprbActualCommit}")
else ()
set(BUILD_SEQ "dev")
endif ()
if (WIN32)
add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h
add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines
endif()
configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVersion.h")
include_application_version()
# grab the implementation and header files from src dirs
file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h")
@ -174,7 +166,7 @@ if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE)
endif ()
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
include_directories("${PROJECT_SOURCE_DIR}/src")
target_link_libraries(
${TARGET_NAME}
@ -213,4 +205,6 @@ else (APPLE)
endif()
endif (APPLE)
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -47,6 +47,11 @@ Item {
font.pixelSize: root.fontSize
text: "Framerate: " + root.framerate
}
Text {
color: root.fontColor;
font.pixelSize: root.fontSize
text: "Simrate: " + root.simrate
}
Text {
color: root.fontColor;
font.pixelSize: root.fontSize

View file

@ -52,6 +52,7 @@
#include <AccountManager.h>
#include <AddressManager.h>
#include <ApplicationVersion.h>
#include <CursorManager.h>
#include <AudioInjector.h>
#include <AutoUpdater.h>
@ -103,7 +104,6 @@
#include "AudioClient.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
#include "InterfaceVersion.h"
#include "LODManager.h"
#include "Menu.h"
#include "ModelPackager.h"
@ -112,9 +112,7 @@
#include "InterfaceActionFactory.h"
#include "avatar/AvatarManager.h"
#include "audio/AudioScope.h"
#include "devices/DdeFaceTracker.h"
#include "devices/EyeTracker.h"
#include "devices/Faceshift.h"
@ -148,6 +146,8 @@
#include "ui/AddressBarDialog.h"
#include "ui/UpdateDialog.h"
#include "ui/overlays/Cube3DOverlay.h"
// ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
#if defined(Q_OS_WIN)
@ -172,12 +172,9 @@ public:
void call() { _fun(); }
};
using namespace std;
// Starfield information
static uint8_t THROTTLED_IDLE_TIMER_DELAY = 10;
const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB
static QTimer* locationUpdateTimer = NULL;
@ -299,6 +296,13 @@ bool setupEssentials(int& argc, char** argv) {
return true;
}
// FIXME move to header, or better yet, design some kind of UI manager
// to take care of highlighting keyboard focused items, rather than
// continuing to overburden Application.cpp
Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 };
Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
@ -554,7 +558,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_toolWindow = new ToolWindow();
_toolWindow->setWindowFlags(_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint);
_toolWindow->setWindowFlags((_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowMinimizeButtonHint);
_toolWindow->setWindowTitle("Tools");
_offscreenContext->makeCurrent();
@ -673,8 +677,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) {
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
[this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) {
if (_keyboardFocusedItem != entityItemID) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
@ -684,16 +690,53 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
if (webEntity) {
webEntity->setProxyWindow(_window->windowHandle());
_keyboardFocusedItem = entityItemID;
_lastAcceptedKeyPress = usecTimestampNow();
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
_keyboardFocusHighlight = new Cube3DOverlay();
_keyboardFocusHighlight->setAlpha(1.0f);
_keyboardFocusHighlight->setBorderSize(1.0f);
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
_keyboardFocusHighlight->setIsSolid(false);
_keyboardFocusHighlight->setPulseMin(0.5);
_keyboardFocusHighlight->setPulseMax(1.0);
_keyboardFocusHighlight->setColorPulse(1.0);
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
_keyboardFocusHighlight->setDrawInFront(true);
}
_keyboardFocusHighlight->setRotation(webEntity->getRotation());
_keyboardFocusHighlight->setPosition(webEntity->getPosition());
_keyboardFocusHighlight->setDimensions(webEntity->getDimensions() * 1.05f);
_keyboardFocusHighlight->setVisible(true);
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
}
}
if (_keyboardFocusedItem == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
}
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity,
[=](const EntityItemID& entityItemID) {
if (entityItemID == _keyboardFocusedItem) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
}
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
if (_keyboardFocusedItem == entityItemID) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
// If the user clicks somewhere where there is NO entity at all, we will release focus
connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity,
[=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
});
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
}
void Application::aboutToQuit() {
@ -704,6 +747,11 @@ void Application::aboutToQuit() {
}
void Application::cleanupBeforeQuit() {
if (_keyboardFocusHighlightID > 0) {
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
_keyboardFocusHighlightID = -1;
}
_keyboardFocusHighlight = nullptr;
_entities.clear(); // this will allow entity scripts to properly shutdown
@ -1270,6 +1318,7 @@ bool Application::event(QEvent* event) {
event->setAccepted(false);
QCoreApplication::sendEvent(webEntity->getEventHandler(), event);
if (event->isAccepted()) {
_lastAcceptedKeyPress = usecTimestampNow();
return true;
}
}
@ -1695,6 +1744,27 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
return;
}
#ifndef Q_OS_MAC
// If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow
// then show the menubar.
if (_window->isFullScreen()) {
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
static const int MENU_TOGGLE_AREA = 10;
if (!menuBar->isVisible()) {
if (event->pos().y() <= MENU_TOGGLE_AREA) {
menuBar->setVisible(true);
}
} else {
if (event->pos().y() > MENU_TOGGLE_AREA) {
menuBar->setVisible(false);
}
}
}
}
#endif
_entities.mouseMoveEvent(event, deviceID);
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
@ -1965,10 +2035,19 @@ void Application::checkFPS() {
void Application::idle() {
PROFILE_RANGE(__FUNCTION__);
static SimpleAverage<float> interIdleDurations;
static uint64_t lastIdleStart{ 0 };
static uint64_t lastIdleEnd{ 0 };
uint64_t now = usecTimestampNow();
uint64_t idleStartToStartDuration = now - lastIdleStart;
if (lastIdleStart > 0 && idleStartToStartDuration > 0) {
_simsPerSecond.updateAverage((float)USECS_PER_SECOND / (float)idleStartToStartDuration);
}
lastIdleStart = now;
if (lastIdleEnd != 0) {
uint64_t now = usecTimestampNow();
interIdleDurations.update(now - lastIdleEnd);
static uint64_t lastReportTime = now;
if ((now - lastReportTime) >= (USECS_PER_SECOND)) {
@ -1986,6 +2065,15 @@ void Application::idle() {
return; // bail early, nothing to do here.
}
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
if (!_keyboardFocusedItem.isInvalidID()) {
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
}
}
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
// details normally.
@ -2041,8 +2129,11 @@ void Application::idle() {
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
// perpetuity and not expect events to get backed up.
bool isThrottled = getActiveDisplayPlugin()->isThrottled();
static const int THROTTLED_IDLE_TIMER_DELAY = MSECS_PER_SECOND / 15;
static const int IDLE_TIMER_DELAY_MS = 2;
int desiredInterval = getActiveDisplayPlugin()->isThrottled() ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS;
int desiredInterval = isThrottled ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS;
//qDebug() << "isThrottled:" << isThrottled << "desiredInterval:" << desiredInterval;
if (idleTimer->interval() != desiredInterval) {
idleTimer->start(desiredInterval);
@ -2054,6 +2145,16 @@ void Application::idle() {
lastIdleEnd = usecTimestampNow();
}
float Application::getAverageSimsPerSecond() {
uint64_t now = usecTimestampNow();
if (now - _lastSimsPerSecondUpdate > USECS_PER_SECOND) {
_simsPerSecondReport = _simsPerSecond.getAverage();
_lastSimsPerSecondUpdate = now;
}
return _simsPerSecondReport;
}
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
InputDevice::setLowVelocityFilter(lowVelocityFilter);
}
@ -2402,6 +2503,10 @@ void Application::updateMouseRay() {
}
}
// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone.
// (Maybe this code should be moved there?)
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
// Note that it is called BEFORE we update position or joints based on sensors, etc.
void Application::updateMyAvatarLookAtPosition() {
PerformanceTimer perfTimer("lookAt");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
@ -4016,23 +4121,12 @@ bool Application::askToSetAvatarUrl(const QString& url) {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle("Set Avatar");
QPushButton* headButton = NULL;
QPushButton* bodyButton = NULL;
QPushButton* bodyAndHeadButton = NULL;
QString modelName = fstMapping["name"].toString();
QString message;
QString typeInfo;
switch (modelType) {
case FSTReader::HEAD_MODEL:
message = QString("Would you like to use '") + modelName + QString("' for your avatar head?");
headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
case FSTReader::BODY_ONLY_MODEL:
message = QString("Would you like to use '") + modelName + QString("' for your avatar body?");
bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
case FSTReader::HEAD_AND_BODY_MODEL:
message = QString("Would you like to use '") + modelName + QString("' for your avatar?");
@ -4040,10 +4134,7 @@ bool Application::askToSetAvatarUrl(const QString& url) {
break;
default:
message = QString("Would you like to use '") + modelName + QString("' for some part of your avatar head?");
headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole);
bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole);
bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole);
message = QString(modelName + QString("Does not support a head and body as required."));
break;
}
@ -4052,14 +4143,7 @@ bool Application::askToSetAvatarUrl(const QString& url) {
msgBox.exec();
if (msgBox.clickedButton() == headButton) {
_myAvatar->useHeadURL(url, modelName);
emit headURLChanged(url, modelName);
} else if (msgBox.clickedButton() == bodyButton) {
_myAvatar->useBodyURL(url, modelName);
emit bodyURLChanged(url, modelName);
} else if (msgBox.clickedButton() == bodyAndHeadButton) {
_myAvatar->useFullAvatarURL(url, modelName);
if (msgBox.clickedButton() == bodyAndHeadButton) {
emit fullAvatarURLChanged(url, modelName);
} else {
qCDebug(interfaceapp) << "Declined to use the avatar: " << url;
@ -4546,12 +4630,30 @@ void Application::checkSkeleton() {
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
_myAvatar->useBodyURL(DEFAULT_BODY_MODEL_URL, "Default");
_myAvatar->useFullAvatarURL(AvatarData::defaultFullAvatarModelUrl(), DEFAULT_FULL_AVATAR_MODEL_NAME);
} else {
_physicsEngine.setCharacterController(_myAvatar->getCharacterController());
}
}
bool Application::isForeground() {
return _isForeground && !getWindow()->isMinimized();
}
void Application::activeChanged(Qt::ApplicationState state) {
switch (state) {
case Qt::ApplicationActive:
_isForeground = true;
break;
case Qt::ApplicationSuspended:
case Qt::ApplicationHidden:
case Qt::ApplicationInactive:
default:
_isForeground = false;
break;
}
}
void Application::showFriendsWindow() {
const QString FRIENDS_WINDOW_TITLE = "Add/Remove Friends";
const QString FRIENDS_WINDOW_URL = "https://metaverse.highfidelity.com/user/friends";
@ -4665,6 +4767,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
}
static QVector<QPair<QString, QString>> _currentDisplayPluginActions;
static bool _activatingDisplayPlugin{false};
void Application::updateDisplayMode() {
auto menu = Menu::getInstance();
@ -4711,7 +4814,9 @@ void Application::updateDisplayMode() {
if (newDisplayPlugin) {
_offscreenContext->makeCurrent();
_activatingDisplayPlugin = true;
newDisplayPlugin->activate();
_activatingDisplayPlugin = false;
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
@ -4719,10 +4824,17 @@ void Application::updateDisplayMode() {
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
bool newPluginWantsHMDTools = newDisplayPlugin ?
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
// Only show the hmd tools after the correct plugin has
// been activated so that it's UI is setup correctly
if (newDisplayPlugin->isHmd()) {
if (newPluginWantsHMDTools) {
showDisplayPluginsTools();
}
@ -4731,7 +4843,7 @@ void Application::updateDisplayMode() {
_offscreenContext->makeCurrent();
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
if (oldDisplayPlugin->isHmd() && !newDisplayPlugin->isHmd()) {
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
DependencyManager::get<DialogsManager>()->hmdTools(false);
}
}
@ -4828,14 +4940,17 @@ void Application::removeMenu(const QString& menuName) {
void Application::addMenuItem(const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
auto menu = Menu::getInstance();
MenuWrapper* parentItem = menu->getMenu(path);
QAction* action = parentItem->addAction(name);
QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
connect(action, &QAction::triggered, [=] {
onClicked(action->isChecked());
});
action->setCheckable(checkable);
action->setChecked(checked);
_currentDisplayPluginActions.push_back({ path, name });
_currentInputPluginActions.push_back({ path, name });
if (_activatingDisplayPlugin) {
_currentDisplayPluginActions.push_back({ path, name });
} else {
_currentInputPluginActions.push_back({ path, name });
}
}
void Application::removeMenuItem(const QString& menuName, const QString& menuItem) {
@ -4891,6 +5006,14 @@ void Application::setFullscreen(const QScreen* target) {
#endif
_window->windowHandle()->setScreen((QScreen*)target);
_window->showFullScreen();
#ifndef Q_OS_MAC
// also hide the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(false);
}
#endif
}
void Application::unsetFullscreen(const QScreen* avoid) {
@ -4921,6 +5044,14 @@ void Application::unsetFullscreen(const QScreen* avoid) {
#else
_window->setGeometry(targetGeometry);
#endif
#ifndef Q_OS_MAC
// also show the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(true);
}
#endif
}

View file

@ -38,6 +38,7 @@
#include <ViewFrustum.h>
#include <plugins/PluginContainer.h>
#include <plugins/PluginManager.h>
#include <SimpleMovingAverage.h>
#include "AudioClient.h"
#include "Bookmarks.h"
@ -290,6 +291,7 @@ public:
virtual void unsetFullscreen(const QScreen* avoid) override;
virtual void showDisplayPluginsTools() override;
virtual QGLWidget* getPrimarySurface() override;
virtual bool isForeground() override;
void setActiveDisplayPlugin(const QString& pluginName);
@ -350,6 +352,8 @@ public:
const QRect& getMirrorViewRect() const { return _mirrorViewRect; }
float getAverageSimsPerSecond();
signals:
/// Fired when we're simulating; allows external parties to hook in.
@ -473,6 +477,7 @@ private slots:
void faceTrackerMuteToggled();
void setCursorVisible(bool visible);
void activeChanged(Qt::ApplicationState state);
private:
void resetCameras(Camera& camera, const glm::uvec2& size);
@ -680,6 +685,12 @@ private:
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
EntityItemID _keyboardFocusedItem;
quint64 _lastAcceptedKeyPress = 0;
SimpleMovingAverage _simsPerSecond{10};
int _simsPerSecondReport = 0;
quint64 _lastSimsPerSecondUpdate = 0;
bool _isForeground = true; // starts out assumed to be in foreground
};
#endif // hifi_Application_h

View file

@ -18,8 +18,6 @@
#include "MainWindow.h"
const int MSECS_PER_FRAME_WHEN_THROTTLED = 66;
static QGLFormat& getDesiredGLFormat() {
// Specify an OpenGL 3.3 format using the Core profile.
// That is, no old-school fixed pipeline functionality
@ -35,10 +33,7 @@ static QGLFormat& getDesiredGLFormat() {
return glFormat;
}
GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()),
_throttleRendering(false),
_idleRenderInterval(MSECS_PER_FRAME_WHEN_THROTTLED)
{
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.
@ -46,15 +41,6 @@ GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()),
#endif
}
void GLCanvas::stopFrameTimer() {
_frameTimer.stop();
}
bool GLCanvas::isThrottleRendering() const {
return (_throttleRendering
|| (Application::getInstance()->getWindow()->isMinimized() && Application::getInstance()->isThrottleFPSEnabled()));
}
int GLCanvas::getDeviceWidth() const {
return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
}
@ -66,17 +52,17 @@ int GLCanvas::getDeviceHeight() const {
void GLCanvas::initializeGL() {
setAttribute(Qt::WA_AcceptTouchEvents);
setAcceptDrops(true);
connect(Application::getInstance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(activeChanged(Qt::ApplicationState)));
connect(&_frameTimer, SIGNAL(timeout()), this, SLOT(throttleRender()));
// 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__);
if (!_throttleRendering &&
(!Application::getInstance()->getWindow()->isMinimized() || !Application::getInstance()->isThrottleFPSEnabled())) {
// FIXME - I'm not sure why this still remains, it appears as if this GLCanvas gets a single paintGL call near
// the beginning of the application starting up. I'm not sure if we really need to call Application::paintGL()
// in this case, since the display plugins eventually handle all the painting
if ((!Application::getInstance()->getWindow()->isMinimized() || !Application::getInstance()->isThrottleFPSEnabled())) {
Application::getInstance()->paintGL();
}
}
@ -85,39 +71,6 @@ void GLCanvas::resizeGL(int width, int height) {
Application::getInstance()->resizeGL();
}
void GLCanvas::activeChanged(Qt::ApplicationState state) {
switch (state) {
case Qt::ApplicationActive:
// If we're active, stop the frame timer and the throttle.
_frameTimer.stop();
_throttleRendering = false;
break;
case Qt::ApplicationSuspended:
case Qt::ApplicationHidden:
// If we're hidden or are about to suspend, don't render anything.
_throttleRendering = false;
_frameTimer.stop();
break;
default:
// Otherwise, throttle.
if (!_throttleRendering && !Application::getInstance()->isAboutToQuit()
&& Application::getInstance()->isThrottleFPSEnabled()) {
_frameTimer.start(_idleRenderInterval);
_throttleRendering = true;
}
break;
}
}
void GLCanvas::throttleRender() {
_frameTimer.start(_idleRenderInterval);
if (!Application::getInstance()->getWindow()->isMinimized()) {
Application::getInstance()->paintGL();
}
}
int updateTime = 0;
bool GLCanvas::event(QEvent* event) {
switch (event->type()) {

View file

@ -23,28 +23,18 @@ class GLCanvas : public QGLWidget {
public:
GLCanvas();
void stopFrameTimer();
bool isThrottleRendering() const;
int getDeviceWidth() const;
int getDeviceHeight() const;
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
protected:
QTimer _frameTimer;
bool _throttleRendering;
int _idleRenderInterval;
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int width, int height);
virtual bool event(QEvent* event);
private slots:
void activeChanged(Qt::ApplicationState state);
void throttleRender();
bool eventFilter(QObject*, QEvent* event);
};

View file

@ -258,7 +258,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true,
avatar, SLOT(updateMotionBehavior()));
avatar, SLOT(updateMotionBehaviorFromMenu()));
MenuWrapper* viewMenu = addMenu("View");
addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
@ -444,6 +444,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
MenuOption::Connexion,
0, false,

View file

@ -168,6 +168,7 @@ namespace MenuOption {
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable Entity NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor";
@ -283,7 +284,7 @@ namespace MenuOption {
const QString TestPing = "Test Ping";
const QString ThirdPerson = "Third Person";
const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head";

View file

@ -106,16 +106,17 @@ bool ModelPackager::loadModel() {
}
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
_geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath());
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
return true;
}
bool ModelPackager::editProperties() {
// open the dialog to configure the rest
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry);
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry);
if (properties.exec() == QDialog::Rejected) {
return false;
}
@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
void ModelPackager::listTextures() {
_textures.clear();
foreach (FBXMesh mesh, _geometry.meshes) {
foreach (FBXMesh mesh, _geometry->meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
!_textures.contains(part.diffuseTexture.filename)) {

View file

@ -39,11 +39,11 @@ private:
QString _texDir;
QVariantHash _mapping;
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
QStringList _textures;
};
#endif // hifi_ModelPackager_h
#endif // hifi_ModelPackager_h

View file

@ -50,20 +50,20 @@ void renderWorldBox(gpu::Batch& batch) {
batch.setModelTransform(transform);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(TREE_SCALE, 0.0f, 0.0f), red);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-TREE_SCALE, 0.0f, 0.0f), DASHED_RED,
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), red);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-HALF_TREE_SCALE, 0.0f, 0.0f), DASHED_RED,
DASH_LENGTH, GAP_LENGTH);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, TREE_SCALE, 0.0f), green);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -TREE_SCALE, 0.0f), DASHED_GREEN,
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, HALF_TREE_SCALE, 0.0f), green);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, -HALF_TREE_SCALE, 0.0f), DASHED_GREEN,
DASH_LENGTH, GAP_LENGTH);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, TREE_SCALE), blue);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -TREE_SCALE), DASHED_BLUE,
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), blue);
geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -HALF_TREE_SCALE), DASHED_BLUE,
DASH_LENGTH, GAP_LENGTH);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), grey);
geometryCache->renderLine(batch, glm::vec3(TREE_SCALE, 0.0f, TREE_SCALE), glm::vec3(TREE_SCALE, 0.0f, 0.0f), grey);
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), grey);
geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), grey);
// Draw meter markers along the 3 axis to help with measuring things
const float MARKER_DISTANCE = 1.0f;

View file

@ -26,7 +26,6 @@
#include <NodeList.h>
#include <NumericalConstants.h>
#include <udt/PacketHeaders.h>
#include <PathUtils.h>
#include <PerfStat.h>
#include <SharedUtil.h>
#include <TextRenderer3D.h>
@ -197,7 +196,6 @@ void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("hand");
getHand()->simulate(deltaTime, false);
}
_skeletonModel.setLODDistance(getLODDistance());
if (!_shouldRenderBillboard && inViewFrustum) {
{
@ -568,16 +566,17 @@ void Avatar::fixupModelsInScene() {
// fix them up in the scene
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
render::PendingChanges pendingChanges;
if (_skeletonModel.needsFixupInScene()) {
if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) {
_skeletonModel.removeFromScene(scene, pendingChanges);
_skeletonModel.addToScene(scene, pendingChanges);
}
if (getHead()->getFaceModel().needsFixupInScene()) {
getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
getHead()->getFaceModel().addToScene(scene, pendingChanges);
Model& faceModel = getHead()->getFaceModel();
if (faceModel.isRenderable() && faceModel.needsFixupInScene()) {
faceModel.removeFromScene(scene, pendingChanges);
faceModel.addToScene(scene, pendingChanges);
}
for (auto attachmentModel : _attachmentModels) {
if (attachmentModel->needsFixupInScene()) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
attachmentModel->removeFromScene(scene, pendingChanges);
attachmentModel->addToScene(scene, pendingChanges);
}
@ -619,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) {
int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition;
glm::quat jointRotation;
if (!isMyAvatar()) {
model->setLODDistance(getLODDistance());
}
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
@ -688,6 +684,23 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
const float HEAD_PROPORTION = 0.75f;
namePosition = _position + getBodyUpDirection() * (getBillboardSize() * HEAD_PROPORTION);
}
#ifdef DEBUG
// TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed.
// See other TODO below.
if (glm::isnan(namePosition.x) || glm::isnan(namePosition.y) || glm::isnan(namePosition.z)
|| glm::isinf(namePosition.x) || glm::isinf(namePosition.y) || glm::isinf(namePosition.z)) {
qDebug() << "namePosition =" << namePosition;
glm::vec3 tempPosition(0.0f);
if (getSkeletonModel().getNeckPosition(tempPosition)) {
qDebug() << "getBodyUpDirection() =" << getBodyUpDirection();
qDebug() << "getHeadHeight() =" << getHeadHeight();
} else {
qDebug() << "_position =" << _position;
qDebug() << "getBodyUpDirection() =" << getBodyUpDirection();
qDebug() << "getBillboardSize() =" << getBillboardSize();
}
}
#endif
return namePosition;
}
@ -722,7 +735,8 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
// Compute correct scale to apply
float scale = DESIRED_HIGHT_ON_SCREEN / (fontSize * pixelHeight) * devicePixelRatio;
#ifdef DEBUG
// TODO: Temporary logging to track cause of invalid scale vale; remove once cause has been fixed.
// TODO: Temporary logging to track cause of invalid scale value; remove once cause has been fixed.
// Problem is probably due to an invalid getDisplayNamePosition(). See extra logging above.
if (scale == 0.0f || glm::isnan(scale) || glm::isinf(scale)) {
if (scale == 0.0f) {
qDebug() << "ASSERT because scale == 0.0f";
@ -733,6 +747,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
if (glm::isinf(scale)) {
qDebug() << "ASSERT because isinf(scale)";
}
qDebug() << "textPosition =" << textPosition;
qDebug() << "windowSizeY =" << windowSizeY;
qDebug() << "p1.y =" << p1.y;
qDebug() << "p1.w =" << p1.w;
@ -957,20 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_head.fst");
getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL, true, !isMyAvatar());
getHead()->getFaceModel().setURL(_faceModelURL);
}
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
const QUrl DEFAULT_FULL_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst");
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst");
if (isMyAvatar()) {
_skeletonModel.setURL(_skeletonModelURL,
getUseFullAvatar() ? DEFAULT_FULL_MODEL_URL : DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
} else {
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
}
_skeletonModel.setURL(_skeletonModelURL);
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {

View file

@ -150,8 +150,6 @@ public:
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; }
Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; }
virtual bool getUseFullAvatar() const { return false; }
/// Scales a world space position vector relative to the avatar position and scale
/// \param vector position to be scaled. Will store the result

View file

@ -47,66 +47,10 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
if (isActive()) {
setOffset(-_geometry->getFBXGeometry().neckPivot);
for (int i = 0; i < _rig->getJointStateCount(); i++) {
maybeUpdateNeckAndEyeRotation(i);
}
Model::simulateInternal(deltaTime);
}
}
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const JointState& state, int index) {
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
state.getPreTransform() * glm::mat4_cast(state.getPreRotation())));
glm::vec3 pitchYawRoll = safeEulerAngles(_owningHead->getFinalOrientationInLocalFrame());
glm::vec3 lean = glm::radians(glm::vec3(_owningHead->getFinalLeanForward(),
_owningHead->getTorsoTwist(),
_owningHead->getFinalLeanSideways()));
pitchYawRoll -= lean;
_rig->setJointRotationInConstrainedFrame(index,
glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2]))
* glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1]))
* glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0]))
* state.getDefaultRotation(), DEFAULT_PRIORITY);
}
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const JointState& state, int index) {
// likewise with the eye joints
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
state.getPreTransform() * glm::mat4_cast(state.getPreRotation() * state.getDefaultRotation()));
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAtDelta = _owningHead->getCorrectedLookAtPosition() - model->getTranslation();
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * _owningHead->getSaccade(), 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
_rig->setJointRotationInConstrainedFrame(index, glm::angleAxis(glm::clamp(glm::angle(between),
-MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
state.getDefaultRotation(), DEFAULT_PRIORITY);
}
void FaceModel::maybeUpdateNeckAndEyeRotation(int index) {
const JointState& state = _rig->getJointState(index);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const int parentIndex = state.getParentIndex();
// guard against out-of-bounds access to _jointStates
if (parentIndex != -1 && parentIndex >= 0 && parentIndex < _rig->getJointStateCount()) {
const JointState& parentState = _rig->getJointState(parentIndex);
if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, state, index);
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
maybeUpdateEyeRotation(this, parentState, state, index);
}
}
}
bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) {
return false;

View file

@ -26,10 +26,6 @@ public:
virtual void simulate(float deltaTime, bool fullUpdate = true);
void maybeUpdateNeckRotation(const JointState& parentState, const JointState& state, int index);
void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const JointState& state, int index);
void maybeUpdateNeckAndEyeRotation(int index);
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;

View file

@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_saccade = glm::vec3();
}
if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
}
_leftEyePosition = _rightEyePosition = getPosition();
if (!billboard) {
_faceModel.simulate(deltaTime);
@ -277,6 +274,10 @@ void Head::calculateMouthShapes() {
void Head::applyEyelidOffset(glm::quat headOrientation) {
// Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches.
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableEyelidAdjustment)) {
return;
}
glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getLookAtPosition() - _eyePosition);
eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head
float eyePitch = safeEulerAngles(eyeRotation).x;
@ -344,6 +345,20 @@ glm::quat Head::getFinalOrientationInLocalFrame() const {
return glm::quat(glm::radians(glm::vec3(getFinalPitch(), getFinalYaw(), getFinalRoll() )));
}
// Everyone else's head keeps track of a lookAtPosition that everybody sees the same, and refers to where that head
// is looking in model space -- e.g., at someone's eyeball, or between their eyes, or mouth, etc. Everyon's Interface
// will have the same value for the lookAtPosition of any given head.
//
// Everyone else's head also keeps track of a correctedLookAtPosition that may be different for the same head within
// different Interfaces. If that head is not looking at me, the correctedLookAtPosition is the same as the lookAtPosition.
// However, if that head is looking at me, then I will attempt to adjust the lookAtPosition by the difference between
// my (singular) eye position and my actual camera position. This adjustment is used on their eyeballs during rendering
// (and also on any lookAt vector display for that head, during rendering). Note that:
// 1. this adjustment can be made directly to the other head's eyeball joints, because we won't be send their joint information to others.
// 2. the corrected position is a separate ivar, so the common/uncorrected value is still available
//
// There is a pun here: The two lookAtPositions will always be the same for my own avatar in my own Interface, because I
// will not be looking at myself. (Even in a mirror, I will be looking at the camera.)
glm::vec3 Head::getCorrectedLookAtPosition() {
if (isLookingAtMe()) {
return _correctedLookAtPosition;
@ -364,7 +379,7 @@ void Head::setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition) {
bool Head::isLookingAtMe() {
// Allow for outages such as may be encountered during avatar movement
quint64 now = usecTimestampNow();
const quint64 LOOKING_AT_ME_GAP_ALLOWED = 1000000; // microseconds
const quint64 LOOKING_AT_ME_GAP_ALLOWED = (5 * 1000 * 1000) / 60; // n frames, in microseconds
return _isLookingAtMe || (now - _wasLastLookingAtMe) < LOOKING_AT_ME_GAP_ALLOWED;
}

View file

@ -652,12 +652,7 @@ void MyAvatar::saveData() {
settings.setValue("leanScale", _leanScale);
settings.setValue("scale", _targetScale);
settings.setValue("useFullAvatar", _useFullAvatar);
settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences);
settings.setValue("faceModelURL", _headURLFromPreferences);
settings.setValue("skeletonModelURL", _skeletonURLFromPreferences);
settings.setValue("headModelName", _headModelName);
settings.setValue("bodyModelName", _bodyModelName);
settings.setValue("fullAvatarModelName", _fullAvatarModelName);
settings.beginWriteArray("attachmentData");
@ -727,61 +722,10 @@ void MyAvatar::loadData() {
_targetScale = loadSetting(settings, "scale", 1.0f);
setScale(_scale);
// The old preferences only stored the face and skeleton URLs, we didn't track if the user wanted to use 1 or 2 urls
// for their avatar, So we need to attempt to detect this old case and set our new preferences accordingly. If
// the head URL is empty, then we will assume they are using a full url...
bool isOldSettings = !(settings.contains("useFullAvatar") || settings.contains("fullAvatarURL"));
_useFullAvatar = settings.value("useFullAvatar").toBool();
_headURLFromPreferences = settings.value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl();
_fullAvatarURLFromPreferences = settings.value("fullAvatarURL", DEFAULT_FULL_AVATAR_MODEL_URL).toUrl();
_skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl();
_headModelName = settings.value("headModelName", DEFAULT_HEAD_MODEL_NAME).toString();
_bodyModelName = settings.value("bodyModelName", DEFAULT_BODY_MODEL_NAME).toString();
_fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
_fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
if (isOldSettings) {
bool assumeFullAvatar = _headURLFromPreferences.isEmpty();
_useFullAvatar = assumeFullAvatar;
if (_useFullAvatar) {
_fullAvatarURLFromPreferences = settings.value("skeletonModelURL").toUrl();
_headURLFromPreferences = DEFAULT_HEAD_MODEL_URL;
_skeletonURLFromPreferences = DEFAULT_BODY_MODEL_URL;
QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString());
_headModelName = "Default";
_bodyModelName = "Default";
_fullAvatarModelName = fullAvatarFST["name"].toString();
} else {
_fullAvatarURLFromPreferences = DEFAULT_FULL_AVATAR_MODEL_URL;
_skeletonURLFromPreferences = settings.value("skeletonModelURL", DEFAULT_BODY_MODEL_URL).toUrl();
if (_skeletonURLFromPreferences == DEFAULT_BODY_MODEL_URL) {
_bodyModelName = DEFAULT_BODY_MODEL_NAME;
} else {
QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString());
_bodyModelName = bodyFST["name"].toString();
}
if (_headURLFromPreferences == DEFAULT_HEAD_MODEL_URL) {
_headModelName = DEFAULT_HEAD_MODEL_NAME;
} else {
QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString());
_headModelName = headFST["name"].toString();
}
_fullAvatarModelName = "Default";
}
}
if (_useFullAvatar) {
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
} else {
useHeadAndBodyURLs(_headURLFromPreferences, _skeletonURLFromPreferences, _headModelName, _bodyModelName);
}
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
QVector<AttachmentData> attachmentData;
int attachmentCount = settings.beginReadArray("attachmentData");
@ -899,10 +843,11 @@ void MyAvatar::sendKillAvatar() {
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer);
}
static int counter = 0;
void MyAvatar::updateLookAtTargetAvatar() {
//
// Look at the avatar whose eyes are closest to the ray in direction of my avatar's head
//
// And set the correctedLookAt for all (nearby) avatars that are looking at me.
_lookAtTargetAvatar.reset();
_targetAvatarPosition = glm::vec3(0.0f);
@ -926,14 +871,51 @@ void MyAvatar::updateLookAtTargetAvatar() {
smallestAngleTo = angleTo;
}
if (Application::getInstance()->isLookingAtMyAvatar(avatar)) {
// Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face.
// Offset their gaze according to whether they're looking at one of my eyes or my mouth.
glm::vec3 gazeOffset = avatar->getHead()->getLookAtPosition() - getHead()->getEyePosition();
const float HUMAN_EYE_SEPARATION = 0.065f;
float myEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition());
gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation;
avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition()
+ gazeOffset);
glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar.
// The camera isn't at the point midway between the avatar eyes. (Even without an HMD, the head can be offset a bit.)
// Let's get everything to world space:
glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition();
glm::vec3 avatarRightEye = getHead()->getRightEyePosition();
// When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok.
// By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.)
// This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space.
glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left);
glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right);
glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]);
glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]);
auto humanSystem = Application::getInstance()->getViewFrustum();
glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal);
glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal);
// First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point.
// (We will be adding that offset to the camera position, after making some other adjustments.)
glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition();
// Scale by proportional differences between avatar and human.
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye);
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
// If the camera is also not oriented with the head, adjust by getting the offset in head-space...
/* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday.
glm::quat avatarHeadOrientation = getHead()->getOrientation();
glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset;
// ... and treat that as though it were in camera space, bringing it back to world space.
// But camera is fudged to make the picture feel like the avatar's orientation.
glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ?
gazeOffset = humanOrientation * gazeOffsetLocalToHead;
glm::vec3 corrected = humanSystem->getPosition() + gazeOffset;
*/
// And now we can finally add that offset to the camera.
glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset;
avatar->getHead()->setCorrectedLookAtPosition(corrected);
} else {
avatar->getHead()->clearCorrectedLookAtPosition();
}
@ -1012,29 +994,6 @@ void MyAvatar::clearJointAnimationPriorities() {
}
}
QString MyAvatar::getModelDescription() const {
QString result;
if (_useFullAvatar) {
if (!getFullAvartarModelName().isEmpty()) {
result = "Full Avatar \"" + getFullAvartarModelName() + "\"";
} else {
result = "Full Avatar \"" + _fullAvatarURLFromPreferences.fileName() + "\"";
}
} else {
if (!getHeadModelName().isEmpty()) {
result = "Head \"" + getHeadModelName() + "\"";
} else {
result = "Head \"" + _headURLFromPreferences.fileName() + "\"";
}
if (!getBodyModelName().isEmpty()) {
result += " and Body \"" + getBodyModelName() + "\"";
} else {
result += " and Body \"" + _skeletonURLFromPreferences.fileName() + "\"";
}
}
return result;
}
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
Avatar::setFaceModelURL(faceModelURL);
@ -1061,8 +1020,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
return;
}
_useFullAvatar = true;
if (_fullAvatarURLFromPreferences != fullAvatarURL) {
_fullAvatarURLFromPreferences = fullAvatarURL;
if (modelName.isEmpty()) {
@ -1077,66 +1034,14 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
setFaceModelURL(QString());
}
if (fullAvatarURL != getSkeletonModelURL()) {
const QString& urlString = fullAvatarURL.toString();
if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) {
setSkeletonModelURL(fullAvatarURL);
UserActivityLogger::getInstance().changedModel("skeleton", fullAvatarURL.toString());
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
}
sendIdentityPacket();
}
void MyAvatar::useHeadURL(const QUrl& headURL, const QString& modelName) {
useHeadAndBodyURLs(headURL, _skeletonURLFromPreferences, modelName, _bodyModelName);
}
void MyAvatar::useBodyURL(const QUrl& bodyURL, const QString& modelName) {
useHeadAndBodyURLs(_headURLFromPreferences, bodyURL, _headModelName, modelName);
}
void MyAvatar::useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName, const QString& bodyName) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "useFullAvatarURL", Qt::BlockingQueuedConnection,
Q_ARG(const QUrl&, headURL),
Q_ARG(const QUrl&, bodyURL),
Q_ARG(const QString&, headName),
Q_ARG(const QString&, bodyName));
return;
}
_useFullAvatar = false;
if (_headURLFromPreferences != headURL) {
_headURLFromPreferences = headURL;
if (headName.isEmpty()) {
QVariantHash headFST = FSTReader::downloadMapping(_headURLFromPreferences.toString());
_headModelName = headFST["name"].toString();
} else {
_headModelName = headName;
}
}
if (_skeletonURLFromPreferences != bodyURL) {
_skeletonURLFromPreferences = bodyURL;
if (bodyName.isEmpty()) {
QVariantHash bodyFST = FSTReader::downloadMapping(_skeletonURLFromPreferences.toString());
_bodyModelName = bodyFST["name"].toString();
} else {
_bodyModelName = bodyName;
}
}
if (headURL != getFaceModelURL()) {
setFaceModelURL(headURL);
UserActivityLogger::getInstance().changedModel("head", headURL.toString());
}
if (bodyURL != getSkeletonModelURL()) {
setSkeletonModelURL(bodyURL);
UserActivityLogger::getInstance().changedModel("skeleton", bodyURL.toString());
}
sendIdentityPacket();
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
Avatar::setAttachmentData(attachmentData);
if (QThread::currentThread() != thread()) {
@ -1247,6 +1152,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
getHead()->render(renderArgs, 1.0f, renderFrustum);
}
// This is drawing the lookat vectors from our avatar to wherever we're looking.
if (qApp->isHMDMode()) {
glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition();
@ -1310,11 +1216,7 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
}
if (shouldDrawHead != _prevShouldDrawHead) {
if (_useFullAvatar) {
_skeletonModel.setCauterizeBones(!shouldDrawHead);
} else {
getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene);
}
_skeletonModel.setCauterizeBones(!shouldDrawHead);
}
_prevShouldDrawHead = shouldDrawHead;
}

View file

@ -126,21 +126,8 @@ public:
virtual void clearJointsData();
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadURL(const QUrl& headURL, const QString& modelName = QString());
Q_INVOKABLE void useBodyURL(const QUrl& bodyURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL,
const QString& headName = QString(), const QString& bodyName = QString());
Q_INVOKABLE virtual bool getUseFullAvatar() const { return _useFullAvatar; }
Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
Q_INVOKABLE const QUrl& getHeadURLFromPreferences() const { return _headURLFromPreferences; }
Q_INVOKABLE const QUrl& getBodyURLFromPreferences() const { return _skeletonURLFromPreferences; }
Q_INVOKABLE const QString& getHeadModelName() const { return _headModelName; }
Q_INVOKABLE const QString& getBodyModelName() const { return _bodyModelName; }
Q_INVOKABLE const QString& getFullAvartarModelName() const { return _fullAvatarModelName; }
Q_INVOKABLE QString getModelDescription() const;
Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; }
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
@ -298,18 +285,9 @@ private:
void initHeadBones();
// Avatar Preferences
bool _useFullAvatar = false;
QUrl _fullAvatarURLFromPreferences;
QUrl _headURLFromPreferences;
QUrl _skeletonURLFromPreferences;
QString _headModelName;
QString _bodyModelName;
QString _fullAvatarModelName;
RigPointer _rig;
bool _prevShouldDrawHead;
// cache of the current HMD sensor position and orientation
// in sensor space.
glm::mat4 _hmdSensorMatrix;
@ -330,6 +308,8 @@ private:
glm::quat _goToOrientation;
std::unordered_set<int> _headBoneSet;
RigPointer _rig;
bool _prevShouldDrawHead;
};
#endif // hifi_MyAvatar_h

View file

@ -97,7 +97,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
}
const float PALM_PRIORITY = DEFAULT_PRIORITY;
// Called within Model::simulate call, below.
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
if (_owningAvatar->isMyAvatar()) {
_rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation());
@ -105,26 +105,43 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
Model::updateRig(deltaTime, parentTransform);
if (_owningAvatar->isMyAvatar()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
Head* head = _owningAvatar->getHead();
Rig::HeadParameters params;
params.modelRotation = getRotation();
params.modelTranslation = getTranslation();
params.leanSideways = _owningAvatar->getHead()->getFinalLeanSideways();
params.leanForward = _owningAvatar->getHead()->getFinalLeanForward();
params.torsoTwist = _owningAvatar->getHead()->getTorsoTwist();
params.localHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInLocalFrame();
params.worldHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInWorldFrame();
params.eyeLookAt = _owningAvatar->getHead()->getLookAtPosition();
params.eyeSaccade = _owningAvatar->getHead()->getSaccade();
params.leanSideways = head->getFinalLeanSideways();
params.leanForward = head->getFinalLeanForward();
params.torsoTwist = head->getTorsoTwist();
params.localHeadOrientation = head->getFinalOrientationInLocalFrame();
params.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
params.eyeLookAt = head->getLookAtPosition();
params.eyeSaccade = head->getSaccade();
params.leanJointIndex = geometry.leanJointIndex;
params.neckJointIndex = geometry.neckJointIndex;
params.leftEyeJointIndex = geometry.leftEyeJointIndex;
params.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig->updateFromHeadParameters(params);
} else {
// This is a little more work than we really want.
//
// Other avatars joint, including their eyes, should already be set just like any other joints
// from the wire data. But when looking at me, we want the eyes to use the corrected lookAt.
//
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
// (They latch their looking at me position.) We will revisit that as priorities allow.
const FBXGeometry& geometry = _geometry->getFBXGeometry();
Head* head = _owningAvatar->getHead();
_rig->updateEyeJoints(geometry.leftEyeJointIndex, geometry.rightEyeJointIndex,
getTranslation(), getRotation(),
head->getFinalOrientationInWorldFrame(), head->getCorrectedLookAtPosition());
}
}
// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed),
// but just before head has been simulated.
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setTranslation(_owningAvatar->getSkeletonPosition());
static const glm::quat refOrientation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));

View file

@ -153,21 +153,20 @@ void EyeTracker::onStreamStarted() {
qCDebug(interfaceapp) << "Eye Tracker: Started streaming";
}
// TODO: Re-enable once saving / loading calibrations is working
//if (_isStreaming) {
// // Automatically load calibration if one has been saved.
// QString availableCalibrations = QString(smi_getAvailableCalibrations());
// if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) {
// result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
// if (result != SMI_RET_SUCCESS) {
// qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result);
// QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration"
// + smiReturnValueToString(result));
// } else {
// qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration";
// }
// }
//}
if (_isStreaming) {
// Automatically load calibration if one has been saved.
QString availableCalibrations = QString(smi_getAvailableCalibrations());
if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) {
result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
if (result != SMI_RET_SUCCESS) {
qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result);
QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration"
+ smiReturnValueToString(result));
} else {
qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration";
}
}
}
}
#endif
@ -260,11 +259,10 @@ void EyeTracker::calibrate(int points) {
if (result != SMI_RET_SUCCESS) {
qCWarning(interfaceapp) << "Eye Tracker: Error performing calibration:" << smiReturnValueToString(result);
} else {
// TODO: Re - enable once saving / loading calibrations is working
//result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
//if (result != SMI_RET_SUCCESS) {
// qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result);
//}
result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
if (result != SMI_RET_SUCCESS) {
qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result);
}
}
}
@ -292,11 +290,10 @@ QString EyeTracker::smiReturnValueToString(int value) {
return "Eye cameras not available";
case smi_ErrorReturnValue::SMI_ERROR_OCULUS_RUNTIME_NOT_SUPPORTED:
return "Oculus runtime not supported";
// TODO: Re-enable once saving / loading calibrations is working
//case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND:
// return "File not found";
//case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY:
// return "File empty";
case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND:
return "File not found";
case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY:
return "File empty";
case smi_ErrorReturnValue::SMI_ERROR_UNKNOWN:
return "Unknown error";
default:

View file

@ -1,215 +0,0 @@
//
// AvatarAppearanceDialog.cpp
// interface/src/ui
//
// Created by Stojce Slavkovski on 2/20/14.
// Copyright 2014 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 <QFileDialog>
#include <QFont>
#include <AudioClient.h>
#include <avatar/AvatarManager.h>
#include <devices/Faceshift.h>
#include <NetworkingConstants.h>
#include "Application.h"
#include "MainWindow.h"
#include "LODManager.h"
#include "Menu.h"
#include "AvatarAppearanceDialog.h"
#include "Snapshot.h"
#include "UserActivityLogger.h"
#include "UIUtil.h"
#include "ui/DialogsManager.h"
#include "ui/PreferencesDialog.h"
AvatarAppearanceDialog::AvatarAppearanceDialog(QWidget* parent) :
QDialog(parent) {
setAttribute(Qt::WA_DeleteOnClose);
ui.setupUi(this);
loadAvatarAppearance();
connect(ui.defaultButton, &QPushButton::clicked, this, &AvatarAppearanceDialog::accept);
connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &AvatarAppearanceDialog::openHeadModelBrowser);
connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &AvatarAppearanceDialog::openBodyModelBrowser);
connect(ui.buttonBrowseFullAvatar, &QPushButton::clicked, this, &AvatarAppearanceDialog::openFullAvatarModelBrowser);
connect(ui.useSeparateBodyAndHead, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useSeparateBodyAndHead);
connect(ui.useFullAvatar, &QRadioButton::clicked, this, &AvatarAppearanceDialog::useFullAvatar);
connect(Application::getInstance(), &Application::headURLChanged, this, &AvatarAppearanceDialog::headURLChanged);
connect(Application::getInstance(), &Application::bodyURLChanged, this, &AvatarAppearanceDialog::bodyURLChanged);
connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &AvatarAppearanceDialog::fullAvatarURLChanged);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
ui.bodyNameLabel->setText("Body - " + myAvatar->getBodyModelName());
ui.headNameLabel->setText("Head - " + myAvatar->getHeadModelName());
ui.fullAvatarNameLabel->setText("Full Avatar - " + myAvatar->getFullAvartarModelName());
UIUtil::scaleWidgetFontSizes(this);
}
void AvatarAppearanceDialog::useSeparateBodyAndHead(bool checked) {
QUrl headURL(ui.faceURLEdit->text());
QUrl bodyURL(ui.skeletonURLEdit->text());
DependencyManager::get<AvatarManager>()->getMyAvatar()->useHeadAndBodyURLs(headURL, bodyURL);
setUseFullAvatar(!checked);
}
void AvatarAppearanceDialog::useFullAvatar(bool checked) {
QUrl fullAvatarURL(ui.fullAvatarURLEdit->text());
DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(fullAvatarURL);
setUseFullAvatar(checked);
}
void AvatarAppearanceDialog::setUseFullAvatar(bool useFullAvatar) {
_useFullAvatar = useFullAvatar;
ui.faceURLEdit->setEnabled(!_useFullAvatar);
ui.skeletonURLEdit->setEnabled(!_useFullAvatar);
ui.fullAvatarURLEdit->setEnabled(_useFullAvatar);
ui.useFullAvatar->setChecked(_useFullAvatar);
ui.useSeparateBodyAndHead->setChecked(!_useFullAvatar);
QPointer<PreferencesDialog> prefs = DependencyManager::get<DialogsManager>()->getPreferencesDialog();
if (prefs) { // Preferences dialog may have been closed
prefs->avatarDescriptionChanged();
}
}
void AvatarAppearanceDialog::headURLChanged(const QString& newValue, const QString& modelName) {
ui.faceURLEdit->setText(newValue);
setUseFullAvatar(false);
ui.headNameLabel->setText("Head - " + modelName);
}
void AvatarAppearanceDialog::bodyURLChanged(const QString& newValue, const QString& modelName) {
ui.skeletonURLEdit->setText(newValue);
setUseFullAvatar(false);
ui.bodyNameLabel->setText("Body - " + modelName);
}
void AvatarAppearanceDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) {
ui.fullAvatarURLEdit->setText(newValue);
setUseFullAvatar(true);
ui.fullAvatarNameLabel->setText("Full Avatar - " + modelName);
}
void AvatarAppearanceDialog::accept() {
saveAvatarAppearance();
QPointer<PreferencesDialog> prefs = DependencyManager::get<DialogsManager>()->getPreferencesDialog();
if (prefs) { // Preferences dialog may have been closed
prefs->avatarDescriptionChanged();
}
close();
delete _marketplaceWindow;
_marketplaceWindow = NULL;
}
void AvatarAppearanceDialog::setHeadUrl(QString modelUrl) {
ui.faceURLEdit->setText(modelUrl);
}
void AvatarAppearanceDialog::setSkeletonUrl(QString modelUrl) {
ui.skeletonURLEdit->setText(modelUrl);
}
void AvatarAppearanceDialog::openFullAvatarModelBrowser() {
auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
auto WIDTH = 900;
auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void AvatarAppearanceDialog::openHeadModelBrowser() {
auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
auto WIDTH = 900;
auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void AvatarAppearanceDialog::openBodyModelBrowser() {
auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
auto WIDTH = 900;
auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void AvatarAppearanceDialog::resizeEvent(QResizeEvent *resizeEvent) {
// keep buttons panel at the bottom
ui.buttonsPanel->setGeometry(0,
size().height() - ui.buttonsPanel->height(),
size().width(),
ui.buttonsPanel->height());
// set width and height of srcollarea to match bottom panel and width
ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(),
size().width(),
size().height() - ui.buttonsPanel->height() - ui.scrollArea->geometry().y());
}
void AvatarAppearanceDialog::loadAvatarAppearance() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
_useFullAvatar = myAvatar->getUseFullAvatar();
_fullAvatarURLString = myAvatar->getFullAvatarURLFromPreferences().toString();
_headURLString = myAvatar->getHeadURLFromPreferences().toString();
_bodyURLString = myAvatar->getBodyURLFromPreferences().toString();
ui.fullAvatarURLEdit->setText(_fullAvatarURLString);
ui.faceURLEdit->setText(_headURLString);
ui.skeletonURLEdit->setText(_bodyURLString);
setUseFullAvatar(_useFullAvatar);
}
void AvatarAppearanceDialog::saveAvatarAppearance() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
QUrl headURL(ui.faceURLEdit->text());
QString headURLString = headURL.toString();
QUrl bodyURL(ui.skeletonURLEdit->text());
QString bodyURLString = bodyURL.toString();
QUrl fullAvatarURL(ui.fullAvatarURLEdit->text());
QString fullAvatarURLString = fullAvatarURL.toString();
bool somethingChanged =
_useFullAvatar != myAvatar->getUseFullAvatar() ||
fullAvatarURLString != myAvatar->getFullAvatarURLFromPreferences().toString() ||
headURLString != myAvatar->getHeadURLFromPreferences().toString() ||
bodyURLString != myAvatar->getBodyURLFromPreferences().toString();
if (somethingChanged) {
if (_useFullAvatar) {
myAvatar->useFullAvatarURL(fullAvatarURL);
} else {
myAvatar->useHeadAndBodyURLs(headURL, bodyURL);
}
}
}

View file

@ -1,64 +0,0 @@
//
// AvatarAppearanceDialog.h
// interface/src/ui
//
// Created by Stojce Slavkovski on 2/20/14.
// Copyright 2014 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_AvatarAppearanceDialog_h
#define hifi_AvatarAppearanceDialog_h
#include "ui_avatarAppearance.h"
#include <QDialog>
#include <QString>
#include "scripting/WebWindowClass.h"
class AvatarAppearanceDialog : public QDialog {
Q_OBJECT
public:
AvatarAppearanceDialog(QWidget* parent = nullptr);
protected:
void resizeEvent(QResizeEvent* resizeEvent);
private:
void loadAvatarAppearance();
void saveAvatarAppearance();
void openHeadModelBrowser();
void openBodyModelBrowser();
void openFullAvatarModelBrowser();
void setUseFullAvatar(bool useFullAvatar);
Ui_AvatarAppearanceDialog ui;
bool _useFullAvatar;
QString _headURLString;
QString _bodyURLString;
QString _fullAvatarURLString;
QString _displayNameString;
WebWindowClass* _marketplaceWindow = NULL;
private slots:
void accept();
void setHeadUrl(QString modelUrl);
void setSkeletonUrl(QString modelUrl);
void headURLChanged(const QString& newValue, const QString& modelName);
void bodyURLChanged(const QString& newValue, const QString& modelName);
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
void useSeparateBodyAndHead(bool checked);
void useFullAvatar(bool checked);
};
#endif // hifi_AvatarAppearanceDialog_h

View file

@ -20,7 +20,6 @@
#include "AddressBarDialog.h"
#include "AnimationsDialog.h"
#include "AttachmentsDialog.h"
#include "AvatarAppearanceDialog.h"
#include "BandwidthDialog.h"
#include "CachesSizeDialog.h"
#include "DiskCacheEditor.h"
@ -88,15 +87,6 @@ void DialogsManager::editPreferences() {
}
}
void DialogsManager::changeAvatarAppearance() {
if (!_avatarAppearanceDialog) {
maybeCreateDialog(_avatarAppearanceDialog);
_avatarAppearanceDialog->show();
} else {
_avatarAppearanceDialog->close();
}
}
void DialogsManager::editAttachments() {
if (!_attachmentsDialog) {
maybeCreateDialog(_attachmentsDialog);

View file

@ -34,7 +34,6 @@ class OctreeStatsDialog;
class PreferencesDialog;
class ScriptEditorWindow;
class QMessageBox;
class AvatarAppearanceDialog;
class DomainConnectionDialog;
class UpdateDialog;
@ -66,7 +65,6 @@ public slots:
void hmdTools(bool showTools);
void showScriptEditor();
void showIRCLink();
void changeAvatarAppearance();
void showDomainConnectionDialog();
// Application Update
@ -110,7 +108,6 @@ private:
QPointer<OctreeStatsDialog> _octreeStatsDialog;
QPointer<PreferencesDialog> _preferencesDialog;
QPointer<ScriptEditorWindow> _scriptEditor;
QPointer<AvatarAppearanceDialog> _avatarAppearanceDialog;
QPointer<DomainConnectionDialog> _domainConnectionDialog;
QPointer<UpdateDialog> _updateDialog;
};

View file

@ -47,45 +47,45 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) :
connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser);
connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, Application::getInstance(), &Application::loadDefaultScripts);
DialogsManager* dialogsManager = DependencyManager::get<DialogsManager>().data();
connect(ui.buttonChangeApperance, &QPushButton::clicked, dialogsManager, &DialogsManager::changeAvatarAppearance);
connect(Application::getInstance(), &Application::headURLChanged, this, &PreferencesDialog::headURLChanged);
connect(Application::getInstance(), &Application::bodyURLChanged, this, &PreferencesDialog::bodyURLChanged);
connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser);
connect(ui.appearanceDescription, &QLineEdit::textChanged, this, [this](const QString& url) {
this->fullAvatarURLChanged(url, "");
});
connect(Application::getInstance(), &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged);
// move dialog to left side
move(parentWidget()->geometry().topLeft());
setFixedHeight(parentWidget()->size().height() - PREFERENCES_HEIGHT_PADDING);
ui.apperanceDescription->setText(DependencyManager::get<AvatarManager>()->getMyAvatar()->getModelDescription());
UIUtil::scaleWidgetFontSizes(this);
}
void PreferencesDialog::avatarDescriptionChanged() {
ui.apperanceDescription->setText(DependencyManager::get<AvatarManager>()->getMyAvatar()->getModelDescription());
}
void PreferencesDialog::headURLChanged(const QString& newValue, const QString& modelName) {
ui.apperanceDescription->setText(DependencyManager::get<AvatarManager>()->getMyAvatar()->getModelDescription());
}
void PreferencesDialog::bodyURLChanged(const QString& newValue, const QString& modelName) {
ui.apperanceDescription->setText(DependencyManager::get<AvatarManager>()->getMyAvatar()->getModelDescription());
}
void PreferencesDialog::fullAvatarURLChanged(const QString& newValue, const QString& modelName) {
ui.apperanceDescription->setText(DependencyManager::get<AvatarManager>()->getMyAvatar()->getModelDescription());
ui.appearanceDescription->setText(newValue);
const QString APPEARANCE_LABEL_TEXT("Appearance: ");
ui.appearanceLabel->setText(APPEARANCE_LABEL_TEXT + modelName);
DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(newValue, modelName);
}
void PreferencesDialog::accept() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
_lastGoodAvatarURL = myAvatar->getFullAvatarURLFromPreferences();
_lastGoodAvatarName = myAvatar->getFullAvatarModelName();
savePreferences();
close();
delete _marketplaceWindow;
_marketplaceWindow = NULL;
}
void PreferencesDialog::restoreLastGoodAvatar() {
fullAvatarURLChanged(_lastGoodAvatarURL.toString(), _lastGoodAvatarName);
}
void PreferencesDialog::reject() {
restoreLastGoodAvatar();
QDialog::reject();
}
void PreferencesDialog::openSnapshotLocationBrowser() {
QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"),
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
@ -103,6 +103,16 @@ void PreferencesDialog::openScriptsLocationBrowser() {
ui.scriptsLocationEdit->setText(dir);
}
}
void PreferencesDialog::openFullAvatarModelBrowser() {
const auto MARKETPLACE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace?category=avatars";
const auto WIDTH = 900;
const auto HEIGHT = 700;
if (!_marketplaceWindow) {
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
}
_marketplaceWindow->setVisible(true);
}
void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) {
@ -129,6 +139,10 @@ void PreferencesDialog::loadPreferences() {
ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL());
_lastGoodAvatarURL = myAvatar->getFullAvatarURLFromPreferences();
_lastGoodAvatarName = myAvatar->getFullAvatarModelName();
fullAvatarURLChanged(_lastGoodAvatarURL.toString(), _lastGoodAvatarName);
ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger));
ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get());
@ -211,6 +225,11 @@ void PreferencesDialog::savePreferences() {
myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text());
// MyAvatar persists its own data. If it doesn't agree with what the user has explicitly accepted, set it back to old values.
if (_lastGoodAvatarURL != myAvatar->getFullAvatarURLFromPreferences()) {
restoreLastGoodAvatar();
}
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger)
!= ui.sendDataCheckBox->isChecked()) {
Menu::getInstance()->triggerOption(MenuOption::DisableActivityLogger);

View file

@ -33,6 +33,9 @@ protected:
private:
void loadPreferences();
void savePreferences();
QUrl _lastGoodAvatarURL;
QString _lastGoodAvatarName;
void restoreLastGoodAvatar();
Ui_PreferencesDialog ui;
@ -42,10 +45,10 @@ private:
private slots:
void accept();
void reject();
void openFullAvatarModelBrowser();
void openSnapshotLocationBrowser();
void openScriptsLocationBrowser();
void headURLChanged(const QString& newValue, const QString& modelName);
void bodyURLChanged(const QString& newValue, const QString& modelName);
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
};

View file

@ -114,6 +114,7 @@ void Stats::updateStats() {
STAT_UPDATE(avatarCount, avatarManager->size() - 1);
STAT_UPDATE(serverCount, nodeList->size());
STAT_UPDATE(framerate, (int)qApp->getFps());
STAT_UPDATE(simrate, (int)Application::getInstance()->getAverageSimsPerSecond());
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
STAT_UPDATE(packetInCount, bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());

View file

@ -30,6 +30,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, serverCount, 0)
STATS_PROPERTY(int, framerate, 0)
STATS_PROPERTY(int, simrate, 0)
STATS_PROPERTY(int, avatarCount, 0)
STATS_PROPERTY(int, packetInCount, 0)
STATS_PROPERTY(int, packetOutCount, 0)
@ -95,6 +96,7 @@ signals:
void timingExpandedChanged();
void serverCountChanged();
void framerateChanged();
void simrateChanged();
void avatarCountChanged();
void packetInCountChanged();
void packetOutCountChanged();

View file

@ -1,471 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AvatarAppearanceDialog</class>
<widget class="QDialog" name="AvatarAppearanceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>350</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>13</pointsize>
</font>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>-107</y>
<width>485</width>
<height>1550</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>30</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>30</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QRadioButton" name="useFullAvatar">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Use single avatar with Body and Head</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_fullAvatar">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="fullAvatarNameLabel">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Full Avatar</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="buddy">
<cstring>fullAvatarURLEdit</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_fullAvatar">
<item>
<widget class="QLineEdit" name="fullAvatarURLEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_fullAvatar">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonBrowseFullAvatar">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Browse</string>
</property>
<property name="iconSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QRadioButton" name="useSeparateBodyAndHead">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Use separate Body and Head avatar files</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_separateParts_head">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="headNameLabel">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Head</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="faceURLEdit"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonBrowseHead">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Browse</string>
</property>
<property name="iconSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_separateParts_body">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="bodyNameLabel">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Body</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="buddy">
<cstring>skeletonURLEdit</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_skeletonURL">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="skeletonURLEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonBrowseBody">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Browse</string>
</property>
<property name="iconSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QFrame" name="buttonsPanel">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<layout class="QHBoxLayout" name="buttonsHBox_2">
<item>
<spacer name="horizontalSpacer_14">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="defaultButton">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Close</string>
</property>
<property name="default">
<bool>true</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
</ui>

View file

@ -275,7 +275,7 @@
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="buddy">
<cstring>apperanceDescription</cstring>
<cstring>appearanceDescription</cstring>
</property>
</widget>
</item>
@ -283,26 +283,21 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_1_apperanceDescription">
<layout class="QHBoxLayout" name="horizontalLayout_1_appearanceDescription">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="apperanceDescription">
<widget class="QLineEdit" name="appearanceDescription">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_1_apperanceDescription">
<spacer name="horizontalSpacer_1_appearanceDescription">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -318,7 +313,7 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonChangeApperance">
<widget class="QPushButton" name="buttonChangeAppearance">
<property name="font">
<font>
<family>Arial</family>

View file

@ -3,4 +3,6 @@ set(TARGET_NAME animation)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)
link_hifi_libraries(shared gpu model fbx)
setup_memory_debugger()
link_hifi_libraries(shared gpu model fbx)

View file

@ -13,6 +13,7 @@
#include <QThreadPool>
#include "AnimationCache.h"
#include "AnimationLogging.h"
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
@ -62,11 +63,15 @@ void AnimationReader::run() {
QSharedPointer<Resource> animation = _animation.toStrongRef();
if (!animation.isNull()) {
QMetaObject::invokeMethod(animation.data(), "setGeometry",
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
}
_reply->deleteLater();
}
bool Animation::isLoaded() const {
return _loaded && _geometry;
}
QStringList Animation::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const {
return result;
}
QStringList names;
foreach (const FBXJoint& joint, _geometry.joints) {
foreach (const FBXJoint& joint, _geometry->joints) {
names.append(joint.name);
}
return names;
@ -88,15 +93,15 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
return result;
}
return _geometry.animationFrames;
return _geometry->animationFrames;
}
const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
return _geometry.animationFrames;
return _geometry->animationFrames;
}
void Animation::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
void Animation::setGeometry(FBXGeometry* geometry) {
_geometry.reset(geometry);
finishedLoading(true);
}

View file

@ -52,7 +52,10 @@ public:
Animation(const QUrl& url);
const FBXGeometry& getGeometry() const { return _geometry; }
const FBXGeometry& getGeometry() const { return *_geometry; }
virtual bool isLoaded() const override;
Q_INVOKABLE QStringList getJointNames() const;
@ -62,13 +65,13 @@ public:
protected:
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
Q_INVOKABLE void setGeometry(FBXGeometry* geometry);
virtual void downloadFinished(QNetworkReply* reply);
private:
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
};

View file

@ -786,8 +786,8 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
void Rig::updateFromHeadParameters(const HeadParameters& params) {
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
updateNeckJoint(params.neckJointIndex, params.localHeadOrientation, params.leanSideways, params.leanForward, params.torsoTwist);
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoints(params.leftEyeJointIndex, params.rightEyeJointIndex, params.modelTranslation, params.modelRotation,
params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
}
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
@ -828,6 +828,11 @@ void Rig::updateNeckJoint(int index, const glm::quat& localHeadOrientation, floa
}
}
void Rig::updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
}
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& state = _jointStates[index];

View file

@ -157,6 +157,8 @@ public:
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
void updateFromHeadParameters(const HeadParameters& params);
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade = glm::vec3(0.0f));
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
float scale, float priority) = 0;

View file

@ -1,5 +1,7 @@
set(TARGET_NAME audio-client)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Multimedia)
@ -25,4 +27,4 @@ if (APPLE)
find_library(CoreAudio CoreAudio)
find_library(CoreFoundation CoreFoundation)
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation})
endif ()
endif ()

View file

@ -1,5 +1,7 @@
set(TARGET_NAME audio)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network)

View file

@ -77,9 +77,9 @@ void AudioInjector::injectAudio() {
int byteOffset = (int) floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f));
byteOffset *= sizeof(int16_t);
_currentSendPosition = byteOffset;
_currentSendOffset = byteOffset;
} else {
_currentSendPosition = 0;
_currentSendOffset = 0;
}
if (_options.localOnly) {
@ -119,7 +119,7 @@ void AudioInjector::injectLocally() {
_localBuffer->setVolume(_options.volume);
// give our current send position to the local buffer
_localBuffer->setCurrentOffset(_currentSendPosition);
_localBuffer->setCurrentOffset(_currentSendOffset);
success = _localAudioInterface->outputLocalInjector(_options.stereo, this);
@ -144,9 +144,9 @@ void AudioInjector::injectLocally() {
const uchar MAX_INJECTOR_VOLUME = 0xFF;
void AudioInjector::injectToMixer() {
if (_currentSendPosition < 0 ||
_currentSendPosition >= _audioData.size()) {
_currentSendPosition = 0;
if (_currentSendOffset < 0 ||
_currentSendOffset >= _audioData.size()) {
_currentSendOffset = 0;
}
auto nodeList = DependencyManager::get<NodeList>();
@ -203,15 +203,15 @@ void AudioInjector::injectToMixer() {
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
quint16 outgoingInjectedAudioSequenceNumber = 0;
while (_currentSendPosition < _audioData.size() && !_shouldStop) {
while (_currentSendOffset < _audioData.size() && !_shouldStop) {
int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL,
_audioData.size() - _currentSendPosition);
_audioData.size() - _currentSendOffset);
// Measure the loudness of this frame
_loudness = 0.0f;
for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) {
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendPosition + i)) /
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendOffset + i)) /
(AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
}
_loudness /= (float)(bytesToCopy / sizeof(int16_t));
@ -220,7 +220,7 @@ void AudioInjector::injectToMixer() {
// pack the sequence number
audioPacket->writePrimitive(outgoingInjectedAudioSequenceNumber);
audioPacket->seek(positionOptionOffset);
audioPacket->writePrimitive(_options.position);
audioPacket->writePrimitive(_options.orientation);
@ -232,7 +232,7 @@ void AudioInjector::injectToMixer() {
audioPacket->seek(audioDataOffset);
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
audioPacket->write(_audioData.data() + _currentSendPosition, bytesToCopy);
audioPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy);
// set the correct size used for this packet
audioPacket->setPayloadSize(audioPacket->pos());
@ -246,11 +246,11 @@ void AudioInjector::injectToMixer() {
outgoingInjectedAudioSequenceNumber++;
}
_currentSendPosition += bytesToCopy;
_currentSendOffset += bytesToCopy;
// send two packets before the first sleep so the mixer can start playback right away
if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) {
if (_currentSendOffset != bytesToCopy && _currentSendOffset < _audioData.size()) {
// process events in case we have been told to stop and be deleted
QCoreApplication::processEvents();
@ -268,8 +268,8 @@ void AudioInjector::injectToMixer() {
}
}
if (shouldLoop && _currentSendPosition >= _audioData.size()) {
_currentSendPosition = 0;
if (shouldLoop && _currentSendOffset >= _audioData.size()) {
_currentSendOffset = 0;
}
}
}

View file

@ -31,7 +31,6 @@ class AbstractAudioInterface;
class AudioInjector : public QObject {
Q_OBJECT
Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions)
public:
AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions);
@ -39,7 +38,8 @@ public:
bool isFinished() const { return _isFinished; }
int getCurrentSendPosition() const { return _currentSendPosition; }
int getCurrentSendOffset() const { return _currentSendOffset; }
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
bool isLocalOnly() const { return _options.localOnly; }
@ -58,9 +58,8 @@ public slots:
void stopAndDeleteLater();
const AudioInjectorOptions& getOptions() const { return _options; }
void setOptions(const AudioInjectorOptions& options) { _options = options; }
void setOptions(const AudioInjectorOptions& options) { _options = options; }
void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; }
float getLoudness() const { return _loudness; }
bool isPlaying() const { return _isPlaying; }
void restartPortionAfterFinished();
@ -82,7 +81,7 @@ private:
bool _isStarted = false;
bool _isFinished = false;
bool _shouldDeleteAfterFinish = false;
int _currentSendPosition = 0;
int _currentSendOffset = 0;
AbstractAudioInterface* _localAudioInterface = NULL;
AudioInjectorLocalBuffer* _localBuffer = NULL;
};

View file

@ -112,7 +112,7 @@ int InboundAudioStream::parseData(NLPacket& packet) {
// parse the info after the seq number and before the audio data (the stream properties)
int prePropertyPosition = packet.pos();
int propertyBytes = parseStreamProperties(packet.getType(), packet.read(packet.bytesLeftToRead()), networkSamples);
int propertyBytes = parseStreamProperties(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples);
packet.seek(prePropertyPosition + propertyBytes);
// handle this packet based on its arrival status.
@ -131,7 +131,7 @@ int InboundAudioStream::parseData(NLPacket& packet) {
if (packet.getType() == PacketType::SilentAudioFrame) {
writeDroppableSilentSamples(networkSamples);
} else {
parseAudioData(packet.getType(), packet.read(packet.bytesLeftToRead()), networkSamples);
parseAudioData(packet.getType(), packet.readWithoutCopy(packet.bytesLeftToRead()), networkSamples);
}
break;
}

View file

@ -1,3 +1,6 @@
set(TARGET_NAME auto-updater)
setup_memory_debugger()
setup_hifi_library(Network)
link_hifi_libraries(shared networking)

View file

@ -1,5 +1,7 @@
set(TARGET_NAME avatars)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)

View file

@ -70,6 +70,16 @@ AvatarData::~AvatarData() {
delete _referential;
}
// We cannot have a file-level variable (const or otherwise) in the header if it uses PathUtils, because that references Application, which will not yet initialized.
// Thus we have a static class getter, referencing a static class var.
QUrl AvatarData::_defaultFullAvatarModelUrl = {}; // In C++, if this initialization were in the header, every file would have it's own copy, even for class vars.
const QUrl AvatarData::defaultFullAvatarModelUrl() {
if (_defaultFullAvatarModelUrl.isEmpty()) {
_defaultFullAvatarModelUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst");
}
return _defaultFullAvatarModelUrl;
}
const glm::vec3& AvatarData::getPosition() const {
if (_referential) {
_referential->update();
@ -899,7 +909,7 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
}
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_BODY_MODEL_URL : skeletonModelURL;
_skeletonModelURL = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString();

View file

@ -54,6 +54,7 @@ typedef unsigned long long quint64;
#include "AABox.h"
#include "HandData.h"
#include "HeadData.h"
#include "PathUtils.h"
#include "Player.h"
#include "Recorder.h"
#include "Referential.h"
@ -107,12 +108,7 @@ const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/models/heads/defaultAvatar_head.fst");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/models/skeletons/defaultAvatar_body.fst");
const QUrl DEFAULT_FULL_AVATAR_MODEL_URL = QUrl("http://public.highfidelity.io/marketplace/contents/029db3d4-da2c-4cb2-9c08-b9612ba576f5/02949063e7c4aed42ad9d1a58461f56d.fst");
const QString DEFAULT_HEAD_MODEL_NAME = QString("Robot");
const QString DEFAULT_BODY_MODEL_NAME = QString("Robot");
// See also static AvatarData::defaultFullAvatarModelUrl().
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
@ -163,6 +159,8 @@ public:
AvatarData();
virtual ~AvatarData();
static const QUrl defaultFullAvatarModelUrl();
virtual bool isMyAvatar() const { return false; }
const QUuid& getSessionUUID() const { return _sessionUUID; }
@ -401,6 +399,7 @@ protected:
SimpleMovingAverage _averageBytesReceived;
private:
static QUrl _defaultFullAvatarModelUrl;
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);
AvatarData& operator= (const AvatarData&);

View file

@ -53,11 +53,11 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<NLPacket> packet, Sha
// enumerate over all of the avatars in this packet
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
while (packet->bytesLeftToRead()) {
QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID));
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
int positionBeforeRead = packet->pos();
QByteArray byteArray = packet->read(packet->bytesLeftToRead());
QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead());
if (sessionUUID != _lastOwnerSessionUUID) {
AvatarSharedPointer avatar = _avatarHash.value(sessionUUID);
@ -114,7 +114,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<NLPacket> packet,
}
void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID));
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
AvatarSharedPointer avatar = _avatarHash.value(sessionUUID);
if (!avatar) {
@ -129,7 +129,7 @@ void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<NLPacket> packet
void AvatarHashMap::processKillAvatar(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
// read the node id
QUuid sessionUUID = QUuid::fromRfc4122(packet->read(NUM_BYTES_RFC4122_UUID));
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
removeAvatar(sessionUUID);
}

View file

@ -371,7 +371,7 @@ void Player::setAudioInjectorPosition() {
int MSEC_PER_SEC = 1000;
int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel();
int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC);
_injector->setCurrentSendPosition(currentAudioFrame);
_injector->setCurrentSendOffset(currentAudioFrame);
}
void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) {

View file

@ -1,5 +1,7 @@
set(TARGET_NAME display-plugins)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(OpenGL)
@ -31,4 +33,4 @@ if (WIN32)
find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
endif()
endif()

View file

@ -9,12 +9,11 @@
#include <plugins/PluginContainer.h>
#include <QWindow>
#include <QGuiApplication>
const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display");
const QString MENU_PARENT = "View";
const QString MENU_NAME = "Display Options";
const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME;
const QString MENU_PATH = "Display";
const QString FULLSCREEN = "Fullscreen";
const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const {
@ -22,15 +21,44 @@ const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const {
}
void Basic2DWindowOpenGLDisplayPlugin::activate() {
// container->addMenu(MENU_PATH);
// container->addMenuItem(MENU_PATH, FULLSCREEN,
// [this] (bool clicked) { this->setFullscreen(clicked); },
// true, false);
CONTAINER->addMenu(MENU_PATH);
CONTAINER->addMenuItem(MENU_PATH, FULLSCREEN,
[this](bool clicked) {
if (clicked) {
CONTAINER->setFullscreen(getFullscreenTarget());
} else {
CONTAINER->unsetFullscreen();
}
}, true, false);
MainWindowOpenGLDisplayPlugin::activate();
}
void Basic2DWindowOpenGLDisplayPlugin::deactivate() {
// container->removeMenuItem(MENU_NAME, FULLSCREEN);
// container->removeMenu(MENU_PATH);
MainWindowOpenGLDisplayPlugin::deactivate();
}
int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval(bool isThrottled) const {
static const int THROTTLED_PAINT_TIMER_DELAY = MSECS_PER_SECOND / 15;
static const int PAINT_TIMER_DELAY_MS = 1;
return isThrottled ? THROTTLED_PAINT_TIMER_DELAY : PAINT_TIMER_DELAY_MS;
}
bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const {
static const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Menu.h
bool shouldThrottle = (!CONTAINER->isForeground() && CONTAINER->isOptionChecked(ThrottleFPSIfNotFocus));
if (_isThrottled != shouldThrottle) {
int desiredInterval = getDesiredInterval(shouldThrottle);
_timer.start(desiredInterval);
_isThrottled = shouldThrottle;
}
return shouldThrottle;
}
// FIXME target the screen the window is currently on
QScreen* Basic2DWindowOpenGLDisplayPlugin::getFullscreenTarget() {
return qApp->primaryScreen();
}

View file

@ -9,6 +9,7 @@
#include "MainWindowOpenGLDisplayPlugin.h"
class QScreen;
class Basic2DWindowOpenGLDisplayPlugin : public MainWindowOpenGLDisplayPlugin {
Q_OBJECT
@ -18,6 +19,14 @@ public:
virtual const QString & getName() const override;
virtual bool isThrottled() const override;
protected:
int getDesiredInterval(bool isThrottled) const;
mutable bool _isThrottled = false;
private:
static const QString NAME;
QScreen* getFullscreenTarget();
int _fullscreenTarget{ -1 };
};

View file

@ -37,8 +37,8 @@ protected:
virtual void doneCurrent() = 0;
virtual void swapBuffers() = 0;
QTimer _timer;
ProgramPtr _program;
mutable QTimer _timer;
ProgramPtr _program;
ShapeWrapperPtr _plane;
};

View file

@ -1,4 +1,6 @@
set(TARGET_NAME embedded-webserver)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network)
setup_hifi_library(Network)

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
@ -19,16 +20,19 @@
#include "EmbeddedWebserverLogging.h"
#include "HTTPManager.h"
const int SOCKET_ERROR_EXIT_CODE = 2;
const int SOCKET_CHECK_INTERVAL_IN_MS = 30000;
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
QTcpServer(parent),
_documentRoot(documentRoot),
_requestHandler(requestHandler)
_requestHandler(requestHandler),
_port(port)
{
// start listening on the passed port
if (!listen(QHostAddress("0.0.0.0"), port)) {
qCDebug(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString();
return;
}
bindSocket();
_isListeningTimer = new QTimer(this);
connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening);
_isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS);
}
void HTTPManager::incomingConnection(qintptr socketDescriptor) {
@ -157,3 +161,19 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
return _requestHandler && _requestHandler->handleHTTPRequest(connection, url);
}
void HTTPManager::isTcpServerListening() {
if (!isListening()) {
qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening";
bindSocket();
}
}
bool HTTPManager::bindSocket() {
qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port);
if (!listen(QHostAddress::Any, _port)) {
qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue";
QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE);
}
return true;
}

View file

@ -17,6 +17,7 @@
#define hifi_HTTPManager_h
#include <QtNetwork/QTcpServer>
#include <QtCore/QTimer>
class HTTPConnection;
class HTTPSConnection;
@ -35,14 +36,22 @@ public:
HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
private slots:
void isTcpServerListening();
private:
bool bindSocket();
protected:
/// Accepts all pending connections
virtual void incomingConnection(qintptr socketDescriptor);
virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
protected:
QString _documentRoot;
HTTPRequestHandler* _requestHandler;
QTimer* _isListeningTimer;
const quint16 _port;
};
#endif // hifi_HTTPManager_h

View file

@ -26,4 +26,6 @@ find_package(PolyVox REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES})
setup_memory_debugger()
link_hifi_libraries(shared gpu script-engine render render-utils)

View file

@ -861,6 +861,8 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
if (entityScript.property("clickDownOnEntity").isValid()) {
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
}
} else {
emit mousePressOffEntity(rayPickResult, event, deviceID);
}
_lastMouseEvent = MouseEvent(*event, deviceID);
_lastMouseEventValid = true;

View file

@ -95,6 +95,7 @@ public:
signals:
void mousePressOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mousePressOffEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mouseMoveOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);
void mouseReleaseOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId);

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <math.h>
#include <QByteArray>
#if defined(__GNUC__) && !defined(__clang__)
@ -40,9 +41,10 @@
#include "EntityTreeRenderer.h"
#include "polyvox_vert.h"
#include "polyvox_frag.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr;
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return std::make_shared<RenderablePolyVoxEntityItem>(entityID, properties);
@ -66,7 +68,7 @@ RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() {
}
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
int x, int y, int z) {
int x, int y, int z) {
// x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords.
switch (surfaceStyle) {
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
@ -78,6 +80,7 @@ bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::
return true;
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
if (x < 0 || y < 0 || z < 0 ||
x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) {
return false;
@ -112,7 +115,7 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize)
_onCount = 0;
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) {
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) {
// with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This
// changes how the surface extractor acts -- mainly it becomes impossible to have holes in the
// generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the
@ -156,8 +159,10 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize)
void RenderablePolyVoxEntityItem::updateVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) {
// if we are switching to or from "edged" we need to force a resize of _volData.
if (voxelSurfaceStyle == SURFACE_EDGED_CUBIC ||
_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) {
bool wasEdged = (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES);
bool willBeEdged = (voxelSurfaceStyle == SURFACE_EDGED_CUBIC || voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES);
if (wasEdged != willBeEdged) {
if (_volData) {
delete _volData;
}
@ -182,11 +187,11 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const {
glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units
switch (_voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
return scale / 2.0f;
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
return scale / -2.0f;
case PolyVoxEntityItem::SURFACE_CUBIC:
return scale / 2.0f;
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
return scale / -2.0f;
}
return glm::vec3(0.0f, 0.0f, 0.0f);
}
@ -202,6 +207,11 @@ glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const {
return scaled;
}
glm::mat4 RenderablePolyVoxEntityItem::localToVoxelMatrix() const {
glm::mat4 localToModelMatrix = glm::inverse(voxelToLocalMatrix());
return localToModelMatrix;
}
glm::mat4 RenderablePolyVoxEntityItem::voxelToWorldMatrix() const {
glm::mat4 rotation = glm::mat4_cast(getRotation());
glm::mat4 translation = glm::translate(getPosition());
@ -223,84 +233,115 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) {
// voxels all around the requested voxel space. Having the empty voxels around
// the edges changes how the surface extractor behaves.
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) {
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) {
return _volData->getVoxelAt(x + 1, y + 1, z + 1);
}
return _volData->getVoxelAt(x, y, z);
}
void RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) {
// set a voxel without recompressing the voxel data
assert(_volData);
bool result = false;
if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) {
return;
return false;
}
updateOnCount(x, y, z, toValue);
result = updateOnCount(x, y, z, toValue);
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC) {
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) {
_volData->setVoxelAt(x + 1, y + 1, z + 1, toValue);
} else {
_volData->setVoxelAt(x, y, z, toValue);
}
return result;
}
void RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) {
if (_locked) {
return;
void RenderablePolyVoxEntityItem::clearEdges() {
// if we are in an edged mode, make sure the outside surfaces are zeroed out.
if (_voxelSurfaceStyle == SURFACE_EDGED_CUBIC || _voxelSurfaceStyle == SURFACE_EDGED_MARCHING_CUBES) {
for (int z = 0; z < _volData->getDepth(); z++) {
for (int y = 0; y < _volData->getHeight(); y++) {
for (int x = 0; x < _volData->getWidth(); x++) {
if (x == 0 || y == 0 || z == 0 ||
x == _volData->getWidth() - 1 ||
y == _volData->getHeight() - 1 ||
z == _volData->getDepth() - 1) {
_volData->setVoxelAt(x, y, z, 0);
}
}
}
}
}
setVoxelInternal(x, y, z, toValue);
compressVolumeData();
}
void RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) {
if (_locked) {
return false;
}
bool result = setVoxelInternal(x, y, z, toValue);
if (result) {
compressVolumeData();
}
return result;
}
bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toValue) {
// keep _onCount up to date
if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) {
return;
return false;
}
uint8_t uVoxelValue = getVoxel(x, y, z);
if (toValue != 0) {
if (uVoxelValue == 0) {
_onCount++;
return true;
}
} else {
// toValue == 0
if (uVoxelValue != 0) {
_onCount--;
assert(_onCount >= 0);
return true;
}
}
return false;
}
void RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
bool result = false;
if (_locked) {
return;
return false;
}
for (int z = 0; z < _voxelVolumeSize.z; z++) {
for (int y = 0; y < _voxelVolumeSize.y; y++) {
for (int x = 0; x < _voxelVolumeSize.x; x++) {
setVoxelInternal(x, y, z, toValue);
result |= setVoxelInternal(x, y, z, toValue);
}
}
}
compressVolumeData();
if (result) {
compressVolumeData();
}
return result;
}
void RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) {
if (_locked) {
return;
return false;
}
// same as setVoxel but takes a vector rather than 3 floats.
setVoxel((int)position.x, (int)position.y, (int)position.z, toValue);
return setVoxel(roundf(position.x), roundf(position.y), roundf(position.z), toValue);
}
void RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) {
bool result = false;
if (_locked) {
return;
return false;
}
// This three-level for loop iterates over every voxel in the volume
@ -313,22 +354,24 @@ void RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi
float fDistToCenter = glm::distance(pos, center);
// If the current voxel is less than 'radius' units from the center then we make it solid.
if (fDistToCenter <= radius) {
updateOnCount(x, y, z, toValue);
setVoxelInternal(x, y, z, toValue);
result |= setVoxelInternal(x, y, z, toValue);
}
}
}
}
compressVolumeData();
if (result) {
compressVolumeData();
}
return result;
}
void RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) {
bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) {
// glm::vec3 centerVoxelCoords = worldToVoxelCoordinates(centerWorldCoords);
glm::vec4 centerVoxelCoords = worldToVoxelMatrix() * glm::vec4(centerWorldCoords, 1.0f);
glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units
float scaleY = scale.y;
float radiusVoxelCoords = radiusWorldCoords / scaleY;
setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue);
return setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue);
}
class RaycastFunctor
@ -340,9 +383,10 @@ public:
}
bool operator()(PolyVox::SimpleVolume<unsigned char>::Sampler& sampler)
{
int x = sampler.getPosition().getX();
int y = sampler.getPosition().getY();
int z = sampler.getPosition().getZ();
PolyVox::Vector3DInt32 positionIndex = sampler.getPosition();
int x = positionIndex.getX();
int y = positionIndex.getY();
int z = positionIndex.getZ();
if (!inBounds(_vol, x, y, z)) {
return true;
@ -351,8 +395,8 @@ public:
if (sampler.getVoxel() == 0) {
return true; // keep raycasting
}
PolyVox::Vector3DInt32 positionIndex = sampler.getPosition();
_result = glm::vec4(positionIndex.getX(), positionIndex.getY(), positionIndex.getZ(), 1.0f);
_result = glm::vec4((float)x, (float)y, (float)z, 1.0f);
return false;
}
glm::vec4 _result;
@ -367,13 +411,16 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
void** intersectedObject,
bool precisionPicking) const
{
// TODO -- correctly pick against marching-cube generated meshes
if (_needsModelReload || !precisionPicking) {
// just intersect with bounding box
return true;
}
// the PolyVox ray intersection code requires a near and far point.
glm::mat4 wtvMatrix = worldToVoxelMatrix();
glm::mat4 vtwMatrix = voxelToWorldMatrix();
glm::mat4 vtlMatrix = voxelToLocalMatrix();
glm::vec3 normDirection = glm::normalize(direction);
// the PolyVox ray intersection code requires a near and far point.
@ -396,23 +443,58 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
return false;
}
glm::vec4 result = callback._result;
switch (_voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
result -= glm::vec4(1, 1, 1, 0); // compensate for the extra voxel border
break;
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
case PolyVoxEntityItem::SURFACE_CUBIC:
break;
// result is in voxel-space coordinates.
glm::vec4 result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f);
// set up ray tests against each face of the voxel.
glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f)));
glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f)));
glm::vec3 minYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.0f, 0.5f, 0.0f)));
glm::vec3 maxYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 1.0f, 0.5f, 0.0f)));
glm::vec3 minZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 0.0f, 0.0f)));
glm::vec3 maxZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 1.0f, 0.0f)));
glm::vec4 baseDimensions = glm::vec4(1.0, 1.0, 1.0, 0.0);
glm::vec3 worldDimensions = glm::vec3(vtlMatrix * baseDimensions);
glm::vec2 xDimensions = glm::vec2(worldDimensions.z, worldDimensions.y);
glm::vec2 yDimensions = glm::vec2(worldDimensions.x, worldDimensions.z);
glm::vec2 zDimensions = glm::vec2(worldDimensions.x, worldDimensions.y);
glm::quat vtwRotation = extractRotation(vtwMatrix);
glm::quat minXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
glm::quat maxXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
glm::quat minYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f));
glm::quat maxYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f));
glm::quat minZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f));
glm::quat maxZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f));
float bestDx = FLT_MAX;
bool hit[ 6 ];
float dx[ 6 ] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX};
hit[0] = findRayRectangleIntersection(origin, direction, minXRotation, minXPosition, xDimensions, dx[0]);
hit[1] = findRayRectangleIntersection(origin, direction, maxXRotation, maxXPosition, xDimensions, dx[1]);
hit[2] = findRayRectangleIntersection(origin, direction, minYRotation, minYPosition, yDimensions, dx[2]);
hit[3] = findRayRectangleIntersection(origin, direction, maxYRotation, maxYPosition, yDimensions, dx[3]);
hit[4] = findRayRectangleIntersection(origin, direction, minZRotation, minZPosition, zDimensions, dx[4]);
hit[5] = findRayRectangleIntersection(origin, direction, maxZRotation, maxZPosition, zDimensions, dx[5]);
bool ok = false;
for (int i = 0; i < 6; i ++) {
if (hit[ i ] && dx[ i ] < bestDx) {
face = (BoxFace)i;
distance = dx[ i ];
ok = true;
bestDx = dx[ i ];
}
}
result -= glm::vec4(0.5f, 0.5f, 0.5f, 0.0f);
glm::vec4 intersectedWorldPosition = voxelToWorldMatrix() * result;
distance = glm::distance(glm::vec3(intersectedWorldPosition), origin);
face = BoxFace::MIN_X_FACE; // XXX
if (!ok) {
// if the attempt to put the ray against one of the voxel-faces fails, just return the center
glm::vec4 intersectedWorldPosition = vtwMatrix * (result + vec4(0.5f, 0.5f, 0.5f, 0.0f));
distance = glm::distance(glm::vec3(intersectedWorldPosition), origin);
face = BoxFace::MIN_X_FACE;
}
return true;
}
@ -421,6 +503,10 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
// compress the data in _volData and save the results. The compressed form is used during
// saves to disk and for transmission over the wire
void RenderablePolyVoxEntityItem::compressVolumeData() {
#ifdef WANT_DEBUG
auto startTime = usecTimestampNow();
#endif
quint16 voxelXSize = _voxelVolumeSize.x;
quint16 voxelYSize = _voxelVolumeSize.y;
quint16 voxelZSize = _voxelVolumeSize.z;
@ -471,6 +557,10 @@ void RenderablePolyVoxEntityItem::compressVolumeData() {
_dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS;
_needsModelReload = true;
#ifdef WANT_DEBUG
qDebug() << "RenderablePolyVoxEntityItem::compressVolumeData" << (usecTimestampNow() - startTime) << getName();
#endif
}
@ -506,11 +596,11 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
for (int y = 0; y < voxelYSize; y++) {
for (int x = 0; x < voxelXSize; x++) {
int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x;
updateOnCount(x, y, z, uncompressedData[uncompressedIndex]);
setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]);
}
}
}
clearEdges();
#ifdef WANT_DEBUG
qDebug() << "--------------- voxel decompress ---------------";
@ -553,53 +643,112 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
}
_points.clear();
unsigned int i = 0;
glm::mat4 wToM = voxelToLocalMatrix();
AABox box;
glm::mat4 vtoM = voxelToLocalMatrix();
for (int z = 0; z < _voxelVolumeSize.z; z++) {
for (int y = 0; y < _voxelVolumeSize.y; y++) {
for (int x = 0; x < _voxelVolumeSize.x; x++) {
if (getVoxel(x, y, z) > 0) {
QVector<glm::vec3> pointsInPart;
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES ||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) {
unsigned int i = 0;
/* pull top-facing triangles into polyhedrons so they can be walked on */
const model::MeshPointer& mesh = _modelGeometry.getMesh();
const gpu::BufferView vertexBufferView = mesh->getVertexBuffer();
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
while (it != indexBufferView.cend<uint32_t>()) {
uint32_t p0Index = *(it++);
uint32_t p1Index = *(it++);
uint32_t p2Index = *(it++);
float offL = -0.5f;
float offH = 0.5f;
const glm::vec3& p0 = vertexBufferView.get<const glm::vec3>(p0Index);
const glm::vec3& p1 = vertexBufferView.get<const glm::vec3>(p1Index);
const glm::vec3& p2 = vertexBufferView.get<const glm::vec3>(p2Index);
glm::vec3 p000 = glm::vec3(wToM * glm::vec4(x + offL, y + offL, z + offL, 1.0f));
glm::vec3 p001 = glm::vec3(wToM * glm::vec4(x + offL, y + offL, z + offH, 1.0f));
glm::vec3 p010 = glm::vec3(wToM * glm::vec4(x + offL, y + offH, z + offL, 1.0f));
glm::vec3 p011 = glm::vec3(wToM * glm::vec4(x + offL, y + offH, z + offH, 1.0f));
glm::vec3 p100 = glm::vec3(wToM * glm::vec4(x + offH, y + offL, z + offL, 1.0f));
glm::vec3 p101 = glm::vec3(wToM * glm::vec4(x + offH, y + offL, z + offH, 1.0f));
glm::vec3 p110 = glm::vec3(wToM * glm::vec4(x + offH, y + offH, z + offL, 1.0f));
glm::vec3 p111 = glm::vec3(wToM * glm::vec4(x + offH, y + offH, z + offH, 1.0f));
glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face
glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0));
glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET;
box += p000;
box += p001;
box += p010;
box += p011;
box += p100;
box += p101;
box += p110;
box += p111;
glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f));
glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f));
glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f));
glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f));
pointsInPart << p000;
pointsInPart << p001;
pointsInPart << p010;
pointsInPart << p011;
pointsInPart << p100;
pointsInPart << p101;
pointsInPart << p110;
pointsInPart << p111;
box += p0Model;
box += p1Model;
box += p2Model;
box += p3Model;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart;
QVector<glm::vec3> pointsInPart;
pointsInPart << p0Model;
pointsInPart << p1Model;
pointsInPart << p2Model;
pointsInPart << p3Model;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart;
}
} else {
unsigned int i = 0;
for (int z = 0; z < _voxelVolumeSize.z; z++) {
for (int y = 0; y < _voxelVolumeSize.y; y++) {
for (int x = 0; x < _voxelVolumeSize.x; x++) {
if (getVoxel(x, y, z) > 0) {
if ((x > 0 && getVoxel(x - 1, y, z) > 0) &&
(y > 0 && getVoxel(x, y - 1, z) > 0) &&
(z > 0 && getVoxel(x, y, z - 1) > 0) &&
(x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) &&
(y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) &&
(z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) {
// this voxel has neighbors in every cardinal direction, so there's no need
// to include it in the collision hull.
continue;
}
QVector<glm::vec3> pointsInPart;
float offL = -0.5f;
float offH = 0.5f;
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) {
offL += 1.0f;
offH += 1.0f;
}
glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f));
glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f));
glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f));
glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f));
glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f));
glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f));
glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f));
glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f));
box += p000;
box += p001;
box += p010;
box += p011;
box += p100;
box += p101;
box += p110;
box += p111;
pointsInPart << p000;
pointsInPart << p001;
pointsInPart << p010;
pointsInPart << p011;
pointsInPart << p100;
pointsInPart << p101;
pointsInPart << p110;
pointsInPart << p111;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart;
}
}
}
}
@ -629,10 +778,15 @@ void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) {
}
void RenderablePolyVoxEntityItem::getModel() {
#ifdef WANT_DEBUG
auto startTime = usecTimestampNow();
#endif
// A mesh object to hold the result of surface extraction
PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> polyVoxMesh;
switch (_voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(_volData, _volData->getEnclosingRegion(), &polyVoxMesh);
@ -682,6 +836,10 @@ void RenderablePolyVoxEntityItem::getModel() {
#endif
_needsModelReload = false;
#ifdef WANT_DEBUG
qDebug() << "RenderablePolyVoxEntityItem::getModel" << (usecTimestampNow() - startTime) << getName();
#endif
}
void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
@ -737,8 +895,6 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
_zTexture = DependencyManager::get<TextureCache>()->getTexture(_zTextureURL);
}
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
if (_xTexture) {
batch.setResourceTexture(0, _xTexture->getGPUTexture());
} else {
@ -802,3 +958,19 @@ namespace render {
}
}
}
glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const {
return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f));
}
glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const {
return glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f));
}
glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const {
return glm::vec3(voxelToLocalMatrix() * glm::vec4(voxelCoords, 0.0f));
}
glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& localCoords) const {
return glm::vec3(localToVoxelMatrix() * glm::vec4(localCoords, 0.0f));
}

View file

@ -54,9 +54,9 @@ public:
}
virtual uint8_t getVoxel(int x, int y, int z);
virtual void setVoxel(int x, int y, int z, uint8_t toValue);
virtual bool setVoxel(int x, int y, int z, uint8_t toValue);
void updateOnCount(int x, int y, int z, uint8_t new_value);
bool updateOnCount(int x, int y, int z, uint8_t new_value);
void render(RenderArgs* args);
virtual bool supportsDetailedRayIntersection() const { return true; }
@ -71,23 +71,26 @@ public:
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
glm::vec3 getSurfacePositionAdjustment() const;
glm::mat4 voxelToWorldMatrix() const;
glm::mat4 voxelToLocalMatrix() const;
glm::mat4 worldToVoxelMatrix() const;
glm::mat4 voxelToLocalMatrix() const;
glm::mat4 localToVoxelMatrix() const;
virtual ShapeType getShapeType() const;
virtual bool isReadyToComputeShape();
virtual void computeShapeInfo(ShapeInfo& info);
virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const;
virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const;
virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const;
virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3& localCoords) const;
// coords are in voxel-volume space
virtual void setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue);
virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue);
virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue);
// coords are in world-space
virtual void setSphere(glm::vec3 center, float radius, uint8_t toValue);
virtual void setAll(uint8_t toValue);
virtual void setVoxelInVolume(glm::vec3 position, uint8_t toValue);
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue);
virtual bool setAll(uint8_t toValue);
virtual void setXTextureURL(QString xTextureURL);
virtual void setYTextureURL(QString yTextureURL);
@ -107,10 +110,10 @@ private:
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize.
void setVoxelInternal(int x, int y, int z, uint8_t toValue);
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
void compressVolumeData();
void decompressVolumeData();
void clearEdges();
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
model::Geometry _modelGeometry;

View file

@ -56,6 +56,16 @@ RenderableWebEntityItem::~RenderableWebEntityItem() {
}
void RenderableWebEntityItem::render(RenderArgs* args) {
#ifdef WANT_EXTRA_DEBUGGING
{
gpu::Batch& batch = *args->_batch;
batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
glm::vec4 cubeColor{ 1.0f, 0.0f, 0.0f, 1.0f};
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {

View file

@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) {
_model = getModel();
_needsInitialSimulation = true;
_model->setURL(getCompoundShapeURL(), QUrl(), true, true);
_model->setURL(getCompoundShapeURL());
}
if (oldPosition != getPosition() ||
oldRotation != getRotation() ||
@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() {
void RenderableZoneEntityItem::updateGeometry() {
if (_model && !_model->isActive() && hasCompoundShapeURL()) {
// Since we have a delayload, we need to update the geometry if it has been downloaded
_model->setURL(getCompoundShapeURL(), QUrl(), true);
_model->setURL(getCompoundShapeURL());
}
if (_model && _model->isActive() && _needsInitialSimulation) {
initialSimulation();

View file

@ -36,9 +36,9 @@ void main(void) {
float inPositionY = (_inPosition.y - 0.5) / voxelVolumeSize.y;
float inPositionZ = (_inPosition.z - 0.5) / voxelVolumeSize.z;
vec4 xyDiffuse = texture2D(xMap, vec2(-inPositionX, -inPositionY));
vec4 xzDiffuse = texture2D(yMap, vec2(-inPositionX, inPositionZ));
vec4 yzDiffuse = texture2D(zMap, vec2(inPositionZ, -inPositionY));
vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY));
vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ));
vec4 yzDiffuse = texture(zMap, vec2(inPositionZ, -inPositionY));
vec3 xyDiffuseScaled = xyDiffuse.rgb * abs(worldNormal.z);
vec3 xzDiffuseScaled = xzDiffuse.rgb * abs(worldNormal.y);

View file

@ -1,5 +1,7 @@
set(TARGET_NAME entities)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)

View file

@ -25,7 +25,8 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree,
{
// caller must have verified existence of newEntity
assert(_newEntity);
_newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, (float)TREE_SCALE);
_newEntityBox = _newEntity->getMaximumAACube().clamp((float)(-HALF_TREE_SCALE), (float)HALF_TREE_SCALE);
}
bool AddEntityOperator::preRecursion(OctreeElement* element) {

View file

@ -154,9 +154,9 @@ public:
DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString);
DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString);
DEFINE_PROPERTY(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool);
DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray);
DEFINE_PROPERTY(PROP_NORMALS, Normals, normals, QVector<glm::vec3>);
DEFINE_PROPERTY(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>);
DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray);
DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString);
DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString);
DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString);
@ -253,7 +253,7 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object
// define these inline here so the macros work
inline void EntityItemProperties::setPosition(const glm::vec3& value)
{ _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; }
{ _position = glm::clamp(value, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); _positionChanged = true; }
inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
debug << "EntityItemProperties[" << "\n";

View file

@ -415,7 +415,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
}
bool EntityScriptingInterface::setVoxels(QUuid entityID,
std::function<void(PolyVoxEntityItem&)> actor) {
std::function<bool(PolyVoxEntityItem&)> actor) {
if (!_entityTree) {
return false;
}
@ -435,7 +435,7 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
_entityTree->lockForWrite();
actor(*polyVoxEntity);
bool result = actor(*polyVoxEntity);
entity->setLastEdited(now);
entity->setLastBroadcast(now);
_entityTree->unlock();
@ -448,42 +448,41 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
properties.setLastEdited(now);
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return true;
return result;
}
bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor) {
if (!_entityTree) {
return false;
}
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::Line) {
return false;
}
auto now = usecTimestampNow();
auto lineEntity = std::static_pointer_cast<LineEntityItem>(entity);
_entityTree->lockForWrite();
bool success = actor(*lineEntity);
entity->setLastEdited(now);
entity->setLastBroadcast(now);
_entityTree->unlock();
_entityTree->lockForRead();
EntityItemProperties properties = entity->getProperties();
_entityTree->unlock();
properties.setLinePointsDirty();
properties.setLastEdited(now);
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return success;
}
@ -491,19 +490,19 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) {
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
polyVoxEntity.setSphere(center, radius, value);
return polyVoxEntity.setSphere(center, radius, value);
});
}
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
polyVoxEntity.setVoxelInVolume(position, value);
return polyVoxEntity.setVoxelInVolume(position, value);
});
}
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
polyVoxEntity.setAll(value);
return polyVoxEntity.setAll(value);
});
}
@ -512,16 +511,16 @@ bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::v
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType == EntityTypes::Line) {
return setPoints(entityID, [points](LineEntityItem& lineEntity) -> bool
{
return (LineEntityItem*)lineEntity.setLinePoints(points);
});
}
return false;
}
@ -658,3 +657,83 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID,
});
return result;
}
glm::vec3 EntityScriptingInterface::voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords) {
if (!_entityTree) {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToWorldCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToWorldCoords(voxelCoords);
}
glm::vec3 EntityScriptingInterface::worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords) {
if (!_entityTree) {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::worldCoordsToVoxelCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->worldCoordsToVoxelCoords(worldCoords);
}
glm::vec3 EntityScriptingInterface::voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords) {
if (!_entityTree) {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::voxelCoordsToLocalCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->voxelCoordsToLocalCoords(voxelCoords);
}
glm::vec3 EntityScriptingInterface::localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords) {
if (!_entityTree) {
return glm::vec3(0.0f);
}
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::localCoordsToVoxelCoords no entity with ID" << entityID;
return glm::vec3(0.0f);
}
EntityTypes::EntityType entityType = entity->getType();
if (entityType != EntityTypes::PolyVox) {
return glm::vec3(0.0f);
}
auto polyVoxEntity = std::dynamic_pointer_cast<PolyVoxEntityItem>(entity);
return polyVoxEntity->localCoordsToVoxelCoords(localCoords);
}

View file

@ -134,6 +134,11 @@ public slots:
Q_INVOKABLE QVector<QUuid> getActionIDs(const QUuid& entityID);
Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID);
Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords);
Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords);
Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords);
Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords);
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
@ -162,7 +167,7 @@ signals:
private:
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulation*, EntityItemPointer)> actor);
bool setVoxels(QUuid entityID, std::function<void(PolyVoxEntityItem&)> actor);
bool setVoxels(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
void queueEntityMessage(PacketType::Value packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -113,7 +113,7 @@ void EntitySimulation::sortEntitiesThatMoved() {
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
PerformanceTimer perfTimer("sortingEntities");
MovingEntitiesOperator moveOperator(_entityTree);
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
while (itemItr != _entitiesToSort.end()) {
EntityItemPointer entity = *itemItr;
@ -195,7 +195,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
bool wasRemoved = false;
uint32_t dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & EntityItem::DIRTY_POSITION) {
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE);
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
AACube newCube = entity->getMaximumAACube();
if (!domainBounds.touches(newCube)) {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";

View file

@ -873,7 +873,7 @@ int EntityTree::processEraseMessage(NLPacket& packet, const SharedNodePointer& s
break; // bail to prevent buffer overflow
}
QUuid entityID = QUuid::fromRfc4122(packet.read(NUM_BYTES_RFC4122_UUID));
QUuid entityID = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
EntityItemID entityItemID(entityID);
entityItemIDsToDelete << entityItemID;

View file

@ -444,14 +444,14 @@ bool EntityTreeElement::bestFitBounds(const AABox& bounds) const {
}
bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMin = glm::clamp(minPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
return _cube.contains(clampedMin) && _cube.contains(clampedMax);
}
bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const {
glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE);
glm::vec3 clampedMin = glm::clamp(minPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {

View file

@ -52,7 +52,7 @@ MovingEntitiesOperator::~MovingEntitiesOperator() {
void MovingEntitiesOperator::addEntityToMoveList(EntityItemPointer entity, const AACube& newCube) {
EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID());
AABox newCubeClamped = newCube.clamp(0.0f, (float)TREE_SCALE);
AABox newCubeClamped = newCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
if (_wantDebug) {
qCDebug(entities) << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------";

Some files were not shown because too many files have changed in this diff Show more