mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 16:19:27 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into mouse_pick_in_oculus
This commit is contained in:
commit
e246b979f7
52 changed files with 1042 additions and 665 deletions
15
BUILD.md
15
BUILD.md
|
@ -96,12 +96,13 @@ Currently building on Windows has been tested using the following compilers:
|
|||
|
||||
#####Windows SDK 7.1
|
||||
|
||||
Whichever version of Visual Studio you use, you will need [Microsoft Windows SDK for Windows 7 and .NET Framework 4](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
|
||||
If using Visual Studio 2010, or using Visual Studio 2013 but building as a Visual Studio 2010 project, you need [Microsoft Windows SDK for Windows 7 and .NET Framework 4](http://www.microsoft.com/en-us/download/details.aspx?id=8279).
|
||||
|
||||
NOTE: If using Visual Studio C++ 2010 Express, you need to follow a specific install order. See below before installing the Windows SDK.
|
||||
|
||||
######Windows 8.1
|
||||
You may have already downloaded the Windows 8 SDK (e.g. if you have previously installed Visual Studio 2013). If so, change CMAKE_PREFIX_PATH in %HIFI_DIR%\CMakeLists.txt to point to the Windows 8 SDK binaries. The default path is `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86`
|
||||
######Windows SDK 8.1
|
||||
|
||||
If using Visual Studio 2013 and building as a Visual Studio 2013 project you need the Windows 8 SDK which you should already have as part of installing Visual Studio 2013. You should be able to see it at `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86`.
|
||||
|
||||
#####Visual Studio C++ 2010 Express
|
||||
|
||||
|
@ -123,9 +124,11 @@ Some of the build instructions will ask you to start a Visual Studio Command Pro
|
|||
|
||||
#####Visual Studio 2013
|
||||
|
||||
This product must be purchased separately.
|
||||
You can use the Community or Professional editions of Visual Studio 2013.
|
||||
|
||||
Visual Studio 2013 doesn't have a shortcut to start a Visual Studio Command Prompt. Instead, start a regular command prompt and then run:
|
||||
You can start a Visual Studio 2013 command prompt using the shortcut provided in the Visual Studio Tools folder installed as part of Visual Studio 2013.
|
||||
|
||||
Or you can start a regular command prompt and then run:
|
||||
|
||||
"%VS120COMNTOOLS%\vsvars32.bat"
|
||||
|
||||
|
@ -146,6 +149,8 @@ Once Qt is installed, you need to manually configure the following:
|
|||
* Make sure the Qt runtime DLLs are loadable. You must do this before you attempt to build because some tools for the build depend on Qt. E.g., add to the PATH: `Qt\5.2.0\msvc2010_opengl\bin\`.
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.2.0\msvc2010_opengl` directory.
|
||||
|
||||
If building as a Visual Studio 2013 project, download and configure the msvc2013 version of Qt instead.
|
||||
|
||||
####External Libraries
|
||||
|
||||
CMake will need to know where the headers and libraries for required external dependencies are.
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QTimer>
|
||||
#include <EntityTree.h>
|
||||
#include <SimpleEntitySimulation.h>
|
||||
|
||||
#include "EntityServer.h"
|
||||
#include "EntityServerConsts.h"
|
||||
|
@ -20,7 +21,8 @@ const char* MODEL_SERVER_NAME = "Entity";
|
|||
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server";
|
||||
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
|
||||
|
||||
EntityServer::EntityServer(const QByteArray& packet) : OctreeServer(packet) {
|
||||
EntityServer::EntityServer(const QByteArray& packet)
|
||||
: OctreeServer(packet), _entitySimulation(NULL) {
|
||||
// nothing special to do here...
|
||||
}
|
||||
|
||||
|
@ -36,6 +38,12 @@ OctreeQueryNode* EntityServer::createOctreeQueryNode() {
|
|||
Octree* EntityServer::createTree() {
|
||||
EntityTree* tree = new EntityTree(true);
|
||||
tree->addNewlyCreatedHook(this);
|
||||
if (!_entitySimulation) {
|
||||
SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation();
|
||||
simpleSimulation->setEntityTree(tree);
|
||||
tree->setSimulation(simpleSimulation);
|
||||
_entitySimulation = simpleSimulation;
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ public:
|
|||
|
||||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode();
|
||||
virtual Octree* createTree();
|
||||
virtual char getMyNodeType() const { return NodeType::EntityServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
|
||||
virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; }
|
||||
|
@ -46,7 +45,11 @@ public:
|
|||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree();
|
||||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -62,7 +62,6 @@ public:
|
|||
|
||||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode() = 0;
|
||||
virtual Octree* createTree() = 0;
|
||||
virtual char getMyNodeType() const = 0;
|
||||
virtual PacketType getMyQueryMessageType() const = 0;
|
||||
virtual const char* getMyServerName() const = 0;
|
||||
|
@ -132,6 +131,7 @@ public slots:
|
|||
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree() = 0;
|
||||
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
|
||||
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
|
||||
bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result);
|
||||
|
|
|
@ -36,7 +36,6 @@ public:
|
|||
|
||||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode();
|
||||
virtual Octree* createTree();
|
||||
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; }
|
||||
virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; }
|
||||
|
@ -50,6 +49,7 @@ public:
|
|||
virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree();
|
||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject);
|
||||
|
||||
private:
|
||||
|
|
|
@ -48,8 +48,13 @@ elseif (UNIX)
|
|||
select_library_configurations(XINERAMA)
|
||||
|
||||
elseif (WIN32)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
if (MSVC10)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
elseif (MSVC12)
|
||||
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2013 HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
endif ()
|
||||
find_package(ATL)
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
|
||||
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||
|
||||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_shutdownEventListener(this),
|
||||
|
@ -52,7 +54,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_webAuthenticationStateSet(),
|
||||
_cookieSessionHash(),
|
||||
_automaticNetworkingSetting(),
|
||||
_settingsManager()
|
||||
_settingsManager(),
|
||||
_iceServerSocket(ICE_SERVER_DEFAULT_HOSTNAME, ICE_SERVER_DEFAULT_PORT)
|
||||
{
|
||||
LogUtils::init();
|
||||
|
||||
|
@ -337,57 +340,67 @@ bool DomainServer::optionallySetupAssignmentPayment() {
|
|||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
|
||||
if (!didSetupAccountManagerWithAccessToken()) {
|
||||
qDebug() << "Cannot setup domain-server automatic networking without an access token.";
|
||||
qDebug() << "Please add an access token to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
|
||||
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
|
||||
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
|
||||
|
||||
// setup our timer to check our IP via stun every X seconds
|
||||
QTimer* dynamicIPTimer = new QTimer(this);
|
||||
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
|
||||
|
||||
_automaticNetworkingSetting =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
|
||||
|
||||
// setup a timer to heartbeat with the ice-server every so often
|
||||
QTimer* iceHeartbeatTimer = new QTimer(this);
|
||||
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
|
||||
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
||||
|
||||
// call our sendHeartbeatToIceServer immediately anytime a local or public socket changes
|
||||
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
|
||||
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
|
||||
|
||||
// attempt to update our public socket now, this will send a heartbeat once we get public socket
|
||||
requestCurrentPublicSocketViaSTUN();
|
||||
|
||||
// in case the STUN lookup is still happening we should re-request a public socket once we get that address
|
||||
connect(&nodeList->getSTUNSockAddr(), &HifiSockAddr::lookupCompleted,
|
||||
this, &DomainServer::requestCurrentPublicSocketViaSTUN);
|
||||
|
||||
}
|
||||
|
||||
if (!didSetupAccountManagerWithAccessToken()) {
|
||||
qDebug() << "Cannot send heartbeat to data server without an access token.";
|
||||
qDebug() << "Add an access token to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
const QUuid& domainID = nodeList->getSessionUUID();
|
||||
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
|
||||
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
|
||||
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
|
||||
|
||||
// setup our timer to check our IP via stun every X seconds
|
||||
QTimer* dynamicIPTimer = new QTimer(this);
|
||||
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
|
||||
|
||||
// send public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate);
|
||||
|
||||
// attempt to update our sockets now
|
||||
requestCurrentPublicSocketViaSTUN();
|
||||
} else {
|
||||
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
|
||||
|
||||
// setup a timer to heartbeat with the ice-server every so often
|
||||
QTimer* iceHeartbeatTimer = new QTimer(this);
|
||||
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
|
||||
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
||||
|
||||
// call our sendHeartbeaToIceServer immediately anytime a local or public socket changes
|
||||
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
|
||||
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
|
||||
|
||||
// send our heartbeat to data server so it knows what our network settings are
|
||||
sendHeartbeatToDataServer();
|
||||
}
|
||||
|
||||
// attempt to update our sockets now
|
||||
requestCurrentPublicSocketViaSTUN();
|
||||
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
@ -1164,8 +1177,7 @@ void DomainServer::performICEUpdates() {
|
|||
}
|
||||
|
||||
void DomainServer::sendHeartbeatToIceServer() {
|
||||
static HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT);
|
||||
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR);
|
||||
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(_iceServerSocket);
|
||||
}
|
||||
|
||||
void DomainServer::sendICEPingPackets() {
|
||||
|
|
|
@ -154,6 +154,8 @@ private:
|
|||
QString _automaticNetworkingSetting;
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ var BUTTON_TURN_AROUND = Joysticks.BUTTON_RIGHT_STICK;
|
|||
|
||||
var BUTTON_FLY_UP = Joysticks.BUTTON_RIGHT_SHOULDER;
|
||||
var BUTTON_FLY_DOWN = Joysticks.BUTTON_LEFT_SHOULDER;
|
||||
var BUTTON_WARP = Joysticks.BUTTON_FACE_BOTTOM;
|
||||
var BUTTON_WARP = Joysticks.BUTTON_FACE_RIGHT;
|
||||
|
||||
var BUTTON_WARP_FORWARD = Joysticks.BUTTON_DPAD_UP;
|
||||
var BUTTON_WARP_BACKWARD = Joysticks.BUTTON_DPAD_DOWN;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
var gridColor = { red: 0, green: 0, blue: 0 };
|
||||
var gridColors = [
|
||||
{ red: 0, green: 0, blue: 0 },
|
||||
{ red: 128, green: 128, blue: 128 },
|
||||
{ red: 255, green: 255, blue: 255 },
|
||||
{ red: 255, green: 0, blue: 0 },
|
||||
{ red: 0, green: 255, blue: 0},
|
||||
{ red: 0, green: 0, blue: 255 },
|
||||
|
|
|
@ -245,10 +245,12 @@ function handleGrabBehavior(deltaTime) {
|
|||
}
|
||||
|
||||
// Update for joysticks and move button
|
||||
var THRUST_DEAD_ZONE = 0.1;
|
||||
var ROTATE_DEAD_ZONE = 0.1;
|
||||
function flyWithHydra(deltaTime) {
|
||||
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
|
||||
|
||||
if (thrustJoystickPosition.x != 0 || thrustJoystickPosition.y != 0) {
|
||||
if (Math.abs(thrustJoystickPosition.x) > THRUST_DEAD_ZONE || Math.abs(thrustJoystickPosition.y) > THRUST_DEAD_ZONE) {
|
||||
if (thrustMultiplier < MAX_THRUST_MULTIPLIER) {
|
||||
thrustMultiplier *= 1 + (deltaTime * THRUST_INCREASE_RATE);
|
||||
}
|
||||
|
@ -270,7 +272,7 @@ function flyWithHydra(deltaTime) {
|
|||
|
||||
// View Controller
|
||||
var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER);
|
||||
if (viewJoystickPosition.x != 0 || viewJoystickPosition.y != 0) {
|
||||
if (Math.abs(viewJoystickPosition.x) > ROTATE_DEAD_ZONE || Math.abs(viewJoystickPosition.y) > ROTATE_DEAD_ZONE) {
|
||||
|
||||
// change the body yaw based on our x controller
|
||||
var orientation = MyAvatar.orientation;
|
||||
|
|
|
@ -89,11 +89,19 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that.addEntity = function(entityID) {
|
||||
that.addEntity = function(entityID, toggleSelection) {
|
||||
if (entityID.isKnownID) {
|
||||
var idx = that.selections.indexOf(entityID);
|
||||
var idx = -1;
|
||||
for (var i = 0; i < that.selections.length; i++) {
|
||||
if (entityID.id == that.selections[i].id) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx == -1) {
|
||||
that.selections.push(entityID);
|
||||
} else if (toggleSelection) {
|
||||
that.selections.splice(idx, 1);
|
||||
}
|
||||
} else {
|
||||
var idx = that.pendingSelections.indexOf(entityID);
|
||||
|
@ -200,6 +208,13 @@ SelectionManager = (function() {
|
|||
return that;
|
||||
})();
|
||||
|
||||
// Normalize degrees to be in the range (-180, 180]
|
||||
function normalizeDegrees(degrees) {
|
||||
while (degrees > 180) degrees -= 360;
|
||||
while (degrees <= -180) degrees += 360;
|
||||
return degrees;
|
||||
}
|
||||
|
||||
SelectionDisplay = (function () {
|
||||
var that = {};
|
||||
|
||||
|
@ -207,6 +222,12 @@ SelectionDisplay = (function () {
|
|||
|
||||
var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.0075;
|
||||
|
||||
// These are multipliers for sizing the rotation degrees display while rotating an entity
|
||||
var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.2;
|
||||
var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.5;
|
||||
var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18;
|
||||
var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.17;
|
||||
|
||||
var showExtendedStretchHandles = false;
|
||||
|
||||
var spaceMode = SPACE_LOCAL;
|
||||
|
@ -214,7 +235,6 @@ SelectionDisplay = (function () {
|
|||
var overlayNames = new Array();
|
||||
var lastCameraPosition = Camera.getPosition();
|
||||
var lastCameraOrientation = Camera.getOrientation();
|
||||
var lastPlaneIntersection;
|
||||
|
||||
var handleHoverColor = { red: 224, green: 67, blue: 36 };
|
||||
var handleHoverAlpha = 1.0;
|
||||
|
@ -306,26 +326,46 @@ SelectionDisplay = (function () {
|
|||
var highlightBox = Overlays.addOverlay("cube", {
|
||||
position: { x:0, y: 0, z: 0},
|
||||
size: 1,
|
||||
color: { red: 180, green: 180, blue: 180},
|
||||
color: { red: 90, green: 90, blue: 90},
|
||||
alpha: 1,
|
||||
solid: false,
|
||||
visible: false,
|
||||
dashed: true,
|
||||
lineWidth: 1.0,
|
||||
lineWidth: 2.0,
|
||||
ignoreRayIntersection: true // this never ray intersects
|
||||
});
|
||||
|
||||
var selectionBox = Overlays.addOverlay("cube", {
|
||||
position: { x:0, y: 0, z: 0},
|
||||
size: 1,
|
||||
color: { red: 180, green: 180, blue: 180},
|
||||
color: { red: 255, green: 0, blue: 0},
|
||||
alpha: 1,
|
||||
solid: false,
|
||||
visible: false,
|
||||
dashed: true,
|
||||
dashed: false,
|
||||
lineWidth: 1.0,
|
||||
});
|
||||
|
||||
var selectionBoxes = [];
|
||||
|
||||
var rotationDegreesDisplay = Overlays.addOverlay("text3d", {
|
||||
position: { x:0, y: 0, z: 0},
|
||||
text: "",
|
||||
color: { red: 0, green: 0, blue: 0},
|
||||
backgroundColor: { red: 255, green: 255, blue: 255 },
|
||||
alpha: 0.7,
|
||||
visible: false,
|
||||
isFacingAvatar: true,
|
||||
drawInFront: true,
|
||||
ignoreRayIntersection: true,
|
||||
dimensions: { x: 0, y: 0 },
|
||||
lineHeight: 0.0,
|
||||
topMargin: 0,
|
||||
rightMargin: 0,
|
||||
bottomMargin: 0,
|
||||
leftMargin: 0,
|
||||
});
|
||||
|
||||
var grabberMoveUp = Overlays.addOverlay("billboard", {
|
||||
url: HIFI_PUBLIC_BUCKET + "images/up-arrow.png",
|
||||
position: { x:0, y: 0, z: 0},
|
||||
|
@ -585,6 +625,7 @@ SelectionDisplay = (function () {
|
|||
rotateOverlayCurrent,
|
||||
rotateZeroOverlay,
|
||||
rotateCurrentOverlay,
|
||||
rotationDegreesDisplay,
|
||||
xRailOverlay,
|
||||
yRailOverlay,
|
||||
zRailOverlay,
|
||||
|
@ -652,6 +693,9 @@ SelectionDisplay = (function () {
|
|||
for (var i = 0; i < allOverlays.length; i++) {
|
||||
Overlays.deleteOverlay(allOverlays[i]);
|
||||
}
|
||||
for (var i = 0; i < selectionBoxes.length; i++) {
|
||||
Overlays.deleteOverlay(selectionBoxes[i]);
|
||||
}
|
||||
};
|
||||
|
||||
that.highlightSelectable = function(entityID) {
|
||||
|
@ -676,13 +720,11 @@ SelectionDisplay = (function () {
|
|||
|
||||
if (event !== false) {
|
||||
pickRay = Camera.computePickRay(event.x, event.y);
|
||||
lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastCameraOrientation));
|
||||
|
||||
var wantDebug = false;
|
||||
if (wantDebug) {
|
||||
print("select() with EVENT...... ");
|
||||
print(" event.y:" + event.y);
|
||||
Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection);
|
||||
Vec3.print(" current position:", properties.position);
|
||||
}
|
||||
|
||||
|
@ -848,8 +890,8 @@ SelectionDisplay = (function () {
|
|||
} else {
|
||||
|
||||
yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 270, z: 0 });
|
||||
rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 });
|
||||
pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 });
|
||||
rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 });
|
||||
|
||||
yawNormal = { x: 0, y: 1, z: 0 };
|
||||
rollNormal = { x: 0, y: 0, z: 1 };
|
||||
|
@ -901,23 +943,6 @@ SelectionDisplay = (function () {
|
|||
var dimensions = selectionManager.worldDimensions;
|
||||
var position = selectionManager.worldPosition;
|
||||
|
||||
Overlays.editOverlay(baseOfEntityProjectionOverlay,
|
||||
{
|
||||
visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL",
|
||||
solid: true,
|
||||
// lineWidth: 2.0,
|
||||
position: {
|
||||
x: position.x,
|
||||
y: grid.getOrigin().y,
|
||||
z: position.z
|
||||
},
|
||||
dimensions: {
|
||||
x: dimensions.x,
|
||||
y: dimensions.z
|
||||
},
|
||||
rotation: rotation,
|
||||
});
|
||||
|
||||
|
||||
Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible });
|
||||
Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible });
|
||||
|
@ -954,7 +979,6 @@ SelectionDisplay = (function () {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
that.updateRotationHandles();
|
||||
that.highlightSelectable();
|
||||
|
||||
|
@ -1094,6 +1118,41 @@ SelectionDisplay = (function () {
|
|||
visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"),
|
||||
});
|
||||
|
||||
// Create more selection box overlays if we don't have enough
|
||||
var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length;
|
||||
for (var i = 0; i < overlaysNeeded; i++) {
|
||||
selectionBoxes.push(
|
||||
Overlays.addOverlay("cube", {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
size: 1,
|
||||
color: { red: 255, green: 153, blue: 0 },
|
||||
alpha: 1,
|
||||
solid: false,
|
||||
visible: false,
|
||||
dashed: false,
|
||||
lineWidth: 1.0,
|
||||
ignoreRayIntersection: true,
|
||||
}));
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
// Only show individual selections boxes if there is more than 1 selection
|
||||
if (selectionManager.selections.length > 1) {
|
||||
for (; i < selectionManager.selections.length; i++) {
|
||||
var properties = Entities.getEntityProperties(selectionManager.selections[i]);
|
||||
Overlays.editOverlay(selectionBoxes[i], {
|
||||
position: properties.position,
|
||||
rotation: properties.rotation,
|
||||
dimensions: properties.dimensions,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Hide any remaining selection boxes
|
||||
for (; i < selectionBoxes.length; i++) {
|
||||
Overlays.editOverlay(selectionBoxes[i], { visible: false });
|
||||
}
|
||||
|
||||
Overlays.editOverlay(grabberEdgeTR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTR });
|
||||
Overlays.editOverlay(grabberEdgeTL, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTL });
|
||||
Overlays.editOverlay(grabberEdgeTF, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTF });
|
||||
|
@ -1110,6 +1169,23 @@ SelectionDisplay = (function () {
|
|||
var grabberMoveUpOffset = 0.1;
|
||||
grabberMoveUpPosition = { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z }
|
||||
Overlays.editOverlay(grabberMoveUp, { visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" });
|
||||
|
||||
Overlays.editOverlay(baseOfEntityProjectionOverlay, {
|
||||
visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL",
|
||||
solid: true,
|
||||
position: {
|
||||
x: selectionManager.worldPosition.x,
|
||||
y: grid.getOrigin().y,
|
||||
z: selectionManager.worldPosition.z
|
||||
},
|
||||
dimensions: {
|
||||
x: selectionManager.worldDimensions.x,
|
||||
y: selectionManager.worldDimensions.z
|
||||
},
|
||||
rotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
that.setOverlaysVisible = function(isVisible) {
|
||||
|
@ -1117,6 +1193,10 @@ SelectionDisplay = (function () {
|
|||
for (var i = 0; i < length; i++) {
|
||||
Overlays.editOverlay(allOverlays[i], { visible: isVisible });
|
||||
}
|
||||
length = selectionBoxes.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
Overlays.editOverlay(selectionBoxes[i], { visible: isVisible });
|
||||
}
|
||||
};
|
||||
|
||||
that.unselect = function (entityID) {
|
||||
|
@ -1210,7 +1290,6 @@ SelectionDisplay = (function () {
|
|||
|
||||
if (wantDebug) {
|
||||
print("translateXZ... ");
|
||||
Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection);
|
||||
Vec3.print(" vector:", vector);
|
||||
Vec3.print(" newPosition:", properties.position);
|
||||
Vec3.print(" newPosition:", newPosition);
|
||||
|
@ -1222,10 +1301,17 @@ SelectionDisplay = (function () {
|
|||
};
|
||||
|
||||
var lastXYPick = null
|
||||
var upDownPickNormal = null;
|
||||
addGrabberTool(grabberMoveUp, {
|
||||
mode: "TRANSLATE_UP_DOWN",
|
||||
onBegin: function(event) {
|
||||
lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, Quat.getFront(lastCameraOrientation));
|
||||
pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
upDownPickNormal = Quat.getFront(lastCameraOrientation);
|
||||
// Remove y component so the y-axis lies along the plane we picking on - this will
|
||||
// give movements that follow the mouse.
|
||||
upDownPickNormal.y = 0;
|
||||
lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal);
|
||||
|
||||
SelectionManager.saveProperties();
|
||||
|
||||
|
@ -1253,11 +1339,9 @@ SelectionDisplay = (function () {
|
|||
pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
// translate mode left/right based on view toward entity
|
||||
var newIntersection = rayPlaneIntersection(pickRay,
|
||||
SelectionManager.worldPosition,
|
||||
Quat.getFront(lastCameraOrientation));
|
||||
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal);
|
||||
|
||||
var vector = Vec3.subtract(newIntersection, lastPlaneIntersection);
|
||||
var vector = Vec3.subtract(newIntersection, lastXYPick);
|
||||
vector = grid.snapToGrid(vector);
|
||||
|
||||
// we only care about the Y axis
|
||||
|
@ -1268,7 +1352,6 @@ SelectionDisplay = (function () {
|
|||
if (wantDebug) {
|
||||
print("translateUpDown... ");
|
||||
print(" event.y:" + event.y);
|
||||
Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection);
|
||||
Vec3.print(" newIntersection:", newIntersection);
|
||||
Vec3.print(" vector:", vector);
|
||||
Vec3.print(" newPosition:", newPosition);
|
||||
|
@ -1292,7 +1375,11 @@ SelectionDisplay = (function () {
|
|||
var vec3Mult = function(v1, v2) {
|
||||
return { x: v1.x * v2.x, y: v1.y * v2.y, z: v1.z * v2.z };
|
||||
}
|
||||
var makeStretchTool = function(stretchMode, direction, pivot) {
|
||||
// stretchMode - name of mode
|
||||
// direction - direction to stretch in
|
||||
// pivot - point to use as a pivot
|
||||
// offset - the position of the overlay tool relative to the selections center position
|
||||
var makeStretchTool = function(stretchMode, direction, pivot, offset) {
|
||||
var signs = {
|
||||
x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0),
|
||||
y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0),
|
||||
|
@ -1313,6 +1400,7 @@ SelectionDisplay = (function () {
|
|||
var initialDimensions = null;
|
||||
var initialIntersection = null;
|
||||
var initialProperties = null;
|
||||
var pickRayPosition = null;
|
||||
var rotation = null;
|
||||
|
||||
var onBegin = function(event) {
|
||||
|
@ -1321,13 +1409,22 @@ SelectionDisplay = (function () {
|
|||
rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0);
|
||||
|
||||
if (spaceMode == SPACE_LOCAL) {
|
||||
rotation = SelectionManager.localRotation;
|
||||
initialPosition = SelectionManager.localPosition;
|
||||
initialDimensions = SelectionManager.localDimensions;
|
||||
} else {
|
||||
rotation = SelectionManager.worldRotation;
|
||||
initialPosition = SelectionManager.worldPosition;
|
||||
initialDimensions = SelectionManager.worldDimensions;
|
||||
}
|
||||
|
||||
var scaledOffset = {
|
||||
x: initialDimensions.x * offset.x * 0.5,
|
||||
y: initialDimensions.y * offset.y * 0.5,
|
||||
z: initialDimensions.z * offset.z * 0.5,
|
||||
};
|
||||
pickRayPosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffset));
|
||||
|
||||
if (numDimensions == 1 && mask.x) {
|
||||
var start = Vec3.multiplyQbyV(rotation, { x: -10000, y: 0, z: 0 });
|
||||
start = Vec3.sum(start, properties.position);
|
||||
|
@ -1381,7 +1478,7 @@ SelectionDisplay = (function () {
|
|||
planeNormal = Vec3.multiplyQbyV(rotation, planeNormal);
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
lastPick = rayPlaneIntersection(pickRay,
|
||||
initialPosition,
|
||||
pickRayPosition,
|
||||
planeNormal);
|
||||
|
||||
// Overlays.editOverlay(normalLine, {
|
||||
|
@ -1416,7 +1513,7 @@ SelectionDisplay = (function () {
|
|||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
newPick = rayPlaneIntersection(pickRay,
|
||||
initialPosition,
|
||||
pickRayPosition,
|
||||
planeNormal);
|
||||
var vector = Vec3.subtract(newPick, lastPick);
|
||||
|
||||
|
@ -1467,7 +1564,6 @@ SelectionDisplay = (function () {
|
|||
var wantDebug = false;
|
||||
if (wantDebug) {
|
||||
print(stretchMode);
|
||||
Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection);
|
||||
Vec3.print(" newIntersection:", newIntersection);
|
||||
Vec3.print(" vector:", vector);
|
||||
Vec3.print(" oldPOS:", oldPOS);
|
||||
|
@ -1491,44 +1587,64 @@ SelectionDisplay = (function () {
|
|||
};
|
||||
};
|
||||
|
||||
function addStretchTool(overlay, mode, pivot, direction) {
|
||||
function addStretchTool(overlay, mode, pivot, direction, offset) {
|
||||
if (!pivot) {
|
||||
pivot = Vec3.multiply(-1, direction);
|
||||
pivot.y = direction.y;
|
||||
}
|
||||
var tool = makeStretchTool(mode, direction, pivot);
|
||||
var tool = makeStretchTool(mode, direction, pivot, offset);
|
||||
|
||||
addGrabberTool(overlay, tool);
|
||||
}
|
||||
|
||||
addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 });
|
||||
addStretchTool(grabberFAR, "STRETCH_FAR", { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 });
|
||||
addStretchTool(grabberTOP, "STRETCH_TOP", { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 });
|
||||
addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 });
|
||||
addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 });
|
||||
addStretchTool(grabberLEFT, "STRETCH_LEFT", { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 });
|
||||
addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 });
|
||||
addStretchTool(grabberFAR, "STRETCH_FAR", { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 });
|
||||
addStretchTool(grabberTOP, "STRETCH_TOP", { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 });
|
||||
addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 });
|
||||
addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 });
|
||||
addStretchTool(grabberLEFT, "STRETCH_LEFT", { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 });
|
||||
|
||||
addStretchTool(grabberLBN, "STRETCH_LBN", null, {x: 1, y: 0, z: 1});
|
||||
addStretchTool(grabberRBN, "STRETCH_RBN", null, {x: -1, y: 0, z: 1});
|
||||
addStretchTool(grabberLBF, "STRETCH_LBF", null, {x: 1, y: 0, z: -1});
|
||||
addStretchTool(grabberRBF, "STRETCH_RBF", null, {x: -1, y: 0, z: -1});
|
||||
addStretchTool(grabberLTN, "STRETCH_LTN", null, {x: 1, y: 0, z: 1});
|
||||
addStretchTool(grabberRTN, "STRETCH_RTN", null, {x: -1, y: 0, z: 1});
|
||||
addStretchTool(grabberLTF, "STRETCH_LTF", null, {x: 1, y: 0, z: -1});
|
||||
addStretchTool(grabberRTF, "STRETCH_RTF", null, {x: -1, y: 0, z: -1});
|
||||
addStretchTool(grabberLBN, "STRETCH_LBN", null, {x: 1, y: 0, z: 1}, { x: -1, y: -1, z: -1 });
|
||||
addStretchTool(grabberRBN, "STRETCH_RBN", null, {x: -1, y: 0, z: 1}, { x: 1, y: -1, z: -1 });
|
||||
addStretchTool(grabberLBF, "STRETCH_LBF", null, {x: 1, y: 0, z: -1}, { x: -1, y: -1, z: 1 });
|
||||
addStretchTool(grabberRBF, "STRETCH_RBF", null, {x: -1, y: 0, z: -1}, { x: 1, y: -1, z: 1 });
|
||||
addStretchTool(grabberLTN, "STRETCH_LTN", null, {x: 1, y: 0, z: 1}, { x: -1, y: 1, z: -1 });
|
||||
addStretchTool(grabberRTN, "STRETCH_RTN", null, {x: -1, y: 0, z: 1}, { x: 1, y: 1, z: -1 });
|
||||
addStretchTool(grabberLTF, "STRETCH_LTF", null, {x: 1, y: 0, z: -1}, { x: -1, y: 1, z: 1 });
|
||||
addStretchTool(grabberRTF, "STRETCH_RTF", null, {x: -1, y: 0, z: -1}, { x: 1, y: 1, z: 1 });
|
||||
|
||||
addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, {x: 1, y: 1, z: 0});
|
||||
addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, {x: -1, y: 1, z: 0});
|
||||
addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, {x: 0, y: 1, z: -1});
|
||||
addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, {x: 0, y: 1, z: 1});
|
||||
addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, {x: -1, y: 0, z: 0});
|
||||
addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, {x: 1, y: 0, z: 0});
|
||||
addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, {x: 0, y: 0, z: -1});
|
||||
addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, {x: 0, y: 0, z: 1});
|
||||
addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, {x: -1, y: 0, z: 1});
|
||||
addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, {x: 1, y: 0, z: 1});
|
||||
addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, {x: -1, y: 0, z: -1});
|
||||
addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, {x: 1, y: 0, z: -1});
|
||||
addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, {x: 1, y: 1, z: 0}, { x: 1, y: 1, z: 0 });
|
||||
addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, {x: -1, y: 1, z: 0}, { x: -1, y: 1, z: 0 });
|
||||
addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, {x: 0, y: 1, z: -1}, { x: 0, y: 1, z: -1 });
|
||||
addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, {x: 0, y: 1, z: 1}, { x: 0, y: 1, z: 1 });
|
||||
addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, {x: -1, y: 0, z: 0}, { x: 1, y: -1, z: 0 });
|
||||
addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, {x: 1, y: 0, z: 0}, { x: -1, y: -1, z: 0 });
|
||||
addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, {x: 0, y: 0, z: -1}, { x: 0, y: -1, z: -1 });
|
||||
addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, {x: 0, y: 0, z: 1}, { x: 0, y: -1, z: 1 });
|
||||
addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, {x: -1, y: 0, z: 1}, { x: 1, y: 0, z: -1 });
|
||||
addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, {x: 1, y: 0, z: 1}, { x: -1, y: 0, z: -1 });
|
||||
addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, {x: -1, y: 0, z: -1}, { x: 1, y: 0, z: 1 });
|
||||
addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, {x: 1, y: 0, z: -1}, { x: -1, y: 0, z: 1 });
|
||||
|
||||
function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) {
|
||||
var angle = angleFromZero * (Math.PI / 180);
|
||||
var position = {
|
||||
x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER,
|
||||
y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER,
|
||||
z: 0,
|
||||
};
|
||||
position = Vec3.multiplyQbyV(handleRotation, position);
|
||||
position = Vec3.sum(centerPosition, position);
|
||||
Overlays.editOverlay(rotationDegreesDisplay, {
|
||||
position: position,
|
||||
dimensions: {
|
||||
x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER,
|
||||
y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER
|
||||
},
|
||||
lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER,
|
||||
text: normalizeDegrees(angleFromZero),
|
||||
});
|
||||
}
|
||||
|
||||
var initialPosition = SelectionManager.worldPosition;
|
||||
addGrabberTool(yawHandle, {
|
||||
|
@ -1570,11 +1686,18 @@ SelectionDisplay = (function () {
|
|||
endAt: 0,
|
||||
innerRadius: 0.9,
|
||||
});
|
||||
|
||||
Overlays.editOverlay(rotationDegreesDisplay, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter);
|
||||
},
|
||||
onEnd: function(event, reason) {
|
||||
Overlays.editOverlay(rotateOverlayInner, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayOuter, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayCurrent, { visible: false });
|
||||
Overlays.editOverlay(rotationDegreesDisplay, { visible: false });
|
||||
|
||||
pushCommandForSelections();
|
||||
},
|
||||
|
@ -1605,12 +1728,9 @@ SelectionDisplay = (function () {
|
|||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||
|
||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||
var snapToInner = false;
|
||||
// var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
|
||||
if (distanceFromCenter < innerRadius) {
|
||||
angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
|
||||
snapToInner = true;
|
||||
}
|
||||
var snapToInner = distanceFromCenter < innerRadius;
|
||||
var snapAngle = snapToInner ? innerSnapAngle : 1.0;
|
||||
angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;
|
||||
|
||||
// for debugging
|
||||
if (debug) {
|
||||
|
@ -1632,7 +1752,9 @@ SelectionDisplay = (function () {
|
|||
rotation: Quat.multiply(yawChange, initialProperties.rotation),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter);
|
||||
|
||||
// update the rotation display accordingly...
|
||||
var startAtCurrent = 0;
|
||||
var endAtCurrent = angleFromZero;
|
||||
|
@ -1701,11 +1823,18 @@ SelectionDisplay = (function () {
|
|||
endAt: 0,
|
||||
innerRadius: 0.9,
|
||||
});
|
||||
|
||||
Overlays.editOverlay(rotationDegreesDisplay, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter);
|
||||
},
|
||||
onEnd: function(event, reason) {
|
||||
Overlays.editOverlay(rotateOverlayInner, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayOuter, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayCurrent, { visible: false });
|
||||
Overlays.editOverlay(rotationDegreesDisplay, { visible: false });
|
||||
|
||||
pushCommandForSelections();
|
||||
},
|
||||
|
@ -1736,11 +1865,9 @@ SelectionDisplay = (function () {
|
|||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||
|
||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||
var snapToInner = false;
|
||||
if (distanceFromCenter < innerRadius) {
|
||||
angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
|
||||
snapToInner = true;
|
||||
}
|
||||
var snapToInner = distanceFromCenter < innerRadius;
|
||||
var snapAngle = snapToInner ? innerSnapAngle : 1.0;
|
||||
angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;
|
||||
|
||||
// for debugging
|
||||
if (debug) {
|
||||
|
@ -1764,6 +1891,8 @@ SelectionDisplay = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter);
|
||||
|
||||
// update the rotation display accordingly...
|
||||
var startAtCurrent = 0;
|
||||
var endAtCurrent = angleFromZero;
|
||||
|
@ -1831,11 +1960,18 @@ SelectionDisplay = (function () {
|
|||
endAt: 0,
|
||||
innerRadius: 0.9,
|
||||
});
|
||||
|
||||
Overlays.editOverlay(rotationDegreesDisplay, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter);
|
||||
},
|
||||
onEnd: function(event, reason) {
|
||||
Overlays.editOverlay(rotateOverlayInner, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayOuter, { visible: false });
|
||||
Overlays.editOverlay(rotateOverlayCurrent, { visible: false });
|
||||
Overlays.editOverlay(rotationDegreesDisplay, { visible: false });
|
||||
|
||||
pushCommandForSelections();
|
||||
},
|
||||
|
@ -1866,11 +2002,9 @@ SelectionDisplay = (function () {
|
|||
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
|
||||
|
||||
var distanceFromCenter = Vec3.distance(center, result.intersection);
|
||||
var snapToInner = false;
|
||||
if (distanceFromCenter < innerRadius) {
|
||||
angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle;
|
||||
snapToInner = true;
|
||||
}
|
||||
var snapToInner = distanceFromCenter < innerRadius;
|
||||
var snapAngle = snapToInner ? innerSnapAngle : 1.0;
|
||||
angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;
|
||||
|
||||
// for debugging
|
||||
if (debug) {
|
||||
|
@ -1893,6 +2027,8 @@ SelectionDisplay = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter);
|
||||
|
||||
// update the rotation display accordingly...
|
||||
var startAtCurrent = 0;
|
||||
var endAtCurrent = angleFromZero;
|
||||
|
@ -1924,7 +2060,7 @@ SelectionDisplay = (function () {
|
|||
that.checkMove = function() {
|
||||
if (SelectionManager.hasSelection() &&
|
||||
(!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation))){
|
||||
that.select(selectionManager.selections[0], false, false);
|
||||
that.updateRotationHandles();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2176,11 +2312,8 @@ SelectionDisplay = (function () {
|
|||
|
||||
if (somethingClicked) {
|
||||
pickRay = Camera.computePickRay(event.x, event.y);
|
||||
lastPlaneIntersection = rayPlaneIntersection(pickRay, selectionManager.worldPosition,
|
||||
Quat.getFront(lastCameraOrientation));
|
||||
if (wantDebug) {
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection);
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,6 +177,8 @@ Grid = function(opts) {
|
|||
color: gridColor,
|
||||
alpha: gridAlpha,
|
||||
});
|
||||
|
||||
that.emitUpdate();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
|
@ -207,6 +209,7 @@ GridTool = function(opts) {
|
|||
|
||||
horizontalGrid.addListener(function(data) {
|
||||
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
|
||||
selectionDisplay.updateHandles();
|
||||
});
|
||||
|
||||
webView.eventBridge.webEventReceived.connect(function(data) {
|
||||
|
|
|
@ -51,8 +51,8 @@ var toolWidth = 50;
|
|||
|
||||
var MIN_ANGULAR_SIZE = 2;
|
||||
var MAX_ANGULAR_SIZE = 45;
|
||||
var allowLargeModels = false;
|
||||
var allowSmallModels = false;
|
||||
var allowLargeModels = true;
|
||||
var allowSmallModels = true;
|
||||
var wantEntityGlow = false;
|
||||
|
||||
var SPAWN_DISTANCE = 1;
|
||||
|
@ -476,7 +476,6 @@ function findClickedEntity(event) {
|
|||
var identify = Entities.identifyEntity(foundEntity);
|
||||
if (!identify.isKnownID) {
|
||||
print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")");
|
||||
selectionManager.clearSelections();
|
||||
return null;
|
||||
}
|
||||
foundEntity = identify;
|
||||
|
@ -485,74 +484,18 @@ function findClickedEntity(event) {
|
|||
return { pickRay: pickRay, entityID: foundEntity };
|
||||
}
|
||||
|
||||
var mouseHasMovedSincePress = false;
|
||||
|
||||
function mousePressEvent(event) {
|
||||
mouseHasMovedSincePress = false;
|
||||
|
||||
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (isActive) {
|
||||
var entitySelected = false;
|
||||
if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
|
||||
// Event handled; do nothing.
|
||||
return;
|
||||
} else {
|
||||
var result = findClickedEntity(event);
|
||||
if (result === null) {
|
||||
selectionManager.clearSelections();
|
||||
return;
|
||||
}
|
||||
var pickRay = result.pickRay;
|
||||
var foundEntity = result.entityID;
|
||||
|
||||
var properties = Entities.getEntityProperties(foundEntity);
|
||||
if (isLocked(properties)) {
|
||||
print("Model locked " + properties.id);
|
||||
} else {
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
// / | X - base of the perpendicular line
|
||||
// A---X----->B d - distance fom axis
|
||||
// x x - distance from A
|
||||
//
|
||||
// |X-A| = (P-A).B
|
||||
// X == A + ((P-A).B)B
|
||||
// d = |P-X|
|
||||
|
||||
var A = pickRay.origin;
|
||||
var B = Vec3.normalize(pickRay.direction);
|
||||
var P = properties.position;
|
||||
|
||||
var x = Vec3.dot(Vec3.subtract(P, A), B);
|
||||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
|
||||
|
||||
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
|
||||
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
|
||||
|
||||
if (0 < x && sizeOK) {
|
||||
entitySelected = true;
|
||||
selectedEntityID = foundEntity;
|
||||
orientation = MyAvatar.orientation;
|
||||
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
|
||||
|
||||
if (!event.isShifted) {
|
||||
selectionManager.clearSelections();
|
||||
}
|
||||
selectionManager.addEntity(foundEntity);
|
||||
|
||||
print("Model selected: " + foundEntity.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entitySelected) {
|
||||
selectionDisplay.select(selectedEntityID, event);
|
||||
}
|
||||
} else if (Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)) {
|
||||
var result = findClickedEntity(event);
|
||||
|
@ -572,6 +515,7 @@ function mousePressEvent(event) {
|
|||
var highlightedEntityID = { isKnownID: false };
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
mouseHasMovedSincePress = true;
|
||||
if (isActive) {
|
||||
// allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing
|
||||
if (selectionDisplay.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) {
|
||||
|
@ -615,6 +559,72 @@ function mouseReleaseEvent(event) {
|
|||
}
|
||||
|
||||
cameraManager.mouseReleaseEvent(event);
|
||||
|
||||
if (!mouseHasMovedSincePress) {
|
||||
mouseClickEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
function mouseClickEvent(event) {
|
||||
var result = findClickedEntity(event);
|
||||
if (result === null) {
|
||||
if (!event.isShifted) {
|
||||
selectionManager.clearSelections();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var pickRay = result.pickRay;
|
||||
var foundEntity = result.entityID;
|
||||
|
||||
var properties = Entities.getEntityProperties(foundEntity);
|
||||
if (isLocked(properties)) {
|
||||
print("Model locked " + properties.id);
|
||||
} else {
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
// / | X - base of the perpendicular line
|
||||
// A---X----->B d - distance fom axis
|
||||
// x x - distance from A
|
||||
//
|
||||
// |X-A| = (P-A).B
|
||||
// X == A + ((P-A).B)B
|
||||
// d = |P-X|
|
||||
|
||||
var A = pickRay.origin;
|
||||
var B = Vec3.normalize(pickRay.direction);
|
||||
var P = properties.position;
|
||||
|
||||
var x = Vec3.dot(Vec3.subtract(P, A), B);
|
||||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
|
||||
|
||||
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
|
||||
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
|
||||
|
||||
if (0 < x && sizeOK) {
|
||||
entitySelected = true;
|
||||
selectedEntityID = foundEntity;
|
||||
orientation = MyAvatar.orientation;
|
||||
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
|
||||
|
||||
if (!event.isShifted) {
|
||||
selectionManager.clearSelections();
|
||||
}
|
||||
|
||||
var toggle = event.isShifted;
|
||||
selectionManager.addEntity(foundEntity, toggle);
|
||||
|
||||
print("Model selected: " + foundEntity.id);
|
||||
selectionDisplay.select(selectedEntityID, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
|
@ -644,9 +654,9 @@ function setupModelMenus() {
|
|||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L",
|
||||
afterItem: "Paste Models", isCheckable: true });
|
||||
afterItem: "Paste Models", isCheckable: true, isChecked: true });
|
||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S",
|
||||
afterItem: "Allow Select Large Models", isCheckable: true });
|
||||
afterItem: "Allow Select Large Models", isCheckable: true, isChecked: true });
|
||||
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
|
||||
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
|
||||
|
|
|
@ -442,6 +442,7 @@ void Application::aboutToQuit() {
|
|||
}
|
||||
|
||||
Application::~Application() {
|
||||
_entities.getTree()->setSimulation(NULL);
|
||||
qInstallMessageHandler(NULL);
|
||||
|
||||
saveSettings();
|
||||
|
@ -842,7 +843,7 @@ bool Application::event(QEvent* event) {
|
|||
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
||||
|
||||
if (!fileEvent->url().isEmpty()) {
|
||||
AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile());
|
||||
AddressManager::getInstance().handleLookupString(fileEvent->url().toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1574,7 +1575,9 @@ void Application::setFullscreen(bool fullscreen) {
|
|||
}
|
||||
_window->setWindowState(fullscreen ? (_window->windowState() | Qt::WindowFullScreen) :
|
||||
(_window->windowState() & ~Qt::WindowFullScreen));
|
||||
_window->show();
|
||||
if (!_aboutToQuit) {
|
||||
_window->show();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::setEnable3DTVMode(bool enable3DTVMode) {
|
||||
|
@ -1974,7 +1977,9 @@ void Application::init() {
|
|||
_entities.init();
|
||||
_entities.setViewFrustum(getViewFrustum());
|
||||
|
||||
_entityCollisionSystem.init(&_entityEditSender, _entities.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
|
||||
EntityTree* entityTree = _entities.getTree();
|
||||
_entityCollisionSystem.init(&_entityEditSender, entityTree, _voxels.getTree(), &_audio, &_avatarManager);
|
||||
entityTree->setSimulation(&_entityCollisionSystem);
|
||||
|
||||
// connect the _entityCollisionSystem to our script engine's EntityScriptingInterface
|
||||
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithVoxel,
|
||||
|
@ -2325,11 +2330,12 @@ void Application::update(float deltaTime) {
|
|||
|
||||
if (!_aboutToQuit) {
|
||||
PerformanceTimer perfTimer("entities");
|
||||
// NOTE: the _entities.update() call below will wait for lock
|
||||
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
|
||||
_entities.update(); // update the models...
|
||||
{
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
_entityCollisionSystem.update(); // collide the entities...
|
||||
}
|
||||
// The _entityCollisionSystem.updateCollisions() call below merely tries for lock,
|
||||
// and on failure it skips collision detection.
|
||||
_entityCollisionSystem.updateCollisions(); // collide the entities...
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -4013,26 +4019,23 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
|
|||
return _scriptEnginesHash[scriptURLString];
|
||||
}
|
||||
|
||||
ScriptEngine* scriptEngine;
|
||||
if (scriptFilename.isNull()) {
|
||||
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
|
||||
} else {
|
||||
// start the script on a new thread...
|
||||
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
|
||||
|
||||
if (!scriptEngine->hasScript()) {
|
||||
qDebug() << "Application::loadScript(), script failed to load...";
|
||||
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
UserActivityLogger::getInstance().loadedScript(scriptURLString);
|
||||
}
|
||||
ScriptEngine* scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
|
||||
scriptEngine->setUserLoaded(isUserLoaded);
|
||||
|
||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||
if (scriptFilename.isNull()) {
|
||||
// this had better be the script editor (we should de-couple so somebody who thinks they are loading a script
|
||||
// doesn't just get an empty script engine)
|
||||
|
||||
// we can complete setup now since there isn't a script we have to load
|
||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||
} else {
|
||||
// connect to the appropriate signals of this script engine
|
||||
connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded);
|
||||
connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &Application::handleScriptLoadError);
|
||||
|
||||
// get the script engine object to load the script at the designated script URL
|
||||
scriptEngine->loadURL(scriptUrl);
|
||||
}
|
||||
|
||||
// restore the main window's active state
|
||||
if (activateMainWindow && !loadScriptFromEditor) {
|
||||
|
@ -4043,6 +4046,22 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
|
|||
return scriptEngine;
|
||||
}
|
||||
|
||||
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
|
||||
|
||||
_scriptEnginesHash.insertMulti(scriptFilename, scriptEngine);
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
UserActivityLogger::getInstance().loadedScript(scriptFilename);
|
||||
|
||||
// register our application services and set it off on its own thread
|
||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||
}
|
||||
|
||||
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
||||
qDebug() << "Application::loadScript(), script failed to load...";
|
||||
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
||||
}
|
||||
|
||||
void Application::scriptFinished(const QString& scriptName) {
|
||||
const QString& scriptURLString = QUrl(scriptName).toString();
|
||||
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptURLString);
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
#include <QUndoStack>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <EntityCollisionSystem.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
@ -393,6 +393,9 @@ private slots:
|
|||
void timer();
|
||||
void idle();
|
||||
void aboutToQuit();
|
||||
|
||||
void handleScriptEngineLoaded(const QString& scriptFilename);
|
||||
void handleScriptLoadError(const QString& scriptFilename);
|
||||
|
||||
void connectedToDomain(const QString& hostname);
|
||||
|
||||
|
|
|
@ -1438,7 +1438,7 @@ void Audio::renderToolBox(int x, int y, bool boxed) {
|
|||
static const float PULSE_MAX = 1.0f;
|
||||
static const float PULSE_FREQUENCY = 1.0f; // in Hz
|
||||
qint64 now = usecTimestampNow();
|
||||
if (now - _iconPulseTimeReference > USECS_PER_SECOND) {
|
||||
if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) {
|
||||
// Prevents t from getting too big, which would diminish glm::cos precision
|
||||
_iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
const float HAIR_DAMPING = 0.99f;
|
||||
const float CONSTRAINT_RELAXATION = 10.0f;
|
||||
const float HAIR_ACCELERATION_COUPLING = 0.045f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.020f;
|
||||
const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.003f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.001f;
|
||||
const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.001f;
|
||||
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
|
||||
const float HAIR_STIFFNESS = 0.00f;
|
||||
const glm::vec3 HAIR_COLOR1(0.98f, 0.76f, 0.075f);
|
||||
|
|
|
@ -68,8 +68,7 @@ const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
|
|||
MyAvatar::MyAvatar() :
|
||||
Avatar(),
|
||||
_mousePressed(false),
|
||||
_bodyPitchDelta(0.0f),
|
||||
_bodyRollDelta(0.0f),
|
||||
_turningKeyPressTime(0.0f),
|
||||
_gravity(0.0f, 0.0f, 0.0f),
|
||||
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
|
||||
_shouldJump(false),
|
||||
|
@ -367,6 +366,15 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
const float TORSO_LENGTH = 0.5f;
|
||||
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
|
||||
const float MAX_LEAN = 45.0f;
|
||||
|
||||
// Invert left/right lean when in mirror mode
|
||||
// NOTE: this is kinda a hack, it's the same hack we use to make the head tilt. But it's not really a mirror
|
||||
// it just makes you feel like you're looking in a mirror because the body movements of the avatar appear to
|
||||
// match your body movements.
|
||||
if (OculusManager::isConnected() && Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) {
|
||||
relativePosition.x = -relativePosition.x;
|
||||
}
|
||||
|
||||
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
|
||||
-MAX_LEAN, MAX_LEAN));
|
||||
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
|
||||
|
@ -1160,26 +1168,41 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend
|
|||
|
||||
void MyAvatar::updateOrientation(float deltaTime) {
|
||||
// Gather rotation information from keyboard
|
||||
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
|
||||
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
|
||||
const float TIME_BETWEEN_HMD_TURNS = 0.5f;
|
||||
const float HMD_TURN_DEGREES = 22.5f;
|
||||
if (!OculusManager::isConnected()) {
|
||||
// Smoothly rotate body with arrow keys if not in HMD
|
||||
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
|
||||
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
|
||||
} else {
|
||||
// Jump turns if in HMD
|
||||
if (_driveKeys[ROT_RIGHT] || _driveKeys[ROT_LEFT]) {
|
||||
if (_turningKeyPressTime == 0.0f) {
|
||||
setOrientation(getOrientation() *
|
||||
glm::quat(glm::radians(glm::vec3(0.f, _driveKeys[ROT_LEFT] ? HMD_TURN_DEGREES : -HMD_TURN_DEGREES, 0.0f))));
|
||||
}
|
||||
_turningKeyPressTime += deltaTime;
|
||||
if (_turningKeyPressTime > TIME_BETWEEN_HMD_TURNS) {
|
||||
_turningKeyPressTime = 0.0f;
|
||||
}
|
||||
} else {
|
||||
_turningKeyPressTime = 0.0f;
|
||||
}
|
||||
}
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
|
||||
|
||||
// update body yaw by body yaw delta
|
||||
glm::quat orientation = getOrientation() * glm::quat(glm::radians(
|
||||
glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime));
|
||||
// update body orientation by movement inputs
|
||||
setOrientation(getOrientation() *
|
||||
glm::quat(glm::radians(glm::vec3(0.0f, _bodyYawDelta, 0.0f) * deltaTime)));
|
||||
|
||||
// decay body rotation momentum
|
||||
const float BODY_SPIN_FRICTION = 7.5f;
|
||||
float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime;
|
||||
if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; }
|
||||
_bodyPitchDelta *= bodySpinMomentum;
|
||||
_bodyYawDelta *= bodySpinMomentum;
|
||||
_bodyRollDelta *= bodySpinMomentum;
|
||||
|
||||
float MINIMUM_ROTATION_RATE = 2.0f;
|
||||
if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; }
|
||||
if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; }
|
||||
if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; }
|
||||
|
||||
if (OculusManager::isConnected()) {
|
||||
// these angles will be in radians
|
||||
|
@ -1192,8 +1215,11 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
// Record the angular velocity
|
||||
Head* head = getHead();
|
||||
glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll());
|
||||
head->setAngularVelocity(angularVelocity);
|
||||
if (deltaTime > 0.0f) {
|
||||
glm::vec3 angularVelocity(pitch - head->getBasePitch(), yaw - head->getBaseYaw(), roll - head->getBaseRoll());
|
||||
angularVelocity *= 1.0f / deltaTime;
|
||||
head->setAngularVelocity(angularVelocity);
|
||||
}
|
||||
|
||||
//Invert yaw and roll when in mirror mode
|
||||
if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) {
|
||||
|
@ -1208,8 +1234,6 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
}
|
||||
|
||||
// update the euler angles
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool hasFloor) {
|
||||
|
|
|
@ -204,8 +204,7 @@ protected:
|
|||
|
||||
private:
|
||||
bool _mousePressed;
|
||||
float _bodyPitchDelta; // degrees
|
||||
float _bodyRollDelta; // degrees
|
||||
float _turningKeyPressTime;
|
||||
glm::vec3 _gravity;
|
||||
float _distanceToNearestAvatar; // How close is the nearest avatar?
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ public:
|
|||
EntityTreeRenderer(bool wantScripts);
|
||||
virtual ~EntityTreeRenderer();
|
||||
|
||||
virtual Octree* createTree() { return new EntityTree(true); }
|
||||
virtual char getMyNodeType() const { return NodeType::EntityServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; }
|
||||
|
@ -108,6 +107,9 @@ public slots:
|
|||
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
|
||||
void entitySciptChanging(const EntityItemID& entityID);
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree() { return new EntityTree(true); }
|
||||
|
||||
private:
|
||||
void checkAndCallPreload(const EntityItemID& entityID);
|
||||
void checkAndCallUnload(const EntityItemID& entityID);
|
||||
|
|
|
@ -835,11 +835,16 @@ void GeometryReader::run() {
|
|||
fbxgeo = readSVO(_reply->readAll());
|
||||
} else {
|
||||
bool grabLightmaps = true;
|
||||
float lightmapLevel = 1.0f;
|
||||
// HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber...
|
||||
if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) {
|
||||
grabLightmaps = false;
|
||||
} else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) {
|
||||
lightmapLevel = 4.0f;
|
||||
} else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) {
|
||||
lightmapLevel = 3.5f;
|
||||
}
|
||||
fbxgeo = readFBX(_reply->readAll(), _mapping, grabLightmaps);
|
||||
fbxgeo = readFBX(_reply->readAll(), _mapping, grabLightmaps, lightmapLevel);
|
||||
}
|
||||
QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo));
|
||||
|
||||
|
|
|
@ -570,8 +570,6 @@ void ApplicationOverlay::renderControllerPointers() {
|
|||
static quint64 pressedTime[NUMBER_OF_MAGNIFIERS] = { 0ULL, 0ULL, 0ULL };
|
||||
static bool isPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false };
|
||||
static bool stateWhenPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false };
|
||||
static bool triggerPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false };
|
||||
static bool bumperPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false };
|
||||
|
||||
const HandData* handData = Application::getInstance()->getAvatar()->getHandData();
|
||||
|
||||
|
@ -612,30 +610,6 @@ void ApplicationOverlay::renderControllerPointers() {
|
|||
}
|
||||
}
|
||||
|
||||
//Check for UI active toggle
|
||||
if (palmData->getTrigger() == 1.0f) {
|
||||
if (!triggerPressed[index]) {
|
||||
if (bumperPressed[index]) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface,
|
||||
!Menu::getInstance()->isOptionChecked(MenuOption::UserInterface));
|
||||
}
|
||||
triggerPressed[index] = true;
|
||||
}
|
||||
} else {
|
||||
triggerPressed[index] = false;
|
||||
}
|
||||
if ((controllerButtons & BUTTON_FWD)) {
|
||||
if (!bumperPressed[index]) {
|
||||
if (triggerPressed[index]) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::UserInterface,
|
||||
!Menu::getInstance()->isOptionChecked(MenuOption::UserInterface));
|
||||
}
|
||||
bumperPressed[index] = true;
|
||||
}
|
||||
} else {
|
||||
bumperPressed[index] = false;
|
||||
}
|
||||
|
||||
//if we have the oculus, we should make the cursor smaller since it will be
|
||||
//magnified
|
||||
if (OculusManager::isConnected()) {
|
||||
|
|
|
@ -884,7 +884,8 @@ void ImportHeightfieldTool::updateSpanner() {
|
|||
}
|
||||
|
||||
HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name) :
|
||||
MetavoxelTool(editor, name, false) {
|
||||
MetavoxelTool(editor, name, false),
|
||||
_positionValid(false) {
|
||||
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setLayout(_form = new QFormLayout());
|
||||
|
@ -911,8 +912,10 @@ void HeightfieldBrushTool::render() {
|
|||
|
||||
float distance;
|
||||
if (!Application::getInstance()->getMetavoxels()->findFirstRayHeightfieldIntersection(origin, direction, distance)) {
|
||||
_positionValid = false;
|
||||
return;
|
||||
}
|
||||
_positionValid = true;
|
||||
Application::getInstance()->getMetavoxels()->renderHeightfieldCursor(
|
||||
_position = origin + distance * direction, _radius->value());
|
||||
}
|
||||
|
@ -924,7 +927,7 @@ bool HeightfieldBrushTool::eventFilter(QObject* watched, QEvent* event) {
|
|||
_radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE));
|
||||
return true;
|
||||
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
} else if (event->type() == QEvent::MouseButtonPress && _positionValid) {
|
||||
MetavoxelEditMessage message = { createEdit(static_cast<QMouseEvent*>(event)->button() == Qt::RightButton) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
return true;
|
||||
|
@ -1103,7 +1106,8 @@ void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, cons
|
|||
}
|
||||
|
||||
VoxelBrushTool::VoxelBrushTool(MetavoxelEditor* editor, const QString& name) :
|
||||
MetavoxelTool(editor, name, false, true) {
|
||||
MetavoxelTool(editor, name, false, true),
|
||||
_positionValid(false) {
|
||||
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setLayout(_form = new QFormLayout());
|
||||
|
@ -1132,8 +1136,10 @@ void VoxelBrushTool::render() {
|
|||
if (!(Application::getInstance()->getMetavoxels()->findFirstRayHeightfieldIntersection(
|
||||
origin, direction, heightfieldDistance) |
|
||||
Application::getInstance()->getMetavoxels()->findFirstRayVoxelIntersection(origin, direction, voxelDistance))) {
|
||||
_positionValid = false;
|
||||
return;
|
||||
}
|
||||
_positionValid = true;
|
||||
Application::getInstance()->getMetavoxels()->renderVoxelCursor(
|
||||
_position = origin + qMin(heightfieldDistance, voxelDistance) * direction, _radius->value());
|
||||
}
|
||||
|
@ -1145,7 +1151,7 @@ bool VoxelBrushTool::eventFilter(QObject* watched, QEvent* event) {
|
|||
_radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE));
|
||||
return true;
|
||||
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
} else if (event->type() == QEvent::MouseButtonPress && _positionValid) {
|
||||
MetavoxelEditMessage message = { createEdit(static_cast<QMouseEvent*>(event)->button() == Qt::RightButton) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||
return true;
|
||||
|
|
|
@ -326,6 +326,7 @@ protected:
|
|||
QDoubleSpinBox* _radius;
|
||||
|
||||
glm::vec3 _position;
|
||||
bool _positionValid;
|
||||
};
|
||||
|
||||
/// Allows raising or lowering parts of the heightfield.
|
||||
|
@ -456,6 +457,7 @@ protected:
|
|||
QDoubleSpinBox* _radius;
|
||||
|
||||
glm::vec3 _position;
|
||||
bool _positionValid;
|
||||
};
|
||||
|
||||
/// Allows texturing parts of the voxel field.
|
||||
|
|
|
@ -461,10 +461,10 @@ void Stats::display(
|
|||
if (_metavoxelSendTotal > 0 || _metavoxelReceiveTotal > 0) {
|
||||
stringstream reliableStats;
|
||||
if (_metavoxelSendTotal > 0) {
|
||||
reliableStats << "Upload: " << (_metavoxelSendProgress * 100 / _metavoxelSendTotal) << "% ";
|
||||
reliableStats << "Upload: " << (_metavoxelSendProgress * 100LL / _metavoxelSendTotal) << "% ";
|
||||
}
|
||||
if (_metavoxelReceiveTotal > 0) {
|
||||
reliableStats << "Download: " << (_metavoxelReceiveProgress * 100 / _metavoxelReceiveTotal) << "%";
|
||||
reliableStats << "Download: " << (_metavoxelReceiveProgress * 100LL / _metavoxelReceiveTotal) << "%";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color);
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
|
||||
// the width/height of the cached glyph textures
|
||||
const int IMAGE_SIZE = 256;
|
||||
const int IMAGE_SIZE = 512;
|
||||
|
||||
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
|
||||
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
|
||||
|
|
|
@ -21,6 +21,7 @@ ToolWindow::ToolWindow(QWidget* parent) :
|
|||
_hasShown(false),
|
||||
_lastGeometry() {
|
||||
|
||||
setDockOptions(QMainWindow::ForceTabbedDocks);
|
||||
Application::getInstance()->installEventFilter(this);
|
||||
}
|
||||
|
||||
|
@ -99,8 +100,17 @@ void ToolWindow::onChildVisibilityUpdated(bool visible) {
|
|||
}
|
||||
|
||||
void ToolWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget) {
|
||||
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
|
||||
|
||||
QMainWindow::addDockWidget(area, dockWidget);
|
||||
|
||||
// We want to force tabbing, so retabify all of our widgets.
|
||||
QDockWidget* lastDockWidget = dockWidget;
|
||||
foreach (QDockWidget* nextDockWidget, dockWidgets) {
|
||||
tabifyDockWidget(lastDockWidget, nextDockWidget);
|
||||
lastDockWidget = nextDockWidget;
|
||||
}
|
||||
|
||||
connect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,9 +48,6 @@ void DeleteEntityOperator::addEntityIDToDeleteList(const EntityItemID& searchEnt
|
|||
details.cube = details.containingElement->getAACube();
|
||||
_entitiesToDelete << details;
|
||||
_lookingCount++;
|
||||
_tree->trackDeletedEntity(searchEntityID);
|
||||
// before deleting any entity make sure to remove it from our Mortal, Changing, and Moving lists
|
||||
_tree->removeEntityFromSimulationLists(searchEntityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,13 +75,9 @@ bool DeleteEntityOperator::preRecursion(OctreeElement* element) {
|
|||
EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element);
|
||||
|
||||
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
|
||||
// path of the tree. For this operation, we want to recurse the branch of the tree if
|
||||
// and of the following are true:
|
||||
// * We have not yet found the old entity, and this branch contains our old entity
|
||||
// * We have not yet found the new entity, and this branch contains our new entity
|
||||
//
|
||||
// Note: it's often the case that the branch in question contains both the old entity
|
||||
// and the new entity.
|
||||
// path of the tree. For this operation, we want to recurse the branch of the tree if:
|
||||
// * We have not yet found the all entities, and
|
||||
// * this branch contains our some of the entities we're looking for.
|
||||
|
||||
bool keepSearching = false; // assume we don't need to search any more
|
||||
|
||||
|
@ -100,6 +93,8 @@ bool DeleteEntityOperator::preRecursion(OctreeElement* element) {
|
|||
if (entityTreeElement == details.containingElement) {
|
||||
EntityItemID entityItemID = details.entity->getEntityItemID();
|
||||
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity
|
||||
assert(theEntity);
|
||||
_tree->trackDeletedEntity(theEntity);
|
||||
entityTreeElement->removeEntityItem(theEntity); // remove it from the element
|
||||
_tree->setContainingElement(entityItemID, NULL); // update or id to element lookup
|
||||
delete theEntity; // now actually delete the entity!
|
||||
|
|
|
@ -16,27 +16,32 @@
|
|||
#include <CollisionInfo.h>
|
||||
#include <HeadData.h>
|
||||
#include <HandData.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "EntityCollisionSystem.h"
|
||||
#include "EntityEditPacketSender.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
const int MAX_COLLISIONS_PER_Entity = 16;
|
||||
|
||||
EntityCollisionSystem::EntityCollisionSystem(EntityEditPacketSender* packetSender,
|
||||
EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio,
|
||||
AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_Entity) {
|
||||
init(packetSender, Entities, voxels, audio, avatars);
|
||||
EntityCollisionSystem::EntityCollisionSystem()
|
||||
: SimpleEntitySimulation(),
|
||||
_packetSender(NULL),
|
||||
_voxels(NULL),
|
||||
_audio(NULL),
|
||||
_avatars(NULL),
|
||||
_collisions(MAX_COLLISIONS_PER_Entity) {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
|
||||
EntityTree* Entities, VoxelTree* voxels, AbstractAudioInterface* audio,
|
||||
AvatarHashMap* avatars) {
|
||||
EntityTree* entities, VoxelTree* voxels, AbstractAudioInterface* audio,
|
||||
AvatarHashMap* avatars) {
|
||||
assert(entities);
|
||||
setEntityTree(entities);
|
||||
_packetSender = packetSender;
|
||||
_entities = Entities;
|
||||
_voxels = voxels;
|
||||
_audio = audio;
|
||||
_avatars = avatars;
|
||||
|
@ -45,14 +50,15 @@ void EntityCollisionSystem::init(EntityEditPacketSender* packetSender,
|
|||
EntityCollisionSystem::~EntityCollisionSystem() {
|
||||
}
|
||||
|
||||
void EntityCollisionSystem::update() {
|
||||
void EntityCollisionSystem::updateCollisions() {
|
||||
PerformanceTimer perfTimer("collisions");
|
||||
assert(_entityTree);
|
||||
// update all Entities
|
||||
if (_entities->tryLockForRead()) {
|
||||
QList<EntityItem*>& movingEntities = _entities->getMovingEntities();
|
||||
foreach (EntityItem* entity, movingEntities) {
|
||||
if (_entityTree->tryLockForRead()) {
|
||||
foreach (EntityItem* entity, _movingEntities) {
|
||||
checkEntity(entity);
|
||||
}
|
||||
_entities->unlock();
|
||||
_entityTree->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,9 +133,8 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
|||
CollisionList collisions(MAX_COLLISIONS_PER_ENTITY);
|
||||
bool shapeCollisionsAccurate = false;
|
||||
|
||||
bool shapeCollisions = _entities->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
||||
bool shapeCollisions = _entityTree->findShapeCollisions(&entityA->getCollisionShapeInMeters(),
|
||||
collisions, Octree::NoLock, &shapeCollisionsAccurate);
|
||||
|
||||
|
||||
if (shapeCollisions) {
|
||||
for(int i = 0; i < collisions.size(); i++) {
|
||||
|
@ -203,7 +208,7 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
|||
propertiesA.setPosition(newPositionA * (float)TREE_SCALE);
|
||||
propertiesA.setLastEdited(now);
|
||||
|
||||
_entities->updateEntity(idA, propertiesA);
|
||||
_entityTree->updateEntity(idA, propertiesA);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA);
|
||||
}
|
||||
|
||||
|
@ -220,7 +225,7 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) {
|
|||
propertiesB.setPosition(newPositionB * (float)TREE_SCALE);
|
||||
propertiesB.setLastEdited(now);
|
||||
|
||||
_entities->updateEntity(idB, propertiesB);
|
||||
_entityTree->updateEntity(idB, propertiesB);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB);
|
||||
}
|
||||
}
|
||||
|
@ -326,6 +331,6 @@ void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const Collisi
|
|||
properties.setVelocity(velocity * (float)TREE_SCALE);
|
||||
properties.setLastEdited(usecTimestampNow());
|
||||
|
||||
_entities->updateEntity(entityItemID, properties);
|
||||
_entityTree->updateEntity(entityItemID, properties);
|
||||
_packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties);
|
||||
}
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <VoxelDetail.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "SimpleEntitySimulation.h"
|
||||
|
||||
class AbstractAudioInterface;
|
||||
class AvatarData;
|
||||
|
@ -32,19 +33,17 @@ class EntityEditPacketSender;
|
|||
class EntityTree;
|
||||
class VoxelTree;
|
||||
|
||||
class EntityCollisionSystem : public QObject {
|
||||
class EntityCollisionSystem : public QObject, public SimpleEntitySimulation {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityCollisionSystem(EntityEditPacketSender* packetSender = NULL, EntityTree* Entitys = NULL,
|
||||
VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL,
|
||||
AvatarHashMap* avatars = NULL);
|
||||
EntityCollisionSystem();
|
||||
|
||||
void init(EntityEditPacketSender* packetSender, EntityTree* Entitys, VoxelTree* voxels,
|
||||
void init(EntityEditPacketSender* packetSender, EntityTree* entities, VoxelTree* voxels,
|
||||
AbstractAudioInterface* audio = NULL, AvatarHashMap* _avatars = NULL);
|
||||
|
||||
~EntityCollisionSystem();
|
||||
|
||||
void update();
|
||||
void updateCollisions();
|
||||
|
||||
void checkEntity(EntityItem* Entity);
|
||||
void updateCollisionWithVoxels(EntityItem* Entity);
|
||||
|
@ -65,7 +64,6 @@ private:
|
|||
void emitGlobalEntityCollisionWithEntity(EntityItem* entityA, EntityItem* entityB, const CollisionInfo& penetration);
|
||||
|
||||
EntityEditPacketSender* _packetSender;
|
||||
EntityTree* _entities;
|
||||
VoxelTree* _voxels;
|
||||
AbstractAudioInterface* _audio;
|
||||
AvatarHashMap* _avatars;
|
||||
|
|
|
@ -292,16 +292,10 @@ public:
|
|||
uint32_t getUpdateFlags() const { return _updateFlags; }
|
||||
void clearUpdateFlags() { _updateFlags = 0; }
|
||||
|
||||
#ifdef USE_BULLET_PHYSICS
|
||||
EntityMotionState* getMotionState() const { return _motionState; }
|
||||
virtual EntityMotionState* createMotionState() { return NULL; }
|
||||
void destroyMotionState();
|
||||
#endif // USE_BULLET_PHYSICS
|
||||
SimulationState getSimulationState() const { return _simulationState; }
|
||||
|
||||
protected:
|
||||
friend class EntityTree;
|
||||
void setSimulationState(SimulationState state) { _simulationState = state; }
|
||||
protected:
|
||||
|
||||
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
|
||||
virtual void recalculateCollisionShape();
|
||||
|
|
20
libraries/entities/src/EntitySimulation.cpp
Normal file
20
libraries/entities/src/EntitySimulation.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// EntitySimulation.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.11.24
|
||||
// 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 "EntitySimulation.h"
|
||||
|
||||
void EntitySimulation::setEntityTree(EntityTree* tree) {
|
||||
if (_entityTree && _entityTree != tree) {
|
||||
clearEntities();
|
||||
}
|
||||
_entityTree = tree;
|
||||
}
|
||||
|
49
libraries/entities/src/EntitySimulation.h
Normal file
49
libraries/entities/src/EntitySimulation.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// EntitySimulation.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.11.24
|
||||
// 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_EntitySimulation_h
|
||||
#define hifi_EntitySimulation_h
|
||||
|
||||
#include <QSet>
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
||||
class EntitySimulation {
|
||||
public:
|
||||
EntitySimulation() : _entityTree(NULL) { }
|
||||
virtual ~EntitySimulation() {}
|
||||
|
||||
/// \param tree pointer to EntityTree which is stored internally
|
||||
virtual void setEntityTree(EntityTree* tree);
|
||||
|
||||
/// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted.
|
||||
virtual void update(QSet<EntityItem*>& entitiesToDelete) = 0;
|
||||
|
||||
/// \param entity pointer to EntityItem to add to the simulation
|
||||
/// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list
|
||||
virtual void addEntity(EntityItem* entity) = 0;
|
||||
|
||||
/// \param entity pointer to EntityItem to removed from the simulation
|
||||
/// \sideeffect the EntityItem::_simulationState member may be updated to indicate non-membership to internal list
|
||||
virtual void removeEntity(EntityItem* entity) = 0;
|
||||
|
||||
/// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation
|
||||
virtual void entityChanged(EntityItem* entity) = 0;
|
||||
|
||||
virtual void clearEntities() = 0;
|
||||
|
||||
EntityTree* getEntityTree() { return _entityTree; }
|
||||
|
||||
protected:
|
||||
EntityTree* _entityTree;
|
||||
};
|
||||
|
||||
#endif // hifi_EntitySimulation_h
|
|
@ -12,13 +12,14 @@
|
|||
#include <PerfStat.h>
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "EntitySimulation.h"
|
||||
|
||||
#include "AddEntityOperator.h"
|
||||
#include "DeleteEntityOperator.h"
|
||||
#include "MovingEntitiesOperator.h"
|
||||
#include "UpdateEntityOperator.h"
|
||||
|
||||
EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) {
|
||||
EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _simulation(NULL) {
|
||||
_rootElement = createNewElement();
|
||||
}
|
||||
|
||||
|
@ -34,14 +35,14 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) {
|
|||
|
||||
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
|
||||
// this would be a good place to clean up our entities...
|
||||
if (_simulation) {
|
||||
_simulation->clearEntities();
|
||||
}
|
||||
foreach (EntityTreeElement* element, _entityToElementMap) {
|
||||
element->cleanupEntities();
|
||||
}
|
||||
_entityToElementMap.clear();
|
||||
Octree::eraseAllOctreeElements(createNewRoot);
|
||||
_movingEntities.clear();
|
||||
_mortalEntities.clear();
|
||||
_changedEntities.clear();
|
||||
}
|
||||
|
||||
bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
||||
|
@ -75,27 +76,17 @@ EntityItem* EntityTree::getOrCreateEntityItem(const EntityItemID& entityID, cons
|
|||
}
|
||||
|
||||
/// Adds a new entity item to the tree
|
||||
void EntityTree::addEntityItem(EntityItem* entityItem) {
|
||||
// You should not call this on existing entities that are already part of the tree! Call updateEntity()
|
||||
EntityItemID entityID = entityItem->getEntityItemID();
|
||||
EntityTreeElement* containingElement = getContainingElement(entityID);
|
||||
if (containingElement) {
|
||||
qDebug() << "UNEXPECTED!!!! don't call addEntityItem() on existing EntityItems. entityID=" << entityID;
|
||||
return;
|
||||
}
|
||||
|
||||
// Recurse the tree and store the entity in the correct tree element
|
||||
AddEntityOperator theOperator(this, entityItem);
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
|
||||
void EntityTree::postAddEntity(EntityItem* entity) {
|
||||
assert(entity);
|
||||
// check to see if we need to simulate this entity..
|
||||
updateEntityState(entityItem);
|
||||
|
||||
if (_simulation) {
|
||||
_simulation->addEntity(entity);
|
||||
}
|
||||
_isDirty = true;
|
||||
emit addingEntity(entity->getEntityItemID());
|
||||
}
|
||||
|
||||
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
// You should not call this on existing entities that are already part of the tree! Call updateEntity()
|
||||
EntityTreeElement* containingElement = getContainingElement(entityID);
|
||||
if (!containingElement) {
|
||||
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID;
|
||||
|
@ -119,6 +110,9 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
|
|||
UpdateEntityOperator theOperator(this, containingElement, existingEntity, tempProperties);
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
_isDirty = true;
|
||||
if (_simulation && existingEntity->getUpdateFlags() != 0) {
|
||||
_simulation->entityChanged(existingEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -129,13 +123,14 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
|
|||
recurseTreeWithOperator(&theOperator);
|
||||
_isDirty = true;
|
||||
|
||||
updateEntityState(existingEntity);
|
||||
if (_simulation && existingEntity->getUpdateFlags() != 0) {
|
||||
_simulation->entityChanged(existingEntity);
|
||||
}
|
||||
|
||||
QString entityScriptAfter = existingEntity->getScript();
|
||||
if (entityScriptBefore != entityScriptAfter) {
|
||||
emitEntityScriptChanging(entityID); // the entity script has changed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
containingElement = getContainingElement(entityID);
|
||||
|
@ -171,33 +166,47 @@ EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItem
|
|||
result = EntityTypes::constructEntityItem(type, entityID, properties);
|
||||
|
||||
if (result) {
|
||||
// this does the actual adding of the entity
|
||||
addEntityItem(result);
|
||||
emitAddingEntity(entityID);
|
||||
// Recurse the tree and store the entity in the correct tree element
|
||||
AddEntityOperator theOperator(this, result);
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
|
||||
postAddEntity(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void EntityTree::trackDeletedEntity(const EntityItemID& entityID) {
|
||||
void EntityTree::trackDeletedEntity(EntityItem* entity) {
|
||||
if (_simulation) {
|
||||
_simulation->removeEntity(entity);
|
||||
}
|
||||
// this is only needed on the server to send delete messages for recently deleted entities to the viewers
|
||||
if (getIsServer()) {
|
||||
// set up the deleted entities ID
|
||||
quint64 deletedAt = usecTimestampNow();
|
||||
_recentlyDeletedEntitiesLock.lockForWrite();
|
||||
_recentlyDeletedEntityItemIDs.insert(deletedAt, entityID.id);
|
||||
_recentlyDeletedEntityItemIDs.insert(deletedAt, entity->getEntityItemID().id);
|
||||
_recentlyDeletedEntitiesLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::emitAddingEntity(const EntityItemID& entityItemID) {
|
||||
emit addingEntity(entityItemID);
|
||||
}
|
||||
|
||||
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) {
|
||||
emit entityScriptChanging(entityItemID);
|
||||
}
|
||||
|
||||
void EntityTree::setSimulation(EntitySimulation* simulation) {
|
||||
if (simulation) {
|
||||
// assert that the simulation's backpointer has already been properly connected
|
||||
assert(simulation->getEntityTree() == this);
|
||||
}
|
||||
if (_simulation && _simulation != simulation) {
|
||||
// It's important to clearEntities() on the simulation since taht will update each
|
||||
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
|
||||
_simulation->clearEntities();
|
||||
}
|
||||
_simulation = simulation;
|
||||
}
|
||||
|
||||
void EntityTree::deleteEntity(const EntityItemID& entityID) {
|
||||
emit deletingEntity(entityID);
|
||||
|
||||
|
@ -220,29 +229,6 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
|
|||
_isDirty = true;
|
||||
}
|
||||
|
||||
void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) {
|
||||
EntityItem* theEntity = findEntityByEntityItemID(entityID);
|
||||
|
||||
if (theEntity) {
|
||||
// make sure to remove it from any of our simulation lists
|
||||
EntityItem::SimulationState theState = theEntity->getSimulationState();
|
||||
switch (theState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(theEntity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(theEntity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_changedEntities.remove(theEntity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This method is used to find and fix entity IDs that are shifting from creator token based to known ID based entity IDs.
|
||||
/// This should only be used on a client side (viewing) tree. The typical usage is that a local editor has been creating
|
||||
/// entities in the local tree, those entities have creatorToken based entity IDs. But those entity edits are also sent up to
|
||||
|
@ -575,183 +561,29 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
|
|||
extraEncodeData->clear();
|
||||
}
|
||||
|
||||
void EntityTree::updateEntityState(EntityItem* entity) {
|
||||
EntityItem::SimulationState oldState = entity->getSimulationState();
|
||||
EntityItem::SimulationState newState = entity->computeSimulationState();
|
||||
if (newState != oldState) {
|
||||
switch (oldState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (newState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.push_back(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.push_back(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
entity->setSimulationState(newState);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::clearEntityState(EntityItem* entity) {
|
||||
EntityItem::SimulationState oldState = entity->getSimulationState();
|
||||
switch (oldState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
}
|
||||
|
||||
void EntityTree::entityChanged(EntityItem* entity) {
|
||||
_changedEntities.insert(entity);
|
||||
if (_simulation) {
|
||||
_simulation->entityChanged(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::update() {
|
||||
// our new strategy should be to segregate entities into three classes:
|
||||
// 1) stationary things that are not changing - most models
|
||||
// 2) mortal things - these are stationary but have a lifetime - then need to be checked,
|
||||
// can be touched linearly, and won't change the tree
|
||||
// 2) changing things - like things animating they can be touched linearly and they don't change the tree
|
||||
// 3) moving things - these need to scan the tree and update accordingly
|
||||
// finally - all things that need to be deleted, can be handled on a single delete pass.
|
||||
//
|
||||
// TODO: theoretically we could combine the move and delete tree passes...
|
||||
lockForWrite();
|
||||
quint64 now = usecTimestampNow();
|
||||
QSet<EntityItemID> entitiesToDelete;
|
||||
updateChangedEntities(now, entitiesToDelete);
|
||||
updateMovingEntities(now, entitiesToDelete);
|
||||
updateMortalEntities(now, entitiesToDelete);
|
||||
|
||||
if (entitiesToDelete.size() > 0) {
|
||||
deleteEntities(entitiesToDelete);
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void EntityTree::updateChangedEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
|
||||
foreach (EntityItem* thisEntity, _changedEntities) {
|
||||
// check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (thisEntity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID();
|
||||
entitiesToDelete << thisEntity->getEntityItemID();
|
||||
clearEntityState(thisEntity);
|
||||
} else {
|
||||
updateEntityState(thisEntity);
|
||||
}
|
||||
thisEntity->clearUpdateFlags();
|
||||
}
|
||||
_changedEntities.clear();
|
||||
}
|
||||
|
||||
void EntityTree::updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
|
||||
PerformanceTimer perfTimer("updateMovingEntities");
|
||||
if (_movingEntities.size() > 0) {
|
||||
MovingEntitiesOperator moveOperator(this);
|
||||
{
|
||||
PerformanceTimer perfTimer("_movingEntities");
|
||||
|
||||
QList<EntityItem*>::iterator item_itr = _movingEntities.begin();
|
||||
while (item_itr != _movingEntities.end()) {
|
||||
EntityItem* thisEntity = *item_itr;
|
||||
|
||||
// always check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (thisEntity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID();
|
||||
entitiesToDelete << thisEntity->getEntityItemID();
|
||||
// remove thisEntity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
thisEntity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
AACube oldCube = thisEntity->getMaximumAACube();
|
||||
thisEntity->update(now);
|
||||
AACube newCube = thisEntity->getMaximumAACube();
|
||||
|
||||
// check to see if this movement has sent the entity outside of the domain.
|
||||
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f);
|
||||
if (!domainBounds.touches(newCube)) {
|
||||
qDebug() << "Entity " << thisEntity->getEntityItemID() << " moved out of domain bounds.";
|
||||
entitiesToDelete << thisEntity->getEntityItemID();
|
||||
// remove thisEntity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
thisEntity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube);
|
||||
EntityItem::SimulationState newState = thisEntity->computeSimulationState();
|
||||
if (newState != EntityItem::Moving) {
|
||||
if (newState == EntityItem::Mortal) {
|
||||
_mortalEntities.push_back(thisEntity);
|
||||
}
|
||||
// remove thisEntity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
thisEntity->setSimulationState(newState);
|
||||
} else {
|
||||
++item_itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_simulation) {
|
||||
lockForWrite();
|
||||
QSet<EntityItem*> entitiesToDelete;
|
||||
_simulation->update(entitiesToDelete);
|
||||
if (entitiesToDelete.size() > 0) {
|
||||
// translate into list of ID's
|
||||
QSet<EntityItemID> idsToDelete;
|
||||
foreach (EntityItem* entity, entitiesToDelete) {
|
||||
idsToDelete.insert(entity->getEntityItemID());
|
||||
}
|
||||
deleteEntities(idsToDelete);
|
||||
}
|
||||
if (moveOperator.hasMovingEntities()) {
|
||||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||
recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::updateMortalEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete) {
|
||||
QList<EntityItem*>::iterator item_itr = _mortalEntities.begin();
|
||||
while (item_itr != _mortalEntities.end()) {
|
||||
EntityItem* thisEntity = *item_itr;
|
||||
thisEntity->update(now);
|
||||
// always check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (thisEntity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID();
|
||||
entitiesToDelete << thisEntity->getEntityItemID();
|
||||
// remove thisEntity from the list
|
||||
item_itr = _mortalEntities.erase(item_itr);
|
||||
thisEntity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
// check to see if this entity is no longer moving
|
||||
EntityItem::SimulationState newState = thisEntity->computeSimulationState();
|
||||
if (newState != EntityItem::Mortal) {
|
||||
if (newState == EntityItem::Moving) {
|
||||
_movingEntities.push_back(thisEntity);
|
||||
}
|
||||
// remove thisEntity from the list
|
||||
item_itr = _mortalEntities.erase(item_itr);
|
||||
thisEntity->setSimulationState(newState);
|
||||
} else {
|
||||
++item_itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) {
|
||||
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
|
||||
bool hasSomethingNewer = false;
|
||||
|
@ -966,19 +798,16 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
|
|||
|
||||
EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityItemID) /*const*/ {
|
||||
// TODO: do we need to make this thread safe? Or is it acceptable as is
|
||||
if (_entityToElementMap.contains(entityItemID)) {
|
||||
return _entityToElementMap.value(entityItemID);
|
||||
} else if (entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN){
|
||||
EntityTreeElement* element = _entityToElementMap.value(entityItemID);
|
||||
if (!element && entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN){
|
||||
// check the creator token version too...
|
||||
EntityItemID creatorTokenOnly;
|
||||
creatorTokenOnly.id = UNKNOWN_ENTITY_ID;
|
||||
creatorTokenOnly.creatorTokenID = entityItemID.creatorTokenID;
|
||||
creatorTokenOnly.isKnownID = false;
|
||||
if (_entityToElementMap.contains(creatorTokenOnly)) {
|
||||
return _entityToElementMap.value(creatorTokenOnly);
|
||||
}
|
||||
element = _entityToElementMap.value(creatorTokenOnly);
|
||||
}
|
||||
return NULL;
|
||||
return element;
|
||||
}
|
||||
|
||||
// TODO: do we need to make this thread safe? Or is it acceptable as is
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
|
||||
class Model;
|
||||
class EntitySimulation;
|
||||
|
||||
class NewlyCreatedEntityHook {
|
||||
public:
|
||||
|
@ -78,13 +79,13 @@ public:
|
|||
|
||||
// The newer API...
|
||||
EntityItem* getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
void addEntityItem(EntityItem* entityItem);
|
||||
void postAddEntity(EntityItem* entityItem);
|
||||
|
||||
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
void deleteEntity(const EntityItemID& entityID);
|
||||
void deleteEntities(QSet<EntityItemID> entityIDs);
|
||||
void removeEntityFromSimulationLists(const EntityItemID& entityID);
|
||||
void removeEntityFromSimulation(EntityItem* entity);
|
||||
|
||||
const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius);
|
||||
EntityItem* findEntityByID(const QUuid& id);
|
||||
|
@ -137,18 +138,14 @@ public:
|
|||
|
||||
void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z);
|
||||
|
||||
void updateEntityState(EntityItem* entity);
|
||||
void clearEntityState(EntityItem* entity);
|
||||
|
||||
void entityChanged(EntityItem* entity);
|
||||
|
||||
void trackDeletedEntity(const EntityItemID& entityID);
|
||||
void trackDeletedEntity(EntityItem* entity);
|
||||
|
||||
void emitAddingEntity(const EntityItemID& entityItemID);
|
||||
void emitEntityScriptChanging(const EntityItemID& entityItemID);
|
||||
|
||||
QList<EntityItem*>& getMovingEntities() { return _movingEntities; }
|
||||
|
||||
void setSimulation(EntitySimulation* simulation);
|
||||
|
||||
signals:
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
|
@ -157,10 +154,6 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
void updateChangedEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
|
||||
void updateMovingEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
|
||||
void updateMortalEntities(quint64 now, QSet<EntityItemID>& entitiesToDelete);
|
||||
|
||||
static bool findNearPointOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInSphereOperation(OctreeElement* element, void* extraData);
|
||||
static bool findInCubeOperation(OctreeElement* element, void* extraData);
|
||||
|
@ -176,11 +169,7 @@ private:
|
|||
EntityItemFBXService* _fbxService;
|
||||
|
||||
QHash<EntityItemID, EntityTreeElement*> _entityToElementMap;
|
||||
|
||||
QList<EntityItem*> _movingEntities; // entities that need to be updated
|
||||
QList<EntityItem*> _mortalEntities; // entities that need to be checked for expiry
|
||||
|
||||
QSet<EntityItem*> _changedEntities; // entities that have changed in the last frame
|
||||
EntitySimulation* _simulation;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -768,8 +768,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
addEntityItem(entityItem); // add this new entity to this elements entities
|
||||
entityItemID = entityItem->getEntityItemID();
|
||||
_myTree->setContainingElement(entityItemID, this);
|
||||
_myTree->updateEntityState(entityItem);
|
||||
_myTree->emitAddingEntity(entityItemID); // we just added an entity
|
||||
_myTree->postAddEntity(entityItem);
|
||||
}
|
||||
}
|
||||
// Move the buffer forward to read more entities
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
//
|
||||
|
||||
#include "EntityTreeHeadlessViewer.h"
|
||||
#include "SimpleEntitySimulation.h"
|
||||
|
||||
EntityTreeHeadlessViewer::EntityTreeHeadlessViewer() :
|
||||
OctreeHeadlessViewer() {
|
||||
EntityTreeHeadlessViewer::EntityTreeHeadlessViewer()
|
||||
: OctreeHeadlessViewer(), _simulation(NULL) {
|
||||
}
|
||||
|
||||
EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() {
|
||||
|
@ -20,9 +21,15 @@ EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() {
|
|||
|
||||
void EntityTreeHeadlessViewer::init() {
|
||||
OctreeHeadlessViewer::init();
|
||||
if (!_simulation) {
|
||||
SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation();
|
||||
EntityTree* entityTree = static_cast<EntityTree*>(_tree);
|
||||
simpleSimulation->setEntityTree(entityTree);
|
||||
entityTree->setSimulation(simpleSimulation);
|
||||
_simulation = simpleSimulation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EntityTreeHeadlessViewer::update() {
|
||||
if (_tree) {
|
||||
EntityTree* tree = static_cast<EntityTree*>(_tree);
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include "EntityTree.h"
|
||||
|
||||
class EntitySimulation;
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeHeadlessViewer : public OctreeHeadlessViewer {
|
||||
Q_OBJECT
|
||||
|
@ -28,7 +30,6 @@ public:
|
|||
EntityTreeHeadlessViewer();
|
||||
virtual ~EntityTreeHeadlessViewer();
|
||||
|
||||
virtual Octree* createTree() { return new EntityTree(true); }
|
||||
virtual char getMyNodeType() const { return NodeType::EntityServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeEntityQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeEntityData; }
|
||||
|
@ -40,6 +41,11 @@ public:
|
|||
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
|
||||
|
||||
virtual void init();
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree() { return new EntityTree(true); }
|
||||
|
||||
EntitySimulation* _simulation;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTreeHeadlessViewer_h
|
||||
|
|
|
@ -374,17 +374,9 @@ bool ModelEntityItem::isAnimatingSomething() const {
|
|||
}
|
||||
|
||||
EntityItem::SimulationState ModelEntityItem::computeSimulationState() const {
|
||||
EntityItem::SimulationState baseClassState = EntityItem::computeSimulationState();
|
||||
|
||||
// if the base class is static, then consider our animation state, and upgrade to changing if
|
||||
// we are animating. If the base class has a higher simulation state than static, then
|
||||
// use the base class state.
|
||||
if (baseClassState == EntityItem::Static) {
|
||||
if (isAnimatingSomething()) {
|
||||
return EntityItem::Moving;
|
||||
}
|
||||
}
|
||||
return baseClassState;
|
||||
// if we're animating then we need to have update() periodically called on this entity
|
||||
// which means we need to categorized as Moving
|
||||
return isAnimatingSomething() ? EntityItem::Moving : EntityItem::computeSimulationState();
|
||||
}
|
||||
|
||||
void ModelEntityItem::update(const quint64& updateTime) {
|
||||
|
|
224
libraries/entities/src/SimpleEntitySimulation.cpp
Normal file
224
libraries/entities/src/SimpleEntitySimulation.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
//
|
||||
// SimpleEntitySimulation.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.11.24
|
||||
// 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 <AACube.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "MovingEntitiesOperator.h"
|
||||
#include "SimpleEntitySimulation.h"
|
||||
|
||||
void SimpleEntitySimulation::update(QSet<EntityItem*>& entitiesToDelete) {
|
||||
quint64 now = usecTimestampNow();
|
||||
updateChangedEntities(now, entitiesToDelete);
|
||||
updateMovingEntities(now, entitiesToDelete);
|
||||
updateMortalEntities(now, entitiesToDelete);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::addEntity(EntityItem* entity) {
|
||||
assert(entity && entity->getSimulationState() == EntityItem::Static);
|
||||
EntityItem::SimulationState state = entity->computeSimulationState();
|
||||
switch(state) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.push_back(entity);
|
||||
entity->setSimulationState(state);
|
||||
break;
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.push_back(entity);
|
||||
entity->setSimulationState(state);
|
||||
break;
|
||||
case EntityItem::Static:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::removeEntity(EntityItem* entity) {
|
||||
assert(entity);
|
||||
// make sure to remove it from any of our simulation lists
|
||||
EntityItem::SimulationState state = entity->getSimulationState();
|
||||
switch (state) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(entity);
|
||||
break;
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
_changedEntities.remove(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::entityChanged(EntityItem* entity) {
|
||||
assert(entity);
|
||||
// we batch all changes and deal with them in updateChangedEntities()
|
||||
_changedEntities.insert(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::clearEntities() {
|
||||
foreach (EntityItem* entity, _changedEntities) {
|
||||
entity->clearUpdateFlags();
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
}
|
||||
_changedEntities.clear();
|
||||
_movingEntities.clear();
|
||||
_mortalEntities.clear();
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateChangedEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete) {
|
||||
foreach (EntityItem* entity, _changedEntities) {
|
||||
// check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (entity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID();
|
||||
entitiesToDelete.insert(entity);
|
||||
clearEntityState(entity);
|
||||
} else {
|
||||
updateEntityState(entity);
|
||||
}
|
||||
entity->clearUpdateFlags();
|
||||
}
|
||||
_changedEntities.clear();
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateMovingEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete) {
|
||||
if (_entityTree && _movingEntities.size() > 0) {
|
||||
PerformanceTimer perfTimer("_movingEntities");
|
||||
MovingEntitiesOperator moveOperator(_entityTree);
|
||||
QList<EntityItem*>::iterator item_itr = _movingEntities.begin();
|
||||
while (item_itr != _movingEntities.end()) {
|
||||
EntityItem* entity = *item_itr;
|
||||
|
||||
// always check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (entity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID();
|
||||
entitiesToDelete.insert(entity);
|
||||
// remove entity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
AACube oldCube = entity->getMaximumAACube();
|
||||
entity->update(now);
|
||||
AACube newCube = entity->getMaximumAACube();
|
||||
|
||||
// check to see if this movement has sent the entity outside of the domain.
|
||||
AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f);
|
||||
if (!domainBounds.touches(newCube)) {
|
||||
qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
|
||||
entitiesToDelete.insert(entity);
|
||||
// remove entity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
moveOperator.addEntityToMoveList(entity, oldCube, newCube);
|
||||
EntityItem::SimulationState newState = entity->computeSimulationState();
|
||||
if (newState != EntityItem::Moving) {
|
||||
if (newState == EntityItem::Mortal) {
|
||||
_mortalEntities.push_back(entity);
|
||||
}
|
||||
// remove entity from the list
|
||||
item_itr = _movingEntities.erase(item_itr);
|
||||
entity->setSimulationState(newState);
|
||||
} else {
|
||||
++item_itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (moveOperator.hasMovingEntities()) {
|
||||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||
_entityTree->recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateMortalEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete) {
|
||||
QList<EntityItem*>::iterator item_itr = _mortalEntities.begin();
|
||||
while (item_itr != _mortalEntities.end()) {
|
||||
EntityItem* entity = *item_itr;
|
||||
// always check to see if the lifetime has expired, for immortal entities this is always false
|
||||
if (entity->lifetimeHasExpired()) {
|
||||
qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID();
|
||||
entitiesToDelete.insert(entity);
|
||||
// remove entity from the list
|
||||
item_itr = _mortalEntities.erase(item_itr);
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
} else {
|
||||
// check to see if this entity is no longer moving
|
||||
EntityItem::SimulationState newState = entity->computeSimulationState();
|
||||
if (newState != EntityItem::Mortal) {
|
||||
if (newState == EntityItem::Moving) {
|
||||
entity->update(now);
|
||||
_movingEntities.push_back(entity);
|
||||
}
|
||||
// remove entity from the list
|
||||
item_itr = _mortalEntities.erase(item_itr);
|
||||
entity->setSimulationState(newState);
|
||||
} else {
|
||||
++item_itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateEntityState(EntityItem* entity) {
|
||||
EntityItem::SimulationState oldState = entity->getSimulationState();
|
||||
EntityItem::SimulationState newState = entity->computeSimulationState();
|
||||
if (newState != oldState) {
|
||||
switch (oldState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (newState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.push_back(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.push_back(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
entity->setSimulationState(newState);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::clearEntityState(EntityItem* entity) {
|
||||
EntityItem::SimulationState oldState = entity->getSimulationState();
|
||||
switch (oldState) {
|
||||
case EntityItem::Moving:
|
||||
_movingEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
case EntityItem::Mortal:
|
||||
_mortalEntities.removeAll(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
entity->setSimulationState(EntityItem::Static);
|
||||
}
|
||||
|
||||
|
47
libraries/entities/src/SimpleEntitySimulation.h
Normal file
47
libraries/entities/src/SimpleEntitySimulation.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// SimpleEntitySimulation.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.11.24
|
||||
// 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_SimpleEntitySimulation_h
|
||||
#define hifi_SimpleEntitySimulation_h
|
||||
|
||||
#include "EntitySimulation.h"
|
||||
|
||||
/// provides simple velocity + gravity extrapolation of EntityItem's
|
||||
|
||||
class SimpleEntitySimulation : public EntitySimulation {
|
||||
public:
|
||||
SimpleEntitySimulation() : EntitySimulation() { }
|
||||
virtual ~SimpleEntitySimulation() { setEntityTree(NULL); }
|
||||
|
||||
virtual void update(QSet<EntityItem*>& entitiesToDelete);
|
||||
|
||||
virtual void addEntity(EntityItem* entity);
|
||||
virtual void removeEntity(EntityItem* entity);
|
||||
virtual void entityChanged(EntityItem* entity);
|
||||
|
||||
virtual void clearEntities();
|
||||
|
||||
protected:
|
||||
void updateEntityState(EntityItem* entity);
|
||||
void clearEntityState(EntityItem* entity);
|
||||
|
||||
QList<EntityItem*>& getMovingEntities() { return _movingEntities; }
|
||||
|
||||
void updateChangedEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
|
||||
void updateMovingEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
|
||||
void updateMortalEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
|
||||
|
||||
QSet<EntityItem*> _changedEntities; // entities that have changed in the last frame
|
||||
QList<EntityItem*> _movingEntities; // entities that need to be updated
|
||||
QList<EntityItem*> _mortalEntities; // non-moving entities that need to be checked for expiry
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
|
@ -1194,7 +1194,7 @@ int matchTextureUVSetToAttributeChannel(const std::string& texUVSetName, const Q
|
|||
}
|
||||
}
|
||||
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, bool loadLightmaps) {
|
||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, bool loadLightmaps, float lightmapLevel) {
|
||||
QHash<QString, ExtractedMesh> meshes;
|
||||
QHash<QString, QString> modelIDsToNames;
|
||||
QHash<QString, int> meshIDsToMeshIndices;
|
||||
|
@ -1954,6 +1954,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
|
|||
|
||||
FBXTexture emissiveTexture;
|
||||
glm::vec2 emissiveParams(0.f, 1.f);
|
||||
emissiveParams.y = lightmapLevel;
|
||||
QString emissiveTextureID = emissiveTextures.value(childID);
|
||||
QString ambientTextureID = ambientTextures.value(childID);
|
||||
if (!emissiveTextureID.isNull() || !ambientTextureID.isNull()) {
|
||||
|
@ -2372,10 +2373,10 @@ QByteArray writeMapping(const QVariantHash& mapping) {
|
|||
return buffer.data();
|
||||
}
|
||||
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps) {
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps, float lightmapLevel) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
return extractFBXGeometry(parseFBX(&buffer), mapping, loadLightmaps);
|
||||
return extractFBXGeometry(parseFBX(&buffer), mapping, loadLightmaps, lightmapLevel);
|
||||
}
|
||||
|
||||
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||
|
|
|
@ -253,7 +253,7 @@ QByteArray writeMapping(const QVariantHash& mapping);
|
|||
|
||||
/// Reads FBX geometry from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps = true);
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
||||
|
||||
/// Reads SVO geometry from the supplied model data.
|
||||
FBXGeometry readSVO(const QByteArray& model);
|
||||
|
|
|
@ -86,6 +86,7 @@ bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const {
|
|||
void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) {
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
qDebug() << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString();
|
||||
emit lookupFailed();
|
||||
}
|
||||
|
||||
foreach(const QHostAddress& address, hostInfo.addresses()) {
|
||||
|
@ -94,6 +95,7 @@ void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) {
|
|||
_address = address;
|
||||
qDebug() << "QHostInfo lookup result for"
|
||||
<< hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString();
|
||||
emit lookupCompleted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ public:
|
|||
friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr);
|
||||
private slots:
|
||||
void handleLookupResult(const QHostInfo& hostInfo);
|
||||
signals:
|
||||
void lookupCompleted();
|
||||
void lookupFailed();
|
||||
private:
|
||||
QHostAddress _address;
|
||||
quint16 _port;
|
||||
|
|
|
@ -105,6 +105,7 @@ public:
|
|||
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
|
||||
|
||||
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
||||
|
||||
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||
void processKillNode(const QByteArray& datagram);
|
||||
|
|
|
@ -109,11 +109,11 @@ public:
|
|||
/// For loading resources, returns the number of bytes received.
|
||||
qint64 getBytesReceived() const { return _bytesReceived; }
|
||||
|
||||
/// For loading resources, returns the number of total bytes (or zero if unknown).
|
||||
/// For loading resources, returns the number of total bytes (<= zero if unknown).
|
||||
qint64 getBytesTotal() const { return _bytesTotal; }
|
||||
|
||||
/// For loading resources, returns the load progress.
|
||||
float getProgress() const { return (_bytesTotal == 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
|
||||
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
|
||||
|
||||
/// Refreshes the resource.
|
||||
void refresh();
|
||||
|
|
|
@ -35,7 +35,6 @@ public:
|
|||
OctreeRenderer();
|
||||
virtual ~OctreeRenderer();
|
||||
|
||||
virtual Octree* createTree() = 0;
|
||||
virtual char getMyNodeType() const = 0;
|
||||
virtual PacketType getMyQueryMessageType() const = 0;
|
||||
virtual PacketType getExpectedPacketType() const = 0;
|
||||
|
@ -81,6 +80,8 @@ public:
|
|||
int getOpaqueMeshPartsRendered() const { return _opaqueMeshPartsRendered; }
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree() = 0;
|
||||
|
||||
Octree* _tree;
|
||||
bool _managedTree;
|
||||
ViewFrustum* _viewFrustum;
|
||||
|
|
|
@ -100,71 +100,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
|||
{
|
||||
}
|
||||
|
||||
ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
_scriptContents(),
|
||||
_isFinished(false),
|
||||
_isRunning(false),
|
||||
_isInitialized(false),
|
||||
_isAvatar(false),
|
||||
_avatarIdentityTimer(NULL),
|
||||
_avatarBillboardTimer(NULL),
|
||||
_timerFunctionMap(),
|
||||
_isListeningToAudioStream(false),
|
||||
_avatarSound(NULL),
|
||||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_scriptName(),
|
||||
_fileNameString(),
|
||||
_quatLibrary(),
|
||||
_vec3Library(),
|
||||
_uuidLibrary(),
|
||||
_animationCache(this),
|
||||
_isUserLoaded(false),
|
||||
_arrayBufferClass(new ArrayBufferClass(this))
|
||||
{
|
||||
QString scriptURLString = scriptURL.toString();
|
||||
_fileNameString = scriptURLString;
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
url = QUrl::fromLocalFile(scriptURLString);
|
||||
}
|
||||
|
||||
// ok, let's see if it's valid... and if so, load it
|
||||
if (url.isValid()) {
|
||||
if (url.scheme() == "file") {
|
||||
QString fileName = url.toLocalFile();
|
||||
QFile scriptFile(fileName);
|
||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qDebug() << "Loading file:" << fileName;
|
||||
QTextStream in(&scriptFile);
|
||||
_scriptContents = in.readAll();
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << fileName;
|
||||
emit errorMessage("ERROR Loading file:" + fileName);
|
||||
}
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
|
||||
qDebug() << "Downloading script at" << url;
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
_scriptContents = reply->readAll();
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << url.toString();
|
||||
emit errorMessage("ERROR Loading file:" + url.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
||||
_isAvatar = isAvatar;
|
||||
|
||||
|
@ -217,6 +152,55 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents, const QStrin
|
|||
return true;
|
||||
}
|
||||
|
||||
void ScriptEngine::loadURL(const QUrl& scriptURL) {
|
||||
if (_isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
_fileNameString = scriptURL.toString();
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
url = QUrl::fromLocalFile(_fileNameString);
|
||||
}
|
||||
|
||||
// ok, let's see if it's valid... and if so, load it
|
||||
if (url.isValid()) {
|
||||
if (url.scheme() == "file") {
|
||||
_fileNameString = url.toLocalFile();
|
||||
QFile scriptFile(_fileNameString);
|
||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qDebug() << "ScriptEngine loading file:" << _fileNameString;
|
||||
QTextStream in(&scriptFile);
|
||||
_scriptContents = in.readAll();
|
||||
emit scriptLoaded(_fileNameString);
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << _fileNameString;
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
}
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::handleScriptDownload() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
|
||||
_scriptContents = reply->readAll();
|
||||
emit scriptLoaded(_fileNameString);
|
||||
} else {
|
||||
qDebug() << "ERROR Loading file:" << reply->url().toString();
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
}
|
||||
}
|
||||
|
||||
Q_SCRIPT_DECLARE_QMETAOBJECT(LocalVoxels, QString)
|
||||
|
||||
void ScriptEngine::init() {
|
||||
|
|
|
@ -40,9 +40,6 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10
|
|||
class ScriptEngine : public QScriptEngine {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
|
||||
const QString& fileNameString = QString(""),
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
@ -94,6 +91,7 @@ public:
|
|||
bool isUserLoaded() const { return _isUserLoaded; }
|
||||
|
||||
public slots:
|
||||
void loadURL(const QUrl& scriptURL);
|
||||
void stop();
|
||||
|
||||
QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1);
|
||||
|
@ -109,6 +107,8 @@ public slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
signals:
|
||||
void scriptLoaded(const QString& scriptFilename);
|
||||
void errorLoadingScript(const QString& scriptFilename);
|
||||
void update(float deltaTime);
|
||||
void scriptEnding();
|
||||
void finished(const QString& fileNameString);
|
||||
|
@ -155,6 +155,8 @@ private:
|
|||
ArrayBufferClass* _arrayBufferClass;
|
||||
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
private slots:
|
||||
void handleScriptDownload();
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEngine_h
|
||||
|
|
|
@ -27,7 +27,6 @@ public:
|
|||
VoxelTreeHeadlessViewer();
|
||||
virtual ~VoxelTreeHeadlessViewer();
|
||||
|
||||
virtual Octree* createTree() { return new VoxelTree(true); }
|
||||
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeVoxelData; }
|
||||
|
@ -35,6 +34,9 @@ public:
|
|||
VoxelTree* getTree() { return (VoxelTree*)_tree; }
|
||||
|
||||
virtual void init();
|
||||
|
||||
protected:
|
||||
virtual Octree* createTree() { return new VoxelTree(true); }
|
||||
};
|
||||
|
||||
#endif // hifi_VoxelTreeHeadlessViewer_h
|
||||
|
|
Loading…
Reference in a new issue