resolve conflicts on merge with upstream/master

This commit is contained in:
Stephen Birarda 2015-12-29 17:54:45 -08:00
commit abea65fa84
258 changed files with 11151 additions and 3419 deletions

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12.2)
cmake_minimum_required(VERSION 3.2)
if (USE_ANDROID_TOOLCHAIN)
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/android/android.toolchain.cmake")
@ -37,11 +37,11 @@ if (WIN32)
set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ")
elseif (MSVC12)
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(WINDOW_SDK_FOLDER "x64")
set(WINDOW_SDK_FOLDER "x64")
else()
set(WINDOW_SDK_FOLDER "x86")
set(WINDOW_SDK_FOLDER "x86")
endif()
set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}")
set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}")
endif ()
message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH})
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH})
@ -67,9 +67,9 @@ if ((NOT MSVC12) AND (NOT MSVC14))
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if (COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
@ -197,6 +197,10 @@ if (WIN32)
add_paths_to_fixup_libs("${QT_DIR}/bin")
endif ()
if (NOT DEFINED SERVER_ONLY)
set(SERVER_ONLY 0)
endif()
# add subdirectories for all targets
if (NOT ANDROID)
add_subdirectory(assignment-client)
@ -205,13 +209,15 @@ if (NOT ANDROID)
set_target_properties(domain-server PROPERTIES FOLDER "Apps")
add_subdirectory(ice-server)
set_target_properties(ice-server PROPERTIES FOLDER "Apps")
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
add_subdirectory(console)
add_subdirectory(tests)
add_subdirectory(plugins)
if (NOT SERVER_ONLY)
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
add_subdirectory(tests)
add_subdirectory(plugins)
endif()
add_subdirectory(tools)
endif ()
endif()
if (ANDROID OR DESKTOP_GVR)
add_subdirectory(gvr-interface)
@ -227,34 +233,4 @@ if (HIFI_MEMORY_DEBUGGING)
endif ()
include_application_version()
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE)
message(STATUS "+++++ Package for deployment will be generated on this build +++++")
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/full-stack-deployment)
set(ICONPATH_INTERFACE "$INSTDIR/${PATH_INSTALL_DATA}/interface.ico")
set(ICONPATH_STACK_MANAGER "$INSTDIR/${PATH_INSTALL_DATA}/stack-manager.ico")
string(REPLACE "/" "\\\\" ICONPATH_INTERFACE ${ICONPATH_INTERFACE})
string(REPLACE "/" "\\\\" ICONPATH_STACK_MANAGER ${ICONPATH_STACK_MANAGER})
set(CPACK_PACKAGE_NAME "High Fidelity")
set(CPACK_PACKAGE_VENDOR "High Fidelity, Inc")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "High Fidelity Interface and Stack")
set(CPACK_PACKAGE_VERSION "${BUILD_SEQ}")
set(CPACK_PACKAGE_VERSION_MAJOR "${BUILD_SEQ}")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "High Fidelity-${BUILD_SEQ}")
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
set(CPACK_PACKAGE_EXECUTABLES
stack-manager "Stack Manager"
interface "Interface"
)
if (WIN32)
install(DIRECTORY ${CMAKE_BINARY_DIR}/full-stack-deployment/ DESTINATION "./")
endif (WIN32)
include(CPack)
endif ()
generate_installers()

View file

@ -35,6 +35,7 @@
#include "AssignmentActionFactory.h"
#include "AssignmentClient.h"
#include "AssignmentClientLogging.h"
#include "avatars/ScriptableAvatar.h"
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
@ -84,7 +85,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
// check for a wallet UUID on the command line or in the config
// this would represent where the user running AC wants funds sent to
if (!walletUUID.isNull()) {
qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
qCDebug(assigmnentclient) << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
_requestAssignment.setWalletUUID(walletUUID);
}
@ -98,13 +99,13 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
_assignmentServerSocket.setObjectName("AssigmentServer");
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
qDebug() << "Assignment server socket is" << _assignmentServerSocket;
qCDebug(assigmnentclient) << "Assignment server socket is" << _assignmentServerSocket;
// call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required
qDebug() << "Waiting for assignment -" << _requestAssignment;
qCDebug(assigmnentclient) << "Waiting for assignment -" << _requestAssignment;
if (_assignmentServerHostname != "localhost") {
qDebug () << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort();
qCDebug(assigmnentclient) << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort();
}
connect(&_requestTimer, SIGNAL(timeout()), SLOT(sendAssignmentRequest()));
@ -122,7 +123,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort);
_assignmentClientMonitorSocket.setObjectName("AssignmentClientMonitor");
qDebug() << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
qCDebug(assigmnentclient) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
// Hook up a timer to send this child's status to the Monitor once per second
setUpStatusToMonitor();
@ -133,7 +134,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
}
void AssignmentClient::stopAssignmentClient() {
qDebug() << "Forced stop of assignment-client.";
qCDebug(assigmnentclient) << "Forced stop of assignment-client.";
_requestTimer.stop();
_statsTimerACM.stop();
@ -209,14 +210,14 @@ void AssignmentClient::sendAssignmentRequest() {
quint16 localAssignmentServerPort;
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
qDebug() << "Port for local assignment server read from shared memory is"
qCDebug(assigmnentclient) << "Port for local assignment server read from shared memory is"
<< localAssignmentServerPort;
_assignmentServerSocket.setPort(localAssignmentServerPort);
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
}
} else {
qDebug() << "Failed to read local assignment server port from shared memory"
qCWarning(assigmnentclient) << "Failed to read local assignment server port from shared memory"
<< "- will send assignment request to previous assignment server socket.";
}
}
@ -226,13 +227,13 @@ void AssignmentClient::sendAssignmentRequest() {
}
void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessage> message) {
qDebug() << "Received a PacketType::CreateAssignment - attempting to unpack.";
qCDebug(assigmnentclient) << "Received a PacketType::CreateAssignment - attempting to unpack.";
// construct the deployed assignment from the packet data
_currentAssignment = AssignmentFactory::unpackAssignment(*message);
if (_currentAssignment && !_isAssigned) {
qDebug() << "Received an assignment -" << *_currentAssignment;
qDebug(assigmnentclient) << "Received an assignment -" << *_currentAssignment;
_isAssigned = true;
auto nodeList = DependencyManager::get<NodeList>();
@ -242,7 +243,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
nodeList->getDomainHandler().setSockAddr(message->getSenderSockAddr(), _assignmentServerHostname);
nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID());
qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
qCDebug(assigmnentclient) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
// start the deployed assignment
QThread* workerThread = new QThread;
@ -270,7 +271,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
// Starts an event loop, and emits workerThread->started()
workerThread->start();
} else {
qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
qCWarning(assigmnentclient) << "Received an assignment that could not be unpacked. Re-requesting.";
}
}
@ -278,12 +279,12 @@ void AssignmentClient::handleStopNodePacket(QSharedPointer<ReceivedMessage> mess
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
qDebug() << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode.";
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
qCDebug(assigmnentclient) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode.";
QCoreApplication::quit();
} else {
qDebug() << "Got a stop packet from other than localhost.";
qCWarning(assigmnentclient) << "Got a stop packet from other than localhost.";
}
}
@ -303,7 +304,7 @@ void AssignmentClient::handleAuthenticationRequest() {
// ask the account manager to log us in from the env variables
accountManager.requestAccessToken(username, password);
} else {
qDebug() << "Authentication was requested against" << qPrintable(accountManager.getAuthURL().toString())
qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager.getAuthURL().toString())
<< "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV)
<< "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate.";
@ -321,7 +322,7 @@ void AssignmentClient::assignmentCompleted() {
// reset the logging target to the the CHILD_TARGET_NAME
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
qDebug() << "Assignment finished or never started - waiting for new assignment.";
qCDebug(assigmnentclient) << "Assignment finished or never started - waiting for new assignment.";
auto nodeList = DependencyManager::get<NodeList>();

View file

@ -0,0 +1,14 @@
//
// AssignmentClientLogging.cpp
// assignment-client/src
//
// Created by Clement on 12/14/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AssignmentClientLogging.h"
Q_LOGGING_CATEGORY(assigmnentclient, "hifi.assignment-client")

View file

@ -0,0 +1,19 @@
//
// AssignmentClientLogging.h
// assignment-client/src
//
// Created by Clement on 12/14/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AssignmentClientLogging_h
#define hifi_AssignmentClientLogging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(assigmnentclient)
#endif // hifi_AssignmentClientLogging_h

View file

@ -20,80 +20,8 @@
#include "OctreeSendThread.h"
OctreeQueryNode::OctreeQueryNode() :
_viewSent(false),
_octreePacket(),
_octreePacketWaiting(false),
_lastOctreePayload(new char[udt::MAX_PACKET_SIZE]),
_lastOctreePacketLength(0),
_duplicatePacketCount(0),
_firstSuppressedPacket(usecTimestampNow()),
_maxSearchLevel(1),
_maxLevelReachedInLastSearch(1),
_lastTimeBagEmpty(0),
_viewFrustumChanging(false),
_viewFrustumJustStoppedChanging(true),
_octreeSendThread(NULL),
_lastClientBoundaryLevelAdjust(0),
_lastClientOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_lodChanged(false),
_lodInitialized(false),
_sequenceNumber(0),
_lastRootTimestamp(0),
_myPacketType(PacketType::Unknown),
_isShuttingDown(false),
_sentPacketHistory()
{
}
OctreeQueryNode::~OctreeQueryNode() {
_isShuttingDown = true;
if (_octreeSendThread) {
forceNodeShutdown();
}
delete[] _lastOctreePayload;
}
void OctreeQueryNode::nodeKilled() {
_isShuttingDown = true;
if (_octreeSendThread) {
// just tell our thread we want to shutdown, this is asynchronous, and fast, we don't need or want it to block
// while the thread actually shuts down
_octreeSendThread->setIsShuttingDown();
}
}
void OctreeQueryNode::forceNodeShutdown() {
_isShuttingDown = true;
if (_octreeSendThread) {
// we really need to force our thread to shutdown, this is synchronous, we will block while the thread actually
// shuts down because we really need it to shutdown, and it's ok if we wait for it to complete
OctreeSendThread* sendThread = _octreeSendThread;
_octreeSendThread = NULL;
sendThread->setIsShuttingDown();
sendThread->terminate();
delete sendThread;
}
}
void OctreeQueryNode::sendThreadFinished() {
// We've been notified by our thread that it is shutting down. So we can clean up our reference to it, and
// delete the actual thread object. Cleaning up our thread will correctly unroll all refereces to shared
// pointers to our node as well as the octree server assignment
if (_octreeSendThread) {
OctreeSendThread* sendThread = _octreeSendThread;
_octreeSendThread = NULL;
delete sendThread;
}
}
void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) {
_octreeSendThread = new OctreeSendThread(myServer, node);
// we want to be notified when the thread finishes
connect(_octreeSendThread, &GenericThread::finished, this, &OctreeQueryNode::sendThreadFinished);
_octreeSendThread->initialize(true);
}
bool OctreeQueryNode::packetIsDuplicate() const {
@ -105,7 +33,7 @@ bool OctreeQueryNode::packetIsDuplicate() const {
// of the entire packet, we need to compare only the packet content...
if (_lastOctreePacketLength == _octreePacket->getPayloadSize()) {
if (memcmp(_lastOctreePayload + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
if (memcmp(_lastOctreePayload.data() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
_octreePacket->getPayload() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
_octreePacket->getPayloadSize() - OCTREE_PACKET_EXTRA_HEADERS_SIZE) == 0) {
return true;
@ -173,7 +101,7 @@ void OctreeQueryNode::resetOctreePacket() {
// scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing
// packet send rate.
_lastOctreePacketLength = _octreePacket->getPayloadSize();
memcpy(_lastOctreePayload, _octreePacket->getPayload(), _lastOctreePacketLength);
memcpy(_lastOctreePayload.data(), _octreePacket->getPayload(), _lastOctreePacketLength);
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state.

View file

@ -29,8 +29,8 @@ class OctreeServer;
class OctreeQueryNode : public OctreeQuery {
Q_OBJECT
public:
OctreeQueryNode();
virtual ~OctreeQueryNode();
OctreeQueryNode() = default;
virtual ~OctreeQueryNode() = default;
void init(); // called after creation to set up some virtual items
virtual PacketType getMyPacketType() const = 0;
@ -79,9 +79,6 @@ public:
OctreeSceneStats stats;
void initializeOctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
bool isOctreeSendThreadInitalized() { return _octreeSendThread; }
void dumpOutOfView();
quint64 getLastRootTimestamp() const { return _lastRootTimestamp; }
@ -92,7 +89,6 @@ public:
void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; }
void nodeKilled();
void forceNodeShutdown();
bool isShuttingDown() const { return _isShuttingDown; }
void octreePacketSent() { packetSent(*_octreePacket); }
@ -104,49 +100,47 @@ public:
bool hasNextNackedPacket() const;
const NLPacket* getNextNackedPacket();
private slots:
void sendThreadFinished();
private:
OctreeQueryNode(const OctreeQueryNode &);
OctreeQueryNode& operator= (const OctreeQueryNode&);
bool _viewSent;
bool _viewSent { false };
std::unique_ptr<NLPacket> _octreePacket;
bool _octreePacketWaiting;
char* _lastOctreePayload = nullptr;
unsigned int _lastOctreePacketLength;
int _duplicatePacketCount;
quint64 _firstSuppressedPacket;
unsigned int _lastOctreePacketLength { 0 };
int _duplicatePacketCount { 0 };
quint64 _firstSuppressedPacket { usecTimestampNow() };
int _maxSearchLevel;
int _maxLevelReachedInLastSearch;
int _maxSearchLevel { 1 };
int _maxLevelReachedInLastSearch { 1 };
ViewFrustum _currentViewFrustum;
ViewFrustum _lastKnownViewFrustum;
quint64 _lastTimeBagEmpty;
bool _viewFrustumChanging;
bool _viewFrustumJustStoppedChanging;
quint64 _lastTimeBagEmpty { 0 };
bool _viewFrustumChanging { false };
bool _viewFrustumJustStoppedChanging { true };
OctreeSendThread* _octreeSendThread;
OctreeSendThread* _octreeSendThread { nullptr };
// watch for LOD changes
int _lastClientBoundaryLevelAdjust;
float _lastClientOctreeSizeScale;
bool _lodChanged;
bool _lodInitialized;
int _lastClientBoundaryLevelAdjust { 0 };
float _lastClientOctreeSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
bool _lodChanged { false };
bool _lodInitialized { false };
OCTREE_PACKET_SEQUENCE _sequenceNumber;
OCTREE_PACKET_SEQUENCE _sequenceNumber { 0 };
quint64 _lastRootTimestamp;
quint64 _lastRootTimestamp { 0 };
PacketType _myPacketType;
bool _isShuttingDown;
PacketType _myPacketType { PacketType::Unknown };
bool _isShuttingDown { false };
SentPacketHistory _sentPacketHistory;
QQueue<OCTREE_PACKET_SEQUENCE> _nackedSequenceNumbers;
quint64 _sceneSendStartTime = 0;
std::array<char, udt::MAX_PACKET_SIZE> _lastOctreePayload;
};
#endif // hifi_OctreeQueryNode_h

View file

@ -14,6 +14,7 @@
#include <udt/PacketHeaders.h>
#include <PerfStat.h>
#include "OctreeQueryNode.h"
#include "OctreeSendThread.h"
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
@ -25,15 +26,12 @@ quint64 endSceneSleepTime = 0;
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
_myServer(myServer),
_node(node),
_nodeUUID(node->getUUID()),
_packetData(),
_nodeMissingCount(0),
_isShuttingDown(false)
_nodeUuid(node->getUUID())
{
QString safeServerName("Octree");
// set our QThread object name so we can identify this thread while debugging
setObjectName(QString("Octree Send Thread (%1)").arg(uuidStringWithoutCurlyBraces(node->getUUID())));
setObjectName(QString("Octree Send Thread (%1)").arg(uuidStringWithoutCurlyBraces(_nodeUuid)));
if (_myServer) {
safeServerName = _myServer->getMyServerName();
@ -46,6 +44,8 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint
}
OctreeSendThread::~OctreeSendThread() {
setIsShuttingDown();
QString safeServerName("Octree");
if (_myServer) {
safeServerName = _myServer->getMyServerName();
@ -56,8 +56,6 @@ OctreeSendThread::~OctreeSendThread() {
OctreeServer::clientDisconnected();
OctreeServer::stopTrackingThread(this);
_node.clear();
}
void OctreeSendThread::setIsShuttingDown() {
@ -79,15 +77,17 @@ bool OctreeSendThread::process() {
// don't do any send processing until the initial load of the octree is complete...
if (_myServer->isInitialLoadComplete()) {
if (_node) {
if (auto node = _node.lock()) {
_nodeMissingCount = 0;
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(_node->getLinkedData());
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
// Sometimes the node data has not yet been linked, in which case we can't really do anything
if (nodeData && !nodeData->isShuttingDown()) {
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
packetDistributor(nodeData, viewFrustumChanged);
packetDistributor(node, nodeData, viewFrustumChanged);
}
} else {
return false; // exit early if we're shutting down
}
}
@ -123,7 +123,8 @@ AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent,
int& truePacketsSent) {
OctreeServer::didHandlePacketSend(this);
// if we're shutting down, then exit early
@ -183,12 +184,12 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes
// actually send it
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *_node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
packetSent = true;
} else {
// not enough room in the packet, send two packets
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *_node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
// there was nothing else to send.
@ -219,7 +220,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes
packetsSent++;
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *_node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
packetSent = true;
int packetSizeWithHeader = nodeData->getPacket().getDataSize();
@ -251,7 +252,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes
if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) {
// just send the octree packet
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *_node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
packetSent = true;
int packetSizeWithHeader = nodeData->getPacket().getDataSize();
@ -293,7 +294,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes
}
/// Version of octree element distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged) {
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
OctreeServer::didPacketDistributor(this);
@ -322,7 +323,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
// then let's just send that waiting packet.
if (nodeData->isPacketWaiting()) {
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
} else {
nodeData->resetOctreePacket();
}
@ -355,7 +356,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
//unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
//unsigned long elapsedTime = nodeData->stats.getElapsedTime();
int packetsJustSent = handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval += packetsJustSent;
// If we're starting a full scene, then definitely we want to empty the elementBag
@ -431,8 +432,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// Our trackSend() function is implemented by the server subclass, and will be called back
// during the encodeTreeBitstream() as new entities/data elements are sent
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUUID);
params.trackSend = [this, node](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, node->getUUID());
};
// TODO: should this include the lock time or not? This stat is sent down to the client,
@ -481,7 +482,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
if (writtenSize > nodeData->getAvailable()) {
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
}
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
@ -501,7 +502,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
if (sendNow) {
quint64 packetSendingStart = usecTimestampNow();
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
quint64 packetSendingEnd = usecTimestampNow();
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
@ -538,9 +539,9 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// Here's where we can/should allow the server to send other data...
// send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketsToSend(_node) && !nodeData->isShuttingDown()) {
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
int specialPacketsSent = 0;
trueBytesSent += _myServer->sendSpecialPackets(_node, nodeData, specialPacketsSent);
trueBytesSent += _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
truePacketsSent += specialPacketsSent;
packetsSentThisInterval += specialPacketsSent;
@ -556,7 +557,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) {
const NLPacket* packet = nodeData->getNextNackedPacket();
if (packet) {
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *_node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
truePacketsSent++;
packetsSentThisInterval++;

View file

@ -18,8 +18,7 @@
#include <GenericThread.h>
#include "OctreeQueryNode.h"
class OctreeQueryNode;
class OctreeServer;
using AtomicUIntStat = std::atomic<uintmax_t>;
@ -32,6 +31,9 @@ public:
virtual ~OctreeSendThread();
void setIsShuttingDown();
bool isShuttingDown() { return _isShuttingDown; }
QUuid getNodeUuid() const { return _nodeUuid; }
static AtomicUIntStat _totalBytes;
static AtomicUIntStat _totalWastedBytes;
@ -48,17 +50,18 @@ protected:
virtual bool process();
private:
OctreeServer* _myServer;
SharedNodePointer _node;
QUuid _nodeUUID;
int handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent);
int packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged);
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent);
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
OctreeServer* _myServer { nullptr };
QWeakPointer<Node> _node;
QUuid _nodeUuid;
OctreePacketData _packetData;
int _nodeMissingCount;
bool _isShuttingDown;
int _nodeMissingCount { 0 };
bool _isShuttingDown { false };
};
#endif // hifi_OctreeSendThread_h

View file

@ -25,9 +25,9 @@
#include "../AssignmentClient.h"
#include "OctreeQueryNode.h"
#include "OctreeServerConsts.h"
OctreeServer* OctreeServer::_instance = NULL;
int OctreeServer::_clientCount = 0;
const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000000;
@ -231,13 +231,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
_started(time(0)),
_startedUSecs(usecTimestampNow())
{
if (_instance) {
qDebug() << "Octree Server starting... while old instance still running _instance=["<<_instance<<"] this=[" << this << "]";
}
qDebug() << "Octree Server starting... setting _instance to=[" << this << "]";
_instance = this;
_averageLoopTime.updateAverage(0);
qDebug() << "Octree server starting... [" << this << "]";
@ -281,9 +274,6 @@ OctreeServer::~OctreeServer() {
_tree.reset();
qDebug() << qPrintable(_safeServerName) << "server DONE cleaning up octree... [" << this << "]";
if (_instance == this) {
_instance = NULL; // we are gone
}
qDebug() << qPrintable(_safeServerName) << "server DONE shutting down... [" << this << "]";
}
@ -878,16 +868,38 @@ void OctreeServer::parsePayload() {
}
}
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
auto sendThread = std::unique_ptr<OctreeSendThread>(new OctreeSendThread(this, node));
// we want to be notified when the thread finishes
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
sendThread->initialize(true);
return sendThread;
}
void OctreeServer::removeSendThread() {
// If the object has been deleted since the event was queued, sender() will return nullptr
if (auto sendThread = qobject_cast<OctreeSendThread*>(sender())) {
// This deletes the unique_ptr, so sendThread is destructed after that line
_sendThreads.erase(sendThread->getNodeUuid());
}
}
void OctreeServer::handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
if (!_isFinished) {
if (!_isFinished && !_isShuttingDown) {
// If we got a query packet, then we're talking to an agent, and we
// need to make sure we have it in our nodeList.
auto nodeList = DependencyManager::get<NodeList>();
nodeList->updateNodeWithDataFromPacket(message, senderNode);
OctreeQueryNode* nodeData = dynamic_cast<OctreeQueryNode*>(senderNode->getLinkedData());
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
nodeData->initializeOctreeSendThread(this, senderNode);
auto it = _sendThreads.find(senderNode->getUUID());
if (it == _sendThreads.end()) {
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
} else if (it->second->isShuttingDown()) {
_sendThreads.erase(it); // Remove right away and wait on thread to be
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
}
}
}
@ -1117,8 +1129,8 @@ void OctreeServer::domainSettingsRequestComplete() {
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
nodeList->linkedDataCreateCallback = [] (Node* node) {
auto queryNodeData = _instance->createOctreeQueryNode();
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
@ -1167,6 +1179,13 @@ void OctreeServer::nodeAdded(SharedNodePointer node) {
void OctreeServer::nodeKilled(SharedNodePointer node) {
quint64 start = usecTimestampNow();
// Shutdown send thread
auto it = _sendThreads.find(node->getUUID());
if (it != _sendThreads.end()) {
auto& sendThread = *it->second;
sendThread.setIsShuttingDown();
}
// calling this here since nodeKilled slot in ReceivedPacketProcessor can't be triggered by signals yet!!
_octreeInboundPacketProcessor->nodeKilled(node);
@ -1188,24 +1207,6 @@ void OctreeServer::nodeKilled(SharedNodePointer node) {
trackViewerGone(node->getUUID());
}
void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
quint64 start = usecTimestampNow();
qDebug() << qPrintable(_safeServerName) << "server killed node:" << *node;
OctreeQueryNode* nodeData = dynamic_cast<OctreeQueryNode*>(node->getLinkedData());
if (nodeData) {
nodeData->forceNodeShutdown(); // tell our node data and sending threads that we'd like to shut down
} else {
qDebug() << qPrintable(_safeServerName) << "server node missing linked data node:" << *node;
}
quint64 end = usecTimestampNow();
quint64 usecsElapsed = (end - start);
qDebug() << qPrintable(_safeServerName) << "server forceNodeShutdown() took: "
<< usecsElapsed << " usecs for node:" << *node;
}
void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
@ -1214,9 +1215,8 @@ void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
// we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects.
// This ensures that when we forceNodeShutdown below for each node we don't get any more newly connecting nodes
auto nodeList = DependencyManager::get<NodeList>();
nodeList->linkedDataCreateCallback = NULL;
// This ensures that we don't get any more newly connecting nodes
DependencyManager::get<NodeList>()->linkedDataCreateCallback = nullptr;
if (_octreeInboundPacketProcessor) {
_octreeInboundPacketProcessor->terminating();
@ -1226,21 +1226,15 @@ void OctreeServer::aboutToFinish() {
_jurisdictionSender->terminating();
}
QSet<SharedNodePointer> nodesToShutdown;
// Force a shutdown of all of our OctreeSendThreads.
// At this point it has to be impossible for a linkedDataCreateCallback to be called for a new node
nodeList->eachNode([&nodesToShutdown](const SharedNodePointer& node) {
nodesToShutdown << node;
});
// What follows is a hack to force OctreeSendThreads to cleanup before the OctreeServer is gone.
// I would prefer to allow the SharedNodePointer ref count drop to zero to do this automatically
// but that isn't possible as long as the OctreeSendThread has an OctreeServer* that it uses.
for (auto& node : nodesToShutdown) {
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
forceNodeShutdown(node);
// Shut down all the send threads
for (auto& it : _sendThreads) {
auto& sendThread = *it.second;
sendThread.setIsShuttingDown();
}
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
// which waits on the thread to be done before returning
_sendThreads.clear(); // Cleans up all the send threads.
if (_persistThread) {
_persistThread->aboutToFinish();
@ -1459,15 +1453,22 @@ void OctreeServer::didCallWriteDatagram(OctreeSendThread* thread) {
void OctreeServer::stopTrackingThread(OctreeSendThread* thread) {
QMutexLocker lockerA(&_threadsDidProcessMutex);
QMutexLocker lockerB(&_threadsDidPacketDistributorMutex);
QMutexLocker lockerC(&_threadsDidHandlePacketSendMutex);
QMutexLocker lockerD(&_threadsDidCallWriteDatagramMutex);
_threadsDidProcess.remove(thread);
_threadsDidPacketDistributor.remove(thread);
_threadsDidHandlePacketSend.remove(thread);
_threadsDidCallWriteDatagram.remove(thread);
{
QMutexLocker locker(&_threadsDidProcessMutex);
_threadsDidProcess.remove(thread);
}
{
QMutexLocker locker(&_threadsDidPacketDistributorMutex);
_threadsDidPacketDistributor.remove(thread);
}
{
QMutexLocker locker(&_threadsDidHandlePacketSendMutex);
_threadsDidHandlePacketSend.remove(thread);
}
{
QMutexLocker locker(&_threadsDidCallWriteDatagramMutex);
_threadsDidCallWriteDatagram.remove(thread);
}
}
int howManyThreadsDidSomething(QMutex& mutex, QMap<OctreeSendThread*, quint64>& something, quint64 since) {

View file

@ -124,7 +124,6 @@ public:
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler);
virtual void aboutToFinish();
void forceNodeShutdown(SharedNodePointer node);
public slots:
/// runs the octree server assignment
@ -138,8 +137,12 @@ private slots:
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void removeSendThread();
protected:
using UniqueSendThread = std::unique_ptr<OctreeSendThread>;
using SendThreads = std::unordered_map<QUuid, UniqueSendThread>;
virtual OctreePointer createTree() = 0;
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
@ -153,6 +156,8 @@ protected:
QString getFileLoadTime();
QString getConfiguration();
QString getStatusLink();
UniqueSendThread createSendThread(const SharedNodePointer& node);
int _argc;
const char** _argv;
@ -187,11 +192,11 @@ protected:
int _backupInterval;
int _maxBackupVersions;
static OctreeServer* _instance;
time_t _started;
quint64 _startedUSecs;
QString _safeServerName;
SendThreads _sendThreads;
static int _clientCount;
static SimpleMovingAverage _averageLoopTime;

View file

@ -1,77 +0,0 @@
set(EXTERNAL_NAME qxmpp)
# we need to find qmake inside QT_DIR
find_program(QMAKE_COMMAND NAME qmake PATHS ${QT_DIR}/bin $ENV{QTTOOLDIR} NO_DEFAULT_PATH)
if (NOT QMAKE_COMMAND)
message(FATAL_ERROR "Could not find qmake. Qxmpp cannot be compiled without qmake.")
endif ()
if (ANDROID)
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
endif ()
if (WIN32)
find_program(PLATFORM_BUILD_COMMAND nmake PATHS "C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/bin")
if (NOT PLATFORM_BUILD_COMMAND)
message(FATAL_ERROR "You asked CMake to grap QXmpp and build it, but nmake was not found. Please make sure the folder containing nmake.exe is in your PATH.")
endif ()
else ()
find_program(PLATFORM_BUILD_COMMAND make)
endif ()
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://qxmpp.googlecode.com/files/qxmpp-0.7.6.tar.gz
URL_MD5 ee45a97313306ded2ff0f6618a3ed1e1
BUILD_IN_SOURCE 1
PATCH_COMMAND patch -p2 -t -N --verbose < ${CMAKE_CURRENT_SOURCE_DIR}/qxmpp.patch
CONFIGURE_COMMAND ${QMAKE_COMMAND} PREFIX=<INSTALL_DIR>
BUILD_COMMAND ${PLATFORM_BUILD_COMMAND}
INSTALL_COMMAND ${PLATFORM_BUILD_COMMAND} install
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
if (CMAKE_GENERATOR STREQUAL Xcode)
find_program(DITTO_COMMAND ditto)
ExternalProject_Add_Step(
${EXTERNAL_NAME}
copy-from-xcode-install
COMMENT "Copying from /tmp/hifi.dst${INSTALL_DIR} to move install to proper location"
COMMAND ${DITTO_COMMAND} /tmp/hifi.dst${INSTALL_DIR} ${INSTALL_DIR}
DEPENDEES install
LOG 1
)
endif ()
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to Qxmpp include directory")
set(_LIB_DIR ${INSTALL_DIR}/lib)
if (WIN32)
set(_LIB_EXT "0.lib")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${_LIB_DIR} CACHE PATH "Location of QXmpp DLL")
else ()
if (APPLE)
set(_LIB_EXT ".dylib")
else ()
set(_LIB_EXT ".so")
endif ()
set(_LIB_PREFIX "lib")
endif ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_LIB_DIR}/${_LIB_PREFIX}qxmpp${_LIB_EXT} CACHE FILEPATH "Path to QXmpp release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to QXmpp debug library")

View file

@ -1,26 +1,27 @@
macro(CONSOLIDATE_STACK_COMPONENTS)
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE AND WIN32)
# Copy all the output for this target into the common deployment location
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory $<TARGET_FILE_DIR:${TARGET_NAME}> ${CMAKE_BINARY_DIR}/full-stack-deployment
)
# Copy icon files for interface and stack manager
if (TARGET_NAME STREQUAL "interface" OR TARGET_NAME STREQUAL "stack-manager")
if (TARGET_NAME STREQUAL "interface")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/interface.ico")
set (ICON_DESTINATION_NAME "interface.ico")
elseif (TARGET_NAME STREQUAL "stack-manager")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/assets/icon.ico")
set (ICON_DESTINATION_NAME "stack-manager.ico")
endif ()
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE)
if (WIN32)
# Copy all the output for this target into the common deployment location
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/full-stack-deployment/${ICON_DESTINATION_NAME}
COMMAND "${CMAKE_COMMAND}" -E copy_directory $<TARGET_FILE_DIR:${TARGET_NAME}> ${CMAKE_BINARY_DIR}/full-stack-deployment
)
# Copy icon files for interface and stack manager
if (TARGET_NAME STREQUAL "interface" OR TARGET_NAME STREQUAL "stack-manager")
if (TARGET_NAME STREQUAL "interface")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/interface.ico")
set (ICON_DESTINATION_NAME "interface.ico")
elseif (TARGET_NAME STREQUAL "stack-manager")
set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/assets/icon.ico")
set (ICON_DESTINATION_NAME "stack-manager.ico")
endif ()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/full-stack-deployment/${ICON_DESTINATION_NAME}
)
endif ()
endif ()
endif ()

View file

@ -0,0 +1,30 @@
#
# GenerateInstallers.cmake
# cmake/macros
#
# Created by Leonardo Murillo on 12/16/2015.
# Copyright 2015 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(GENERATE_INSTALLERS)
if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE AND WIN32)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/full-stack-deployment")
find_program(MAKENSIS_COMMAND makensis PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS])
if (NOT MAKENSIS_COMMAND)
message(FATAL_ERROR "The Nullsoft Scriptable Install Systems is required for generating packaged installers on Windows (http://nsis.sourceforge.net/)")
endif ()
add_custom_target(
build-package ALL
DEPENDS interface assignment-client domain-server stack-manager
COMMAND set INSTALLER_SOURCE_DIR=${CMAKE_BINARY_DIR}/full-stack-deployment
COMMAND set INSTALLER_NAME=${CMAKE_BINARY_DIR}/${INSTALLER_NAME}
COMMAND set INSTALLER_SCRIPTS_DIR=${CMAKE_SOURCE_DIR}/examples
COMMAND set INSTALLER_COMPANY=${INSTALLER_COMPANY}
COMMAND set INSTALLER_DIRECTORY=${INSTALLER_DIRECTORY}
COMMAND CMD /C "\"${MAKENSIS_COMMAND}\" ${CMAKE_SOURCE_DIR}/tools/nsis/release.nsi"
)
endif ()
endmacro()

View file

@ -14,13 +14,22 @@ macro(INCLUDE_APPLICATION_VERSION)
# We are relying on Jenkins defined environment variables to determine the origin of this build
# and will only package if this is a PR or Release build
if (DEFINED ENV{JOB_ID})
set (DEPLOY_PACKAGE 1)
set (BUILD_SEQ $ENV{JOB_ID})
set(DEPLOY_PACKAGE 1)
set(BUILD_SEQ $ENV{JOB_ID})
set(INSTALLER_COMPANY "High Fidelity")
set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}")
set(INSTALLER_NAME "interface-win64-${BUILD_SEQ}.exe")
elseif (DEFINED ENV{ghprbPullId})
set (DEPLOY_PACKAGE 1)
set (BUILD_SEQ "PR-$ENV{ghprbPullId}")
set(DEPLOY_PACKAGE 1)
set(BUILD_SEQ "PR-$ENV{ghprbPullId}")
set(INSTALLER_COMPANY "High Fidelity - PR")
set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}\\${BUILD_SEQ}")
set(INSTALLER_NAME "pr-interface-win64-${BUILD_SEQ}.exe")
else ()
set(BUILD_SEQ "dev")
set(INSTALLER_COMPANY "High Fidelity - Dev")
set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}")
set(INSTALLER_NAME "dev-interface-win64.exe")
endif ()
configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${PROJECT_BINARY_DIR}/includes/ApplicationVersion.h")
include_directories("${PROJECT_BINARY_DIR}/includes")

View file

@ -40,7 +40,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
)
elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE)
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)

View file

@ -0,0 +1,72 @@
//
// triggeredRecordingOnAC.js
// examples/acScripts
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// This is the triggered rocording script used in the winterSmashUp target practice game.
// Change the CLIP_URL to your asset,
// the RECORDING_CHANNEL and RECORDING_CHANNEL_MESSAGE are used to trigger it i.e.:
// Messages.sendMessage("PlayBackOnAssignment", "BowShootingGameWelcome");
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const CLIP_URL = "atp:3fbe82f2153c443f12f9a2b14ce2d7fa2fff81977263746d9e0885ea5aabed62.hfr";
const RECORDING_CHANNEL = 'PlayBackOnAssignment';
const RECORDING_CHANNEL_MESSAGE = 'BowShootingGameWelcome'; // For each different assignment add a different message here.
const PLAY_FROM_CURRENT_LOCATION = true;
const USE_DISPLAY_NAME = true;
const USE_ATTACHMENTS = true;
const USE_AVATAR_MODEL = true;
const AUDIO_OFFSET = 0.0;
const STARTING_TIME = 0.0;
const COOLDOWN_PERIOD = 0; // The period in ms that no animations can be played after one has been played already
var isPlaying = false;
var isPlayable = true;
var playRecording = function() {
if (!isPlayable || isPlaying) {
return;
}
Agent.isAvatar = true;
Recording.setPlayFromCurrentLocation(PLAY_FROM_CURRENT_LOCATION);
Recording.setPlayerUseDisplayName(USE_DISPLAY_NAME);
Recording.setPlayerUseAttachments(USE_ATTACHMENTS);
Recording.setPlayerUseHeadModel(false);
Recording.setPlayerUseSkeletonModel(USE_AVATAR_MODEL);
Recording.setPlayerLoop(false);
Recording.setPlayerTime(STARTING_TIME);
Recording.setPlayerAudioOffset(AUDIO_OFFSET);
Recording.loadRecording(CLIP_URL);
Recording.startPlaying();
isPlaying = true;
isPlayable = false; // Set this true again after the cooldown period
};
Script.update.connect(function(deltaTime) {
if (isPlaying && !Recording.isPlaying()) {
print('Reached the end of the recording. Resetting.');
isPlaying = false;
Agent.isAvatar = false;
if (COOLDOWN_PERIOD === 0) {
isPlayable = true;
return;
}
Script.setTimeout(function () {
isPlayable;
}, COOLDOWN_PERIOD);
}
});
Messages.subscribe(RECORDING_CHANNEL);
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel === RECORDING_CHANNEL && message === RECORDING_CHANNEL_MESSAGE) {
playRecording();
}
});

View file

@ -0,0 +1,54 @@
//
// getHUDLookAtPositionTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// This script demonstrates the testing of the HMD.getHUDLookAtPosition--() functions.
// If these functions are working correctly, we'd expect to see a 3D cube and a 2D square
// follow around the center of the HMD view.
var cubePosition = { x: 0, y: 0, z: 0 };
var cubeSize = 0.03;
var cube = Overlays.addOverlay("cube", {
position: cubePosition,
size: cubeSize,
color: { red: 255, green: 0, blue: 0},
alpha: 1,
solid: false
});
var square = Overlays.addOverlay("text", {
x: 0,
y: 0,
width: 20,
height: 20,
color: { red: 255, green: 255, blue: 0},
backgroundColor: { red: 255, green: 255, blue: 0},
alpha: 1
});
Script.update.connect(function(deltaTime) {
if (!HMD.active) {
return;
}
var lookAt3D = HMD.getHUDLookAtPosition3D();
Overlays.editOverlay(cube, { position: lookAt3D });
var lookAt2D = HMD.getHUDLookAtPosition2D();
Overlays.editOverlay(square, { x: lookAt2D.x, y: lookAt2D.y });
});
Script.scriptEnding.connect(function(){
Overlays.deleteOverlay(cube);
Overlays.deleteOverlay(square);
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,87 @@
//
// reticleTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var PITCH_DEADZONE = 1.0;
var PITCH_MAX = 20.0;
var YAW_DEADZONE = 1.0;
var YAW_MAX = 20.0;
var PITCH_SCALING = 10.0;
var YAW_SCALING = 10.0;
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticle(dY, dX) {
var globalPos = Controller.getReticlePosition();
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
print("distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
print("UNEXPECTED dX:" + dX + "----------------------------");
dX = 0;
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
print("UNEXPECTED dY:" + dY + "----------------------------");
dY = 0;
}
globalPos.x += dX;
globalPos.y += dY;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
var mapping = Controller.newMapping(MAPPING_NAME);
var lastHandPitch = 0;
var lastHandYaw = 0;
mapping.from(Controller.Standard.LeftHand).peek().to(function(pose) {
var handEulers = Quat.safeEulerAngles(pose.rotation);
//Vec3.print("handEulers:", handEulers);
var handPitch = handEulers.y;
var handYaw = handEulers.x;
var changePitch = (handPitch - lastHandPitch) * PITCH_SCALING;
var changeYaw = (handYaw - lastHandYaw) * YAW_SCALING;
if (Math.abs(changePitch) > PITCH_MAX) {
print("Pitch: " + changePitch);
changePitch = 0;
}
if (Math.abs(changeYaw) > YAW_MAX) {
print("Yaw: " + changeYaw);
changeYaw = 0;
}
changePitch = Math.abs(changePitch) < PITCH_DEADZONE ? 0 : changePitch;
changeYaw = Math.abs(changeYaw) < YAW_DEADZONE ? 0 : changeYaw;
moveReticle(changePitch, changeYaw);
lastHandPitch = handPitch;
lastHandYaw = handYaw;
});
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,76 @@
//
// proceduralHandPoseExample.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MAPPING_NAME = "com.highfidelity.examples.proceduralHandPose";
var mapping = Controller.newMapping(MAPPING_NAME);
var translation = { x: 0, y: 0.1, z: 0 };
var translationDx = 0.01;
var translationDy = 0.01;
var translationDz = -0.01;
var TRANSLATION_LIMIT = 0.5;
var pitch = 45;
var yaw = 0;
var roll = 45;
var pitchDelta = 1;
var yawDelta = -1;
var rollDelta = 1;
var ROTATION_MIN = -90;
var ROTATION_MAX = 90;
mapping.from(function() {
// adjust the hand translation in a periodic back and forth motion for each of the 3 axes
translation.x = translation.x + translationDx;
translation.y = translation.y + translationDy;
translation.z = translation.z + translationDz;
if ((translation.x > TRANSLATION_LIMIT) || (translation.x < (-1 * TRANSLATION_LIMIT))) {
translationDx = translationDx * -1;
}
if ((translation.y > TRANSLATION_LIMIT) || (translation.y < (-1 * TRANSLATION_LIMIT))) {
translationDy = translationDy * -1;
}
if ((translation.z > TRANSLATION_LIMIT) || (translation.z < (-1 * TRANSLATION_LIMIT))) {
translationDz = translationDz * -1;
}
// adjust the hand rotation in a periodic back and forth motion for each of pitch/yaw/roll
pitch = pitch + pitchDelta;
yaw = yaw + yawDelta;
roll = roll + rollDelta;
if ((pitch > ROTATION_MAX) || (pitch < ROTATION_MIN)) {
pitchDelta = pitchDelta * -1;
}
if ((yaw > ROTATION_MAX) || (yaw < ROTATION_MIN)) {
yawDelta = yawDelta * -1;
}
if ((roll > ROTATION_MAX) || (roll < ROTATION_MIN)) {
rollDelta = rollDelta * -1;
}
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll);
var pose = {
translation: translation,
rotation: rotation,
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 }
};
return pose;
}).debug(true).to(Controller.Standard.LeftHand);
Controller.enableMapping(MAPPING_NAME);
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,121 @@
//
// reticleHandAngularVelocityTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// If you set this to true, you will get the raw instantaneous angular velocity.
// note: there is a LOT of noise in the hydra rotation, you will probably be very
// frustrated with the level of jitter.
var USE_INSTANTANEOUS_ANGULAR_VELOCITY = false;
var whichHand = Controller.Standard.RightHand;
var whichTrigger = Controller.Standard.RT;
function msecTimestampNow() {
var d = new Date();
return d.getTime();
}
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticle(dX, dY) {
var globalPos = Controller.getReticlePosition();
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
print("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dX:" + dX + "----------------------------");
dX = 0;
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dY:" + dY + "----------------------------");
dY = 0;
}
globalPos.x += dX;
globalPos.y += dY;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var firstTime = true;
var lastTime = msecTimestampNow();
var previousRotation;
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from(whichTrigger).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
mapping.from(whichHand).peek().to(function(pose) {
var MSECS_PER_SECOND = 1000;
var now = msecTimestampNow();
var deltaMsecs = (now - lastTime);
var deltaTime = deltaMsecs / MSECS_PER_SECOND;
if (firstTime) {
previousRotation = pose.rotation;
lastTime = msecTimestampNow();
firstTime = false;
}
// pose.angularVelocity - is the angularVelocity in a "physics" sense, that
// means the direction of the vector is the axis of symetry of rotation
// and the scale of the vector is the speed in radians/second of rotation
// around that axis.
//
// we want to deconstruct that in the portion of the rotation on the Y axis
// and make that portion move our reticle in the horizontal/X direction
// and the portion of the rotation on the X axis and make that portion
// move our reticle in the veritcle/Y direction
var xPart = -pose.angularVelocity.y;
var yPart = -pose.angularVelocity.x;
// pose.angularVelocity is "smoothed", we can calculate our own instantaneous
// angular velocity as such:
if (USE_INSTANTANEOUS_ANGULAR_VELOCITY) {
var previousConjugate = Quat.conjugate(previousRotation);
var deltaRotation = Quat.multiply(pose.rotation, previousConjugate);
var normalizedDeltaRotation = Quat.normalize(deltaRotation);
var axis = Quat.axis(normalizedDeltaRotation);
var speed = Quat.angle(normalizedDeltaRotation) / deltaTime;
var instantaneousAngularVelocity = Vec3.multiply(speed, axis);
xPart = -instantaneousAngularVelocity.y;
yPart = -instantaneousAngularVelocity.x;
previousRotation = pose.rotation;
}
var MOVE_SCALE = 1;
lastTime = now;
var dX = (xPart * MOVE_SCALE) / deltaTime;
var dY = (yPart * MOVE_SCALE) / deltaTime;
moveReticle(dX, dY);
});
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,254 @@
//
// reticleHandRotationTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEBUGGING = false;
Math.clamp=function(a,b,c) {
return Math.max(b,Math.min(c,a));
}
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticleAbsolute(x, y) {
var globalPos = Controller.getReticlePosition();
var dX = x - globalPos.x;
var dY = y - globalPos.y;
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
debugPrint("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
debugPrint("surpressing unexpectedly large change dX:" + dX + "----------------------------");
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
debugPrint("surpressing unexpectedly large change dY:" + dY + "----------------------------");
}
globalPos.x = x;
globalPos.y = y;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from(Controller.Standard.LT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
mapping.from(Controller.Standard.RT).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
mapping.enable();
var lastRotatedLeft = Vec3.UNIT_NEG_Y;
var lastRotatedRight = Vec3.UNIT_NEG_Y;
function debugPrint(message) {
if (DEBUGGING) {
print(message);
}
}
var MAX_WAKE_UP_DISTANCE = 0.005;
var MIN_WAKE_UP_DISTANCE = 0.001;
var INITIAL_WAKE_UP_DISTANCE = MIN_WAKE_UP_DISTANCE;
var INCREMENTAL_WAKE_UP_DISTANCE = 0.001;
var MAX_SLEEP_DISTANCE = 0.0004;
var MIN_SLEEP_DISTANCE = 0.00001; //0.00002;
var INITIAL_SLEEP_DISTANCE = MIN_SLEEP_DISTANCE;
var INCREMENTAL_SLEEP_DISTANCE = 0.000002; // 0.00002;
var leftAsleep = true;
var rightAsleep = true;
var leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
var rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
var leftSleepDistance = INITIAL_SLEEP_DISTANCE;
var rightSleepDistance = INITIAL_SLEEP_DISTANCE;
Script.update.connect(function(deltaTime) {
var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);
// NOTE: hack for now
var screenSizeX = 1920;
var screenSizeY = 1080;
var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
var suppressRight = false;
var suppressLeft = false;
// What I really want to do is to slowly increase the epsilon you have to move it
// to wake up, the longer you go without moving it
var leftDistance = Vec3.distance(rotatedLeft, lastRotatedLeft);
var rightDistance = Vec3.distance(rotatedRight, lastRotatedRight);
// check to see if hand should wakeup or sleep
if (leftAsleep) {
if (leftDistance > leftWakeUpDistance) {
leftAsleep = false;
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
} else {
// grow the wake up distance to make it harder to wake up
leftWakeUpDistance = Math.min(leftWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE);
}
} else {
// we are awake, determine if we should fall asleep, if we haven't moved
// at least as much as our sleep distance then we sleep
if (leftDistance < leftSleepDistance) {
leftAsleep = true;
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
} else {
// if we moved more than the sleep amount, but we moved less than the max sleep
// amount, then increase our liklihood of sleep.
if (leftDistance < MAX_SLEEP_DISTANCE) {
print("growing sleep....");
leftSleepDistance = Math.max(leftSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE);
} else {
// otherwise reset it to initial
leftSleepDistance = INITIAL_SLEEP_DISTANCE;
}
}
}
if (leftAsleep) {
suppressLeft = true;
debugPrint("suppressing left not moving enough");
}
// check to see if hand should wakeup or sleep
if (rightAsleep) {
if (rightDistance > rightWakeUpDistance) {
rightAsleep = false;
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
} else {
// grow the wake up distance to make it harder to wake up
rightWakeUpDistance = Math.min(rightWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE);
}
} else {
// we are awake, determine if we should fall asleep, if we haven't moved
// at least as much as our sleep distance then we sleep
if (rightDistance < rightSleepDistance) {
rightAsleep = true;
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE;
} else {
// if we moved more than the sleep amount, but we moved less than the max sleep
// amount, then increase our liklihood of sleep.
if (rightDistance < MAX_SLEEP_DISTANCE) {
print("growing sleep....");
rightSleepDistance = Math.max(rightSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE);
} else {
// otherwise reset it to initial
rightSleepDistance = INITIAL_SLEEP_DISTANCE;
}
}
}
if (rightAsleep) {
suppressRight = true;
debugPrint("suppressing right not moving enough");
}
// check to see if hand is on base station
if (Vec3.equal(rotatedLeft, Vec3.UNIT_NEG_Y)) {
suppressLeft = true;
debugPrint("suppressing left on base station");
}
if (Vec3.equal(rotatedRight, Vec3.UNIT_NEG_Y)) {
suppressRight = true;
debugPrint("suppressing right on base station");
}
// Keep track of last rotations, to detect resting (but not on base station hands) in the future
lastRotatedLeft = rotatedLeft;
lastRotatedRight = rotatedRight;
if (suppressLeft && suppressRight) {
debugPrint("both hands suppressed bail out early");
return;
}
if (suppressLeft) {
debugPrint("right only");
rotatedLeft = rotatedRight;
}
if (suppressRight) {
debugPrint("left only");
rotatedRight = rotatedLeft;
}
// Average the two hand positions, if either hand is on base station, the
// other hand becomes the only used hand and the average is the hand in use
var rotated = Vec3.multiply(Vec3.sum(rotatedRight,rotatedLeft), 0.5);
if (DEBUGGING) {
Vec3.print("rotatedRight:", rotatedRight);
Vec3.print("rotatedLeft:", rotatedLeft);
Vec3.print("rotated:", rotated);
}
var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
var absoluteYaw = -rotated.x; // from -1 left to 1 right
if (DEBUGGING) {
print("absolutePitch:" + absolutePitch);
print("absoluteYaw:" + absoluteYaw);
Vec3.print("rotated:", rotated);
}
var ROTATION_BOUND = 0.6;
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
if (DEBUGGING) {
print("clampYaw:" + clampYaw);
print("clampPitch:" + clampPitch);
}
// using only from -ROTATION_BOUND to ROTATION_BOUND
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
if (DEBUGGING) {
print("xRatio:" + xRatio);
print("yRatio:" + yRatio);
}
var x = screenSizeX * xRatio;
var y = screenSizeY * yRatio;
if (DEBUGGING) {
print("position x:" + x + " y:" + y);
}
if (!(xRatio == 0.5 && yRatio == 0)) {
moveReticleAbsolute(x, y);
}
});
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -33,7 +33,6 @@ var mappingJSON = {
mapping = Controller.parseMapping(JSON.stringify(mappingJSON));
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -62,7 +62,7 @@ var directory = (function () {
function setUp() {
viewport = Controller.getViewportDimensions();
directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow.setVisible(false);
directoryButton = Overlays.addOverlay("image", {

View file

@ -140,9 +140,33 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", {
});
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false);
var marketplaceWindow = new OverlayWebWindow('Marketplace', "about:blank", 900, 700, false);
marketplaceWindow.setVisible(false);
function showMarketplace(marketplaceID) {
var url = MARKETPLACE_URL;
if (marketplaceID) {
url = url + "/items/" + marketplaceID;
}
print("setting marketplace URL to " + url);
marketplaceWindow.setURL(url);
marketplaceWindow.setVisible(true);
marketplaceWindow.raise();
}
function hideMarketplace() {
marketplaceWindow.setVisible(false);
marketplaceWindow.setURL("about:blank");
}
function toggleMarketplace() {
if (marketplaceWindow.visible) {
hideMarketplace();
} else {
showMarketplace();
}
}
var toolBar = (function() {
var that = {},
toolBar,
@ -413,12 +437,9 @@ var toolBar = (function() {
newModelButtonDown = true;
return true;
}
if (browseMarketplaceButton === toolBar.clicked(clickedOverlay)) {
if (marketplaceWindow.url != MARKETPLACE_URL) {
marketplaceWindow.setURL(MARKETPLACE_URL);
}
marketplaceWindow.setVisible(true);
marketplaceWindow.raise();
toggleMarketplace();
return true;
}
@ -1336,6 +1357,7 @@ function getPositionToCreateEntity() {
}
function importSVO(importURL) {
print("Import URL requested: " + importURL)
if (!Entities.canAdjustLocks()) {
Window.alert(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
return;
@ -1574,11 +1596,7 @@ PropertiesTool = function(opts) {
pushCommandForSelections();
selectionManager._update();
} else if (data.type == "showMarketplace") {
if (marketplaceWindow.url != data.url) {
marketplaceWindow.setURL(data.url);
}
marketplaceWindow.setVisible(true);
marketplaceWindow.raise();
showMarketplace();
} else if (data.type == "action") {
if (data.action == "moveSelectionToGrid") {
if (selectionManager.hasSelection()) {
@ -1859,12 +1877,7 @@ var propertyMenu = PopupMenu();
propertyMenu.onSelectMenuItem = function(name) {
if (propertyMenu.marketplaceID) {
var url = MARKETPLACE_URL + "/items/" + propertyMenu.marketplaceID;
if (marketplaceWindow.url != url) {
marketplaceWindow.setURL(url);
}
marketplaceWindow.setVisible(true);
marketplaceWindow.raise();
showMarketplace(propertyMenu.marketplaceID);
}
};

View file

@ -60,7 +60,7 @@
});
break;
case 3:
print("Radius spread");
print("Radius spread - temporarily not working");
Entities.editEntity(particles, {
accelerationSpread: { x: 0.0, y: 0.0, z: 0.0 },
radiusSpread: 0.035
@ -71,6 +71,7 @@
Entities.editEntity(particles, {
radiusSpread: 0.0,
radiusStart: 0.0,
particleRadius: 2 * PARTICLE_RADIUS, // Bezier interpolation used means that middle value isn't intersected
radiusFinish: 0.0
});
break;
@ -78,12 +79,13 @@
print("Alpha 0.5");
Entities.editEntity(particles, {
radiusStart: PARTICLE_RADIUS,
particleRadius: PARTICLE_RADIUS,
radiusFinish: PARTICLE_RADIUS,
alpha: 0.5
});
break;
case 6:
print("Alpha spread");
print("Alpha spread - temporarily not working");
Entities.editEntity(particles, {
alpha: 0.5,
alphaSpread: 0.5
@ -99,7 +101,7 @@
});
break;
case 8:
print("Color spread");
print("Color spread - temporarily not working");
Entities.editEntity(particles, {
alpha: 1.0,
alphaStart: 1.0,
@ -255,7 +257,6 @@
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
color: { red: 255, green: 255, blue: 255 },
lifespan: 5.0,
visible: false,
locked: false,
isEmitting: false,
lifetime: 3600 // 1 hour; just in case

View file

@ -0,0 +1,145 @@
//
// arcBall.js
// examples/arcBall
//
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This script creats a particle light ball which makes particle trails as you move it.
//
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/utils.js");
var scriptURL = Script.resolvePath("arcBallEntityScript.js?v1" + Math.random());
ArcBall = function(spawnPosition) {
var colorPalette = [{
red: 25,
green: 20,
blue: 162
}];
var containerBall = Entities.addEntity({
type: "Sphere",
name: "Arc Ball",
script: scriptURL,
position: Vec3.sum(spawnPosition, {
x: 0,
y: .7,
z: 0
}),
dimensions: {
x: .05,
y: .05,
z: .05
},
color: {
red: 100,
green: 10,
blue: 150
},
ignoreForCollisions: true,
damping: 0.8,
collisionsWillMove: true,
userData: JSON.stringify({
grabbableKey: {
spatialKey: {
// relativePosition: {
// x: 0,
// y: -0.5,
// z: 0.0
// },
},
// invertSolidWhileHeld: true
}
})
});
var light = Entities.addEntity({
type: 'Light',
name: "ballLight",
parentID: containerBall,
dimensions: {
x: 30,
y: 30,
z: 30
},
color: colorPalette[randInt(0, colorPalette.length)],
intensity: 5
});
var arcBall = Entities.addEntity({
type: "ParticleEffect",
parentID: containerBall,
isEmitting: true,
name: "Arc Ball Particle Effect",
colorStart: {
red: 200,
green: 20,
blue: 40
},
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: {
red: 25,
green: 20,
blue: 255
},
maxParticles: 100000,
lifespan: 2,
emitRate: 400,
emitSpeed: .1,
lifetime: -1,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: Math.PI,
azimuthStart: -Math.PI,
azimuthFinish: Math.PI,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: .00,
y: .00,
z: .00
},
particleRadius: 0.02,
radiusSpread: 0,
radiusStart: 0.03,
radiusFinish: 0.0003,
alpha: 0,
alphaSpread: .5,
alphaStart: 0,
alphaFinish: 0.5,
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
emitterShouldTrail: true
})
function cleanup() {
Entities.deleteEntity(arcBall);
Entities.deleteEntity(containerBall);
Entities.deleteEntity(light);
}
this.cleanup = cleanup;
}

View file

@ -0,0 +1,155 @@
// arcBallEntityScript.js
//
// Script Type: Entity
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This entity script handles the logic for the arcBall rave toy
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include("../../libraries/utils.js");
var _this;
var ArcBall = function() {
_this = this;
this.colorPalette = [{
red: 25,
green: 20,
blue: 162
}, {
red: 200,
green: 10,
blue: 10
}];
this.searchRadius = 10;
};
ArcBall.prototype = {
isGrabbed: false,
startDistantGrab: function() {
this.searchForNearbyArcBalls();
},
startNearGrab: function() {
this.searchForNearbyArcBalls();
},
searchForNearbyArcBalls: function() {
//Search for nearby balls and create an arc to it if one is found
var position = Entities.getEntityProperties(this.entityID, "position").position
var entities = Entities.findEntities(position, this.searchRadius);
entities.forEach(function(entity) {
var props = Entities.getEntityProperties(entity, ["position", "name"]);
if (props.name === "Arc Ball" && JSON.stringify(_this.entityID) !== JSON.stringify(entity)) {
_this.target = entity;
_this.createBeam(position, props.position);
}
});
},
createBeam: function(startPosition, endPosition) {
// Creates particle arc from start position to end position
var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation;
var sourceToTargetVec = Vec3.subtract(endPosition, startPosition);
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_Z, sourceToTargetVec);
emitOrientation = Quat.multiply(Quat.inverse(rotation), emitOrientation);
var color = this.colorPalette[randInt(0, this.colorPalette.length)];
var props = {
type: "ParticleEffect",
name: "Particle Arc",
parentID: this.entityID,
parentJointIndex: -1,
// position: startPosition,
isEmitting: true,
colorStart: color,
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: color,
maxParticles: 100000,
lifespan: 1,
emitRate: 1000,
emitOrientation: emitOrientation,
emitSpeed: 1,
speedSpread: 0.02,
emitDimensions: {
x: .01,
y: .01,
z: .01
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0.02,
azimuthFinish: .01,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0,
y: 0,
z: 0
},
radiusStart: 0.01,
radiusFinish: 0.005,
radiusSpread: 0.005,
alpha: 0.5,
alphaSpread: 0.1,
alphaStart: 0.5,
alphaFinish: 0.5,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
emitterShouldTrail: true
}
this.particleArc = Entities.addEntity(props);
},
updateBeam: function() {
if(!this.target) {
return;
}
var startPosition = Entities.getEntityProperties(this.entityID, "position").position;
var targetPosition = Entities.getEntityProperties(this.target, "position").position;
var rotation = Entities.getEntityProperties(this.entityID, "rotation").rotation;
var sourceToTargetVec = Vec3.subtract(targetPosition, startPosition);
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_Z, sourceToTargetVec);
Entities.editEntity(this.particleArc, {
emitOrientation: emitOrientation
});
},
continueNearGrab: function() {
this.updateBeam();
},
continueDistantGrab: function() {
this.updateBeam();
},
releaseGrab: function() {
Entities.editEntity(this.particleArc, {
isEmitting: false
});
this.target = null;
},
unload: function() {
if (this.particleArc) {
Entities.deleteEntity(this.particleArc);
}
},
preload: function(entityID) {
this.entityID = entityID;
},
};
return new ArcBall();
});

View file

@ -0,0 +1,91 @@
//
// flowArtsHutSpawner.js
// examples/flowArts
//
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This script creates a special flow arts hut with a bunch of flow art toys people can go in and play with
//
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/utils.js");
Script.include("lightBall/lightBall.js");
Script.include("raveStick/raveStick.js");
Script.include("lightSaber/lightSaber.js");
Script.include("arcBall/arcBall.js");
var basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(Camera.getOrientation())));
basePosition.y = MyAvatar.position.y + 1;
// RAVE ITEMS
// var lightBall = new LightBall(basePosition);
var arcBall = new ArcBall(basePosition);
var arcBall2 = new ArcBall(Vec3.sum(basePosition, {x: -1, y: 0, z: 0}));
var raveStick = new RaveStick(Vec3.sum(basePosition, {x: 1, y: 0.5, z: 1}));
var lightSaber = new LightSaber(Vec3.sum(basePosition, {x: 3, y: 0.5, z: 1}));
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/RaveRoom.fbx";
var roomDimensions = {x: 30.58, y: 15.29, z: 30.58};
var raveRoom = Entities.addEntity({
type: "Model",
name: "Rave Hut Room",
modelURL: modelURL,
position: basePosition,
dimensions:roomDimensions,
visible: true
});
var floor = Entities.addEntity({
type: "Box",
name: "Rave Floor",
position: Vec3.sum(basePosition, {x: 0, y: -1.2, z: 0}),
dimensions: {x: roomDimensions.x, y: 0.6, z: roomDimensions.z},
color: {red: 50, green: 10, blue: 100},
shapeType: 'box'
});
var lightZone = Entities.addEntity({
type: "Zone",
name: "Rave Hut Zone",
shapeType: 'box',
keyLightIntensity: 0.4,
keyLightColor: {
red: 50,
green: 0,
blue: 50
},
keyLightAmbientIntensity: .2,
position: MyAvatar.position,
dimensions: {
x: 100,
y: 100,
z: 100
}
});
function cleanup() {
Entities.deleteEntity(raveRoom);
Entities.deleteEntity(lightZone);
Entities.deleteEntity(floor);
// lightBall.cleanup();
arcBall.cleanup();
arcBall2.cleanup();
raveStick.cleanup();
lightSaber.cleanup();
}
Script.scriptEnding.connect(cleanup);

View file

@ -0,0 +1,145 @@
//
// LightBall.js
// examples/lightBall
//
// Created by Eric Levin on 12/17/15.
// Copyright 2014 High Fidelity, Inc.
//
// This script creats a particle light ball which makes particle trails as you move it.
//
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/utils.js");
LightBall = function(spawnPosition) {
var colorPalette = [{
red: 25,
green: 20,
blue: 162
}];
var containerBall = Entities.addEntity({
type: "Sphere",
name: "containerBall",
position: Vec3.sum(spawnPosition, {
x: 0,
y: 0.5,
z: 0
}),
dimensions: {
x: 0.1,
y: 0.1,
z: 0.1
},
color: {
red: 15,
green: 10,
blue: 150
},
collisionsWillMove: true,
// gravity: {
// x: 0,
// y: -0.5,
// z: 0
// },
userData: JSON.stringify({
grabbableKey: {
spatialKey: {
relativePosition: {
x: 0,
y: 0.1,
z: 0
}
},
invertSolidWhileHeld: true
}
})
});
var light = Entities.addEntity({
type: 'Light',
name: "ballLight",
parentID: containerBall,
dimensions: {
x: 30,
y: 30,
z: 30
},
color: colorPalette[randInt(0, colorPalette.length)],
intensity: 5
});
var lightBall = Entities.addEntity({
type: "ParticleEffect",
parentID: containerBall,
isEmitting: true,
name: "particleBall",
colorStart: {
red: 200,
green: 20,
blue: 40
},
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: {
red: 25,
green: 20,
blue: 255
},
maxParticles: 100000,
lifespan: 2,
emitRate: 10000,
emitSpeed: 0.1,
lifetime: -1,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: Math.PI,
azimuthStart: -Math.PI,
azimuthFinish: Math.PI,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0.00,
y: 0.00,
z: 0.00
},
particleRadius: 0.02,
radiusSpread: 0,
radiusStart: 0.03,
radiusFinish: 0.0003,
alpha: 0,
alphaSpread: 0.5,
alphaStart: 0,
alphaFinish: 0.5,
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
emitterShouldTrail: true
})
function cleanup() {
Entities.deleteEntity(lightBall);
Entities.deleteEntity(containerBall);
Entities.deleteEntity(light);
}
this.cleanup = cleanup;
}

View file

@ -0,0 +1,67 @@
//
// LightSaber.js
// examples
//
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This script creates a lightsaber which activates on grab
//
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/utils.js");
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/lightSaber.fbx";
var scriptURL = Script.resolvePath("lightSaberEntityScript.js");
LightSaber = function(spawnPosition) {
var saberHandle = Entities.addEntity({
type: "Model",
name: "LightSaber Handle",
modelURL: modelURL,
position: spawnPosition,
shapeType: 'box',
collisionsWillMove: true,
script: scriptURL,
dimensions: {
x: 0.06,
y: 0.06,
z: 0.31
},
userData: JSON.stringify({
grabbableKey: {
spatialKey: {
relativePosition: {
x: 0,
y: 0,
z: -0.1
},
relativeRotation: Quat.fromPitchYawRollDegrees(180, 90, 0)
},
invertSolidWhileHeld: true
}
})
});
var light = Entities.addEntity({
type: 'Light',
name: "raveLight",
parentID: saberHandle,
dimensions: {
x: 30,
y: 30,
z: 30
},
color: {red: 200, green: 10, blue: 200},
intensity: 5
});
function cleanup() {
Entities.deleteEntity(saberHandle);
}
this.cleanup = cleanup;
}

View file

@ -0,0 +1,116 @@
// lightSaberEntityScript.js
//
// Script Type: Entity
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This entity script creates the logic for displaying the lightsaber beam.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include("../../libraries/utils.js");
var _this;
// this is the "constructor" for the entity as a JS object we don't do much here
var LightSaber = function() {
_this = this;
this.colorPalette = [{
red: 0,
green: 200,
blue: 40
}, {
red: 200,
green: 10,
blue: 40
}];
};
LightSaber.prototype = {
isGrabbed: false,
startNearGrab: function() {
Entities.editEntity(this.beam, {
isEmitting: true,
visible: true
});
},
releaseGrab: function() {
Entities.editEntity(this.beam, {
visible: false,
isEmitting: false
});
},
preload: function(entityID) {
this.entityID = entityID;
this.createBeam();
},
unload: function() {
Entities.deleteEntity(this.beam);
},
createBeam: function() {
this.props = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
var forwardVec = Quat.getFront(Quat.multiply(this.props.rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec);
var position = this.props.position;
var color = this.colorPalette[randInt(0, this.colorPalette.length)];
var props = {
type: "ParticleEffect",
name: "LightSaber Beam",
position: position,
parentID: this.entityID,
isEmitting: false,
colorStart: color,
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: color,
maxParticles: 100000,
lifespan: 2,
emitRate: 1000,
emitOrientation: forwardQuat,
emitSpeed: 0.7,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0.1,
azimuthFinish: 0.01,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: .00,
y: .00,
z: .00
},
radiusStart: 0.03,
adiusFinish: 0.025,
alpha: 0.7,
alphaSpread: 0.1,
alphaStart: 0.5,
alphaFinish: 0.5,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
emitterShouldTrail: false
}
this.beam = Entities.addEntity(props);
}
};
// entity scripts always need to return a newly constructed object of our type
return new LightSaber();
});

View file

@ -0,0 +1,191 @@
//
// lightTrails.js
// examples
//
// Created by Eric Levin on 5/14/15.
// Copyright 2015 High Fidelity, Inc.
//
// This script creates light trails as you move your hydra hands
//
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../libraries/utils.js");
var eraseTrail = true;
var ugLSD = 25;
// var eraseTrail = false;
var LEFT = 0;
var RIGHT = 1;
var MAX_POINTS_PER_LINE = 50;
var LIFETIME = 6000;
var DRAWING_DEPTH = 0.8;
var LINE_DIMENSIONS = 100;
var MIN_POINT_DISTANCE = 0.02;
var colorPalette = [{
red: 250,
green: 137,
blue: 162
}, {
red: 204,
green: 244,
blue: 249
}, {
red: 146,
green: 206,
blue: 116
}, {
red: 240,
green: 87,
blue: 129
}];
var STROKE_WIDTH = 0.04;
function controller(side, triggerAction) {
this.triggerHeld = false;
this.triggerThreshold = 0.9;
this.side = side;
this.triggerAction = triggerAction;
var texture = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/trails.png";
this.light = Entities.addEntity({
type: 'Light',
position: MyAvatar.position,
dimensions: {
x: 30,
y: 30,
z: 30
},
color: colorPalette[randInt(0, colorPalette.length)],
intensity: 5
});
this.trail = Entities.addEntity({
type: "PolyLine",
dimensions: {
x: LINE_DIMENSIONS,
y: LINE_DIMENSIONS,
z: LINE_DIMENSIONS
},
color: {red: 255, green: 255, blue: 255},
textures: texture,
lifetime: LIFETIME
});
this.points = [];
this.normals = [];
this.strokeWidths = [];
var self = this;
this.trailEraseInterval = Script.setInterval(function() {
if (self.points.length > 0 && eraseTrail) {
self.points.shift();
self.normals.shift();
self.strokeWidths.shift();
Entities.editEntity(self.trail, {
linePoints: self.points,
strokeWidths: self.strokeWidths,
normals: self.normals
});
}
}, ugLSD);
this.setTrailPosition = function(position) {
this.trailPosition = position;
Entities.editEntity(this.trail, {
position: this.trailPosition
});
}
this.update = function(deltaTime) {
this.updateControllerState();
var newTrailPosOffset = Vec3.multiply(Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)), DRAWING_DEPTH);
var newTrailPos = Vec3.sum(this.palmPosition, newTrailPosOffset);
Entities.editEntity(this.light, {
position: newTrailPos
});
if (!this.drawing) {
this.setTrailPosition(newTrailPos);
this.drawing = true;
}
if (this.drawing) {
var localPoint = Vec3.subtract(newTrailPos, this.trailPosition);
if (Vec3.distance(localPoint, this.points[this.points.length - 1]) < MIN_POINT_DISTANCE) {
//Need a minimum distance to avoid binormal NANs
return;
}
if (this.points.length === MAX_POINTS_PER_LINE) {
this.points.shift();
this.normals.shift();
this.strokeWidths.shift();
}
this.points.push(localPoint);
var normal = computeNormal(newTrailPos, Camera.getPosition());
this.normals.push(normal);
this.strokeWidths.push(STROKE_WIDTH + Math.random() * 0.01);
Entities.editEntity(this.trail, {
linePoints: this.points,
normals: this.normals,
strokeWidths: this.strokeWidths,
});
}
}
this.updateControllerState = function() {
this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation;
this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation;
this.triggerValue = Controller.getActionValue(this.triggerAction);
}
this.cleanup = function() {
Entities.deleteEntity(this.trail);
Entities.deleteEntity(this.light);
Script.clearInterval(this.trailEraseInterval);
}
}
function computeNormal(p1, p2) {
return Vec3.normalize(Vec3.subtract(p2, p1));
}
function update(deltaTime) {
leftController.update(deltaTime);
rightController.update(deltaTime);
}
function scriptEnding() {
leftController.cleanup();
rightController.cleanup();
}
function vectorIsZero(v) {
return v.x === 0 && v.y === 0 && v.z === 0;
}
var rightController = new controller(RIGHT, Controller.findAction("RIGHT_HAND_CLICK"));
var leftController = new controller(LEFT, Controller.findAction("LEFT_HAND_CLICK"));
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);
function map(value, min1, max1, min2, max2) {
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
}

View file

@ -0,0 +1,95 @@
//
// RaveStick.js
// examples/flowArats/raveStick
//
// Created by Eric Levin on 12/17/15.
// Copyright 2015 High Fidelity, Inc.
//
// This script creates a rave stick which makes pretty light trails as you paint
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../libraries/utils.js");
var modelURL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/raveStick.fbx";
var scriptURL = Script.resolvePath("raveStickEntityScript.js");
RaveStick = function(spawnPosition) {
var colorPalette = [{
red: 0,
green: 200,
blue: 40
}, {
red: 200,
green: 10,
blue: 40
}];
var stick = Entities.addEntity({
type: "Model",
name: "raveStick",
modelURL: modelURL,
position: spawnPosition,
shapeType: 'box',
collisionsWillMove: true,
script: scriptURL,
dimensions: {
x: 0.06,
y: 0.06,
z: 0.31
},
userData: JSON.stringify({
grabbableKey: {
spatialKey: {
rightRelativePosition: {
x: 0.02,
y: 0,
z: 0
},
leftRelativePosition: {
x: -0.02,
y: 0,
z: 0
},
relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0)
},
invertSolidWhileHeld: true
}
})
});
var light = Entities.addEntity({
type: 'Light',
name: "raveLight",
parentID: stick,
dimensions: {
x: 30,
y: 30,
z: 30
},
color: colorPalette[randInt(0, colorPalette.length)],
intensity: 5
});
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0)
var forwardVec = Quat.getFront(Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
forwardVec = Vec3.normalize(forwardVec);
var forwardQuat = orientationOf(forwardVec);
var position = Vec3.sum(spawnPosition, Vec3.multiply(Quat.getFront(rotation), 0.1));
position.z += 0.1;
position.x += -0.035;
var color = {
red: 0,
green: 200,
blue: 40
};
function cleanup() {
Entities.deleteEntity(stick);
Entities.deleteEntity(light);
}
this.cleanup = cleanup;
}

View file

@ -0,0 +1,141 @@
// raveStickEntityScript.js
//
// Script Type: Entity
// Created by Eric Levin on 12/16/15.
// Copyright 2015 High Fidelity, Inc.
//
// This entity script create light trails on a given object as it moves.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include("../../libraries/utils.js");
var _this;
var LIFETIME = 6000;
var DRAWING_DEPTH = 0.8;
var LINE_DIMENSIONS = 100;
var MAX_POINTS_PER_LINE = 50;
var MIN_POINT_DISTANCE = 0.02;
var STROKE_WIDTH = 0.05
var ugLSD = 35;
var RaveStick = function() {
_this = this;
this.colorPalette = [{
red: 0,
green: 200,
blue: 40
}, {
red: 200,
green: 10,
blue: 40
}];
var texture = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/trails.png";
this.trail = Entities.addEntity({
type: "PolyLine",
dimensions: {
x: LINE_DIMENSIONS,
y: LINE_DIMENSIONS,
z: LINE_DIMENSIONS
},
color: {
red: 255,
green: 255,
blue: 255
},
textures: texture,
lifetime: LIFETIME
});
this.points = [];
this.normals = [];
this.strokeWidths = [];
};
RaveStick.prototype = {
isGrabbed: false,
startNearGrab: function() {
this.trailBasePosition = Entities.getEntityProperties(this.entityID, "position").position;
Entities.editEntity(this.trail, {
position: this.trailBasePosition
});
this.points = [];
this.normals = [];
this.strokeWidths = [];
this.setupEraseInterval();
},
continueNearGrab: function() {
var props = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
var forwardVec = Quat.getFront(Quat.multiply(props.rotation, Quat.fromPitchYawRollDegrees(-90, 0, 0)));
forwardVec = Vec3.normalize(forwardVec);
var forwardQuat = orientationOf(forwardVec);
var position = Vec3.sum(props.position, Vec3.multiply(Quat.getFront(props.rotation), 0.04));
var localPoint = Vec3.subtract(position, this.trailBasePosition);
if (this.points.length >= 1 && Vec3.distance(localPoint, this.points[this.points.length - 1]) < MIN_POINT_DISTANCE) {
//Need a minimum distance to avoid binormal NANs
return;
}
if (this.points.length === MAX_POINTS_PER_LINE) {
this.points.shift();
this.normals.shift();
this.strokeWidths.shift();
}
this.points.push(localPoint);
var normal = Quat.getUp(props.rotation);
this.normals.push(normal);
this.strokeWidths.push(STROKE_WIDTH);
Entities.editEntity(this.trail, {
linePoints: this.points,
normals: this.normals,
strokeWidths: this.strokeWidths
});
},
setupEraseInterval: function() {
this.trailEraseInterval = Script.setInterval(function() {
if (_this.points.length > 0) {
_this.points.shift();
_this.normals.shift();
_this.strokeWidths.shift();
Entities.editEntity(_this.trail, {
linePoints: _this.points,
strokeWidths: _this.strokeWidths,
normals: _this.normals
});
}
}, ugLSD);
},
releaseGrab: function() {
if(!this.trailEraseInterval) {
return;
}
Script.setTimeout(function() {
Script.clearInterval(_this.trailEraseInterval);
_this.trailEraseInterval = null;
}, 3000);
},
preload: function(entityID) {
this.entityID = entityID;
},
unload: function() {
Entities.deleteEntity(this.beam);
Entities.deleteEntity(this.trail);
if (this.trailEraseInterval) {
Script.clearInterval(this.trailEraseInterval);
}
}
};
return new RaveStick();
function computeNormal(p1, p2) {
return Vec3.normalize(Vec3.subtract(p2, p1));
}
});

View file

@ -0,0 +1,59 @@
//public slots:
// void emitWebEvent(const QString& data);
// void emitScriptEvent(const QString& data);
//
//signals:
// void webEventReceived(const QString& data);
// void scriptEventReceived(const QString& data);
//
EventBridgeConnectionProxy = function(parent) {
this.parent = parent;
this.realSignal = this.parent.realBridge.scriptEventReceived
this.webWindowId = this.parent.webWindow.windowId;
}
EventBridgeConnectionProxy.prototype.connect = function(callback) {
var that = this;
this.realSignal.connect(function(id, message) {
if (id === that.webWindowId) { callback(message); }
});
}
EventBridgeProxy = function(webWindow) {
this.webWindow = webWindow;
this.realBridge = this.webWindow.eventBridge;
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
}
EventBridgeProxy.prototype.emitWebEvent = function(data) {
this.realBridge.emitWebEvent(data);
}
openEventBridge = function(callback) {
EVENT_BRIDGE_URI = "ws://localhost:51016";
socket = new WebSocket(this.EVENT_BRIDGE_URI);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL);
for(var key in channel.objects){
console.log("registered object: " + key);
}
var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow);
if (callback) { callback(eventBridgeProxy); }
});
}
}

View file

@ -0,0 +1,31 @@
<html>
<head>
<title>Properties</title>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script>
var myBridge;
window.onload = function() {
openEventBridge(function(eventBridge) {
myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message);
});
});
}
testClick = function() {
myBridge.emitWebEvent("HTML side sending message - button click");
}
</script>
</head>
<body class="properties">
<button name="Test" title="Test" onclick="testClick()">Test</button>
</body>
</html>
</body>

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,9 @@ LightOverlayManager = function() {
if (visible != isVisible) {
visible = isVisible;
for (var id in entityOverlays) {
Overlays.editOverlay(entityOverlays[id], { visible: visible });
Overlays.editOverlay(entityOverlays[id], {
visible: visible
});
}
}
};
@ -61,8 +63,7 @@ LightOverlayManager = function() {
// Allocate or get an unused overlay
function getOverlay() {
if (unusedOverlays.length == 0) {
var overlay = Overlays.addOverlay("image3d", {
});
var overlay = Overlays.addOverlay("image3d", {});
allOverlays.push(overlay);
} else {
var overlay = unusedOverlays.pop();
@ -72,7 +73,9 @@ LightOverlayManager = function() {
function releaseOverlay(overlay) {
unusedOverlays.push(overlay);
Overlays.editOverlay(overlay, { visible: false });
Overlays.editOverlay(overlay, {
visible: false
});
}
function addEntity(entityID) {
@ -88,7 +91,11 @@ LightOverlayManager = function() {
visible: visible,
alpha: 0.9,
scale: 0.5,
color: { red: 255, green: 255, blue: 255 }
color: {
red: 255,
green: 255,
blue: 255
}
});
}
}
@ -123,4 +130,4 @@ LightOverlayManager = function() {
Overlays.deleteOverlay(allOverlays[i]);
}
});
};
};

View file

@ -271,3 +271,29 @@ hexToRgb = function(hex) {
} : null;
}
calculateHandSizeRatio = function() {
// Get the ratio of the current avatar's hand to Owen's hand
var standardCenterHandPoint = 0.11288;
var jointNames = MyAvatar.getJointNames();
//get distance from handJoint up to leftHandIndex3 as a proxy for center of hand
var wristToFingertipDistance = 0;;
for (var i = 0; i < jointNames.length; i++) {
var jointName = jointNames[i];
print(jointName)
if (jointName.indexOf("LeftHandIndex") !== -1) {
// translations are relative to parent joint, so simply add them together
// joints face down the y-axis
var translation = MyAvatar.getDefaultJointTranslation(i).y;
wristToFingertipDistance += translation;
}
}
// Right now units are in cm, so convert to meters
wristToFingertipDistance /= 100;
var centerHandPoint = wristToFingertipDistance/2;
// Compare against standard hand (Owen)
var handSizeRatio = centerHandPoint/standardCenterHandPoint;
return handSizeRatio;
}

View file

@ -0,0 +1,29 @@
This PR demonstrates one way in-world editing of objects might work.
Running this script will show light overlay icons in-world. Enter edit mode by running your distance beam through a light overlay. Exit using the red X.
When you distant grab the sliders, you can move them along their axis to change their values. You may also rotate / move the block to which the spotlight is attached.
To test: https://rawgit.com/imgntn/hifi/light_mod/examples/lights/lightLoader.js
To reset, I recommend stopping all scripts then re-loading lightLoader.js
When you run the lightLoader.js script, several scripts will be loaded:
- handControllerGrab.js (will not impart velocity when you move the parent or a slider, will not move sliders with head movement,will constrain movement for a slider to a given axis start and end, will support blacklisting of entities for raypicking during search for objects)
- lightModifier.js (listens for message to create sliders for a given light. will start with slider set to the light's initial properties)
- lightModifierTestScene.js (creates a light)
- slider.js (attached to each slider entity)
- lightParent.js (attached to a 3d model of a light, to which a light is parented, so you can move it around. or keep the current parent if a light already has a parent)
- visiblePanel.js (the transparent panel)
- closeButton.js (for closing the ui)
- ../libraries/lightOverlayManager.js (shows 2d overlays for lights in the world)
- ../libraries/entitySelectionTool.js (visualizes volume of the lights)
Current sliders are (top to bottom):
red
green
blue
intensity
cutoff
exponent
![capture](https://cloud.githubusercontent.com/assets/843228/11910139/afaaf1ae-a5a5-11e5-8b66-0eb3fc6976df.PNG)

View file

@ -0,0 +1,36 @@
//
// closeButton.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Entity script that closes sliders when interacted with.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
function CloseButton() {
return this;
}
CloseButton.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
this.initialProperties = entityProperties
this.userData = JSON.parse(entityProperties.userData);
},
startNearGrab: function() {
},
startFarTrigger: function() {
Messages.sendMessage('Hifi-Light-Modifier-Cleanup', 'callCleanup')
}
};
return new CloseButton();
});

View file

@ -0,0 +1,20 @@
//
// lightLoader.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Loads a test scene showing sliders that you can grab and move to change entity properties.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var grabScript = Script.resolvePath('../controllers/handControllerGrab.js?' + Math.random(0 - 100));
Script.load(grabScript);
var lightModifier = Script.resolvePath('lightModifier.js?' + Math.random(0 - 100));
Script.load(lightModifier);
Script.setTimeout(function() {
var lightModifierTestScene = Script.resolvePath('lightModifierTestScene.js?' + Math.random(0 - 100));
Script.load(lightModifierTestScene);
}, 750)

View file

@ -0,0 +1,876 @@
//
// lightModifier.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Given a selected light, instantiate some entities that represent various values you can dynamically adjust by grabbing and moving.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//some experimental options
var ONLY_I_CAN_EDIT = false;
var SLIDERS_SHOULD_STAY_WITH_AVATAR = false;
var VERTICAL_SLIDERS = false;
var SHOW_OVERLAYS = true;
var SHOW_LIGHT_VOLUME = true;
var USE_PARENTED_PANEL = true;
var VISIBLE_PANEL = true;
var USE_LABELS = true;
var LEFT_LABELS = false;
var RIGHT_LABELS = true;
var ROTATE_CLOSE_BUTTON = false;
//variables for managing overlays
var selectionDisplay;
var selectionManager;
var lightOverlayManager;
//for when we make a 3d model of a light a parent for the light
var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100));
if (SHOW_OVERLAYS === true) {
Script.include('../libraries/gridTool.js');
Script.include('../libraries/entitySelectionTool.js?' + Math.random(0 - 100));
Script.include('../libraries/lightOverlayManager.js');
var grid = Grid();
gridTool = GridTool({
horizontalGrid: grid
});
gridTool.setVisible(false);
selectionDisplay = SelectionDisplay;
selectionManager = SelectionManager;
lightOverlayManager = new LightOverlayManager();
selectionManager.addEventListener(function() {
selectionDisplay.updateHandles();
lightOverlayManager.updatePositions();
});
lightOverlayManager.setVisible(true);
}
var DEFAULT_PARENT_ID = '{00000000-0000-0000-0000-000000000000}'
var AXIS_SCALE = 1;
var COLOR_MAX = 255;
var INTENSITY_MAX = 0.05;
var CUTOFF_MAX = 360;
var EXPONENT_MAX = 1;
var SLIDER_SCRIPT_URL = Script.resolvePath('slider.js?' + Math.random(0, 100));
var LIGHT_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/source4_very_good.fbx';
var CLOSE_BUTTON_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/red_x.fbx';
var CLOSE_BUTTON_SCRIPT_URL = Script.resolvePath('closeButton.js?' + Math.random(0, 100));
var TRANSPARENT_PANEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/transparent_box_alpha_15.fbx';
var VISIBLE_PANEL_SCRIPT_URL = Script.resolvePath('visiblePanel.js?' + Math.random(0, 100));
var RED = {
red: 255,
green: 0,
blue: 0
};
var GREEN = {
red: 0,
green: 255,
blue: 0
};
var BLUE = {
red: 0,
green: 0,
blue: 255
};
var PURPLE = {
red: 255,
green: 0,
blue: 255
};
var WHITE = {
red: 255,
green: 255,
blue: 255
};
var ORANGE = {
red: 255,
green: 165,
blue: 0
}
var SLIDER_DIMENSIONS = {
x: 0.075,
y: 0.075,
z: 0.075
};
var CLOSE_BUTTON_DIMENSIONS = {
x: 0.1,
y: 0.025,
z: 0.1
}
var LIGHT_MODEL_DIMENSIONS = {
x: 0.58,
y: 1.21,
z: 0.57
}
var PER_ROW_OFFSET = {
x: 0,
y: -0.2,
z: 0
};
var sliders = [];
var slidersRef = {
'color_red': null,
'color_green': null,
'color_blue': null,
intensity: null,
cutoff: null,
exponent: null
};
var light = null;
var basePosition;
var avatarRotation;
function entitySlider(light, color, sliderType, displayText, row) {
this.light = light;
this.lightID = light.id.replace(/[{}]/g, "");
this.initialProperties = light.initialProperties;
this.color = color;
this.sliderType = sliderType;
this.displayText = displayText;
this.verticalOffset = Vec3.multiply(row, PER_ROW_OFFSET);
this.avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0);
this.basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(1.5, Quat.getFront(this.avatarRot)));
this.basePosition.y += 1;
basePosition = this.basePosition;
avatarRot = this.avatarRot;
var message = {
lightID: this.lightID,
sliderType: this.sliderType,
sliderValue: null
};
if (this.sliderType === 'color_red') {
message.sliderValue = this.initialProperties.color.red
this.setValueFromMessage(message);
}
if (this.sliderType === 'color_green') {
message.sliderValue = this.initialProperties.color.green
this.setValueFromMessage(message);
}
if (this.sliderType === 'color_blue') {
message.sliderValue = this.initialProperties.color.blue
this.setValueFromMessage(message);
}
if (this.sliderType === 'intensity') {
message.sliderValue = this.initialProperties.intensity
this.setValueFromMessage(message);
}
if (this.sliderType === 'exponent') {
message.sliderValue = this.initialProperties.exponent
this.setValueFromMessage(message);
}
if (this.sliderType === 'cutoff') {
message.sliderValue = this.initialProperties.cutoff
this.setValueFromMessage(message);
}
this.setInitialSliderPositions();
this.createAxis();
this.createSliderIndicator();
if (USE_LABELS === true) {
this.createLabel()
}
return this;
}
//what's the ux for adjusting values? start with simple entities, try image overlays etc
entitySlider.prototype = {
createAxis: function() {
//start of line
var position;
var extension;
if (VERTICAL_SLIDERS == true) {
position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot)))));
//line starts on bottom and goes up
var upVector = Quat.getUp(this.avatarRot);
extension = Vec3.multiply(AXIS_SCALE, upVector);
} else {
position = Vec3.sum(this.basePosition, this.verticalOffset);
//line starts on left and goes to right
//set the end of the line to the right
var rightVector = Quat.getRight(this.avatarRot);
extension = Vec3.multiply(AXIS_SCALE, rightVector);
}
this.axisStart = position;
this.endOfAxis = Vec3.sum(position, extension);
this.createEndOfAxisEntity();
var properties = {
type: 'Line',
name: 'Hifi-Slider-Axis::' + this.sliderType,
color: this.color,
collisionsWillMove: false,
ignoreForCollisions: true,
dimensions: {
x: 3,
y: 3,
z: 3
},
position: position,
linePoints: [{
x: 0,
y: 0,
z: 0
}, extension],
lineWidth: 5,
};
this.axis = Entities.addEntity(properties);
},
createEndOfAxisEntity: function() {
//we use this to track the end of the axis while parented to a panel
var properties = {
name: 'Hifi-End-Of-Axis',
type: 'Box',
collisionsWillMove: false,
ignoreForCollisions: true,
dimensions: {
x: 0.01,
y: 0.01,
z: 0.01
},
color: {
red: 255,
green: 255,
blue: 255
},
position: this.endOfAxis,
parentID: this.axis,
visible: false
}
this.endOfAxisEntity = Entities.addEntity(this.endOfAxis);
},
createLabel: function() {
var LABEL_WIDTH = 0.25
var PER_LETTER_SPACING = 0.1;
var textWidth = this.displayText.length * PER_LETTER_SPACING;
var position;
if (LEFT_LABELS === true) {
var leftVector = Vec3.multiply(-1, Quat.getRight(this.avatarRot));
var extension = Vec3.multiply(textWidth, leftVector);
position = Vec3.sum(this.axisStart, extension);
}
if (RIGHT_LABELS === true) {
var rightVector = Quat.getRight(this.avatarRot);
var extension = Vec3.multiply(textWidth / 1.75, rightVector);
position = Vec3.sum(this.endOfAxis, extension);
}
var labelProperties = {
name: 'Hifi-Slider-Label-' + this.sliderType,
type: 'Text',
dimensions: {
x: textWidth,
y: 0.2,
z: 0.1
},
textColor: {
red: 255,
green: 255,
blue: 255
},
text: this.displayText,
lineHeight: 0.14,
backgroundColor: {
red: 0,
green: 0,
blue: 0
},
position: position,
rotation: this.avatarRot,
}
print('BEFORE CREATE LABEL' + JSON.stringify(labelProperties))
this.label = Entities.addEntity(labelProperties);
print('AFTER CREATE LABEL')
},
createSliderIndicator: function() {
var extensionVector;
var position;
if (VERTICAL_SLIDERS == true) {
position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot)))));
extensionVector = Quat.getUp(this.avatarRot);
} else {
position = Vec3.sum(this.basePosition, this.verticalOffset);
extensionVector = Quat.getRight(this.avatarRot);
}
var initialDistance;
if (this.sliderType === 'color_red') {
initialDistance = this.distanceRed;
}
if (this.sliderType === 'color_green') {
initialDistance = this.distanceGreen;
}
if (this.sliderType === 'color_blue') {
initialDistance = this.distanceBlue;
}
if (this.sliderType === 'intensity') {
initialDistance = this.distanceIntensity;
}
if (this.sliderType === 'cutoff') {
initialDistance = this.distanceCutoff;
}
if (this.sliderType === 'exponent') {
initialDistance = this.distanceExponent;
}
var extension = Vec3.multiply(initialDistance, extensionVector);
var sliderPosition = Vec3.sum(position, extension);
var properties = {
type: 'Sphere',
name: 'Hifi-Slider-' + this.sliderType,
dimensions: SLIDER_DIMENSIONS,
collisionsWillMove: true,
color: this.color,
position: sliderPosition,
script: SLIDER_SCRIPT_URL,
ignoreForCollisions: true,
userData: JSON.stringify({
lightModifierKey: {
lightID: this.lightID,
sliderType: this.sliderType,
axisStart: position,
axisEnd: this.endOfAxis,
},
handControllerKey: {
disableReleaseVelocity: true,
disableMoveWithHead: true,
disableNearGrab:true
}
}),
};
this.sliderIndicator = Entities.addEntity(properties);
},
setValueFromMessage: function(message) {
//message is not for our light
if (message.lightID !== this.lightID) {
// print('not our light')
return;
}
//message is not our type
if (message.sliderType !== this.sliderType) {
// print('not our slider type')
return
}
var lightProperties = Entities.getEntityProperties(this.lightID);
if (this.sliderType === 'color_red') {
Entities.editEntity(this.lightID, {
color: {
red: message.sliderValue,
green: lightProperties.color.green,
blue: lightProperties.color.blue
}
});
}
if (this.sliderType === 'color_green') {
Entities.editEntity(this.lightID, {
color: {
red: lightProperties.color.red,
green: message.sliderValue,
blue: lightProperties.color.blue
}
});
}
if (this.sliderType === 'color_blue') {
Entities.editEntity(this.lightID, {
color: {
red: lightProperties.color.red,
green: lightProperties.color.green,
blue: message.sliderValue,
}
});
}
if (this.sliderType === 'intensity') {
Entities.editEntity(this.lightID, {
intensity: message.sliderValue
});
}
if (this.sliderType === 'cutoff') {
Entities.editEntity(this.lightID, {
cutoff: message.sliderValue
});
}
if (this.sliderType === 'exponent') {
Entities.editEntity(this.lightID, {
exponent: message.sliderValue
});
}
},
setInitialSliderPositions: function() {
this.distanceRed = (this.initialProperties.color.red / COLOR_MAX) * AXIS_SCALE;
this.distanceGreen = (this.initialProperties.color.green / COLOR_MAX) * AXIS_SCALE;
this.distanceBlue = (this.initialProperties.color.blue / COLOR_MAX) * AXIS_SCALE;
this.distanceIntensity = (this.initialProperties.intensity / INTENSITY_MAX) * AXIS_SCALE;
this.distanceCutoff = (this.initialProperties.cutoff / CUTOFF_MAX) * AXIS_SCALE;
this.distanceExponent = (this.initialProperties.exponent / EXPONENT_MAX) * AXIS_SCALE;
}
};
var panel;
var visiblePanel;
function makeSliders(light) {
if (USE_PARENTED_PANEL === true) {
panel = createPanelEntity(MyAvatar.position);
}
if (light.type === 'spotlight') {
var USE_COLOR_SLIDER = true;
var USE_INTENSITY_SLIDER = true;
var USE_CUTOFF_SLIDER = true;
var USE_EXPONENT_SLIDER = true;
}
if (light.type === 'pointlight') {
var USE_COLOR_SLIDER = true;
var USE_INTENSITY_SLIDER = true;
var USE_CUTOFF_SLIDER = false;
var USE_EXPONENT_SLIDER = false;
}
if (USE_COLOR_SLIDER === true) {
slidersRef.color_red = new entitySlider(light, RED, 'color_red', 'Red', 1);
slidersRef.color_green = new entitySlider(light, GREEN, 'color_green', 'Green', 2);
slidersRef.color_blue = new entitySlider(light, BLUE, 'color_blue', 'Blue', 3);
sliders.push(slidersRef.color_red);
sliders.push(slidersRef.color_green);
sliders.push(slidersRef.color_blue);
}
if (USE_INTENSITY_SLIDER === true) {
slidersRef.intensity = new entitySlider(light, WHITE, 'intensity', 'Intensity', 4);
sliders.push(slidersRef.intensity);
}
if (USE_CUTOFF_SLIDER === true) {
slidersRef.cutoff = new entitySlider(light, PURPLE, 'cutoff', 'Cutoff', 5);
sliders.push(slidersRef.cutoff);
}
if (USE_EXPONENT_SLIDER === true) {
slidersRef.exponent = new entitySlider(light, ORANGE, 'exponent', 'Exponent', 6);
sliders.push(slidersRef.exponent);
}
createCloseButton(slidersRef.color_red.axisStart);
subscribeToSliderMessages();
if (USE_PARENTED_PANEL === true) {
parentEntitiesToPanel(panel);
}
if (SLIDERS_SHOULD_STAY_WITH_AVATAR === true) {
parentPanelToAvatar(panel);
}
if (VISIBLE_PANEL === true) {
visiblePanel = createVisiblePanel();
}
};
function parentPanelToAvatar(panel) {
//this is going to need some more work re: the sliders actually being grabbable. probably something to do with updating axis movement
Entities.editEntity(panel, {
parentID: MyAvatar.sessionUUID,
//actually figure out which one to parent it to -- probably a spine or something.
parentJointIndex: 1,
})
}
function parentEntitiesToPanel(panel) {
sliders.forEach(function(slider) {
Entities.editEntity(slider.axis, {
parentID: panel
})
Entities.editEntity(slider.sliderIndicator, {
parentID: panel
})
})
closeButtons.forEach(function(button) {
Entities.editEntity(button, {
parentID: panel
})
})
}
function createPanelEntity(position) {
print('CREATING PANEL at ' + JSON.stringify(position));
var panelProperties = {
name: 'Hifi-Slider-Panel',
type: 'Box',
dimensions: {
x: 0.1,
y: 0.1,
z: 0.1
},
visible: false,
collisionsWillMove: false,
ignoreForCollisions: true
}
var panel = Entities.addEntity(panelProperties);
return panel
}
function createVisiblePanel() {
var totalOffset = -PER_ROW_OFFSET.y * sliders.length;
var moveRight = Vec3.sum(basePosition, Vec3.multiply(AXIS_SCALE / 2, Quat.getRight(avatarRot)));
var moveDown = Vec3.sum(moveRight, Vec3.multiply((sliders.length + 1) / 2, PER_ROW_OFFSET))
var panelProperties = {
name: 'Hifi-Visible-Transparent-Panel',
type: 'Model',
modelURL: TRANSPARENT_PANEL_URL,
dimensions: {
x: AXIS_SCALE + 0.1,
y: totalOffset,
z: SLIDER_DIMENSIONS.z / 4
},
visible: true,
collisionsWillMove: false,
ignoreForCollisions: true,
position: moveDown,
rotation: avatarRot,
script: VISIBLE_PANEL_SCRIPT_URL
}
var panel = Entities.addEntity(panelProperties);
return panel
}
function createLightModel(position, rotation) {
var blockProperties = {
name: 'Hifi-Spotlight-Model',
type: 'Model',
shapeType: 'box',
modelURL: LIGHT_MODEL_URL,
dimensions: LIGHT_MODEL_DIMENSIONS,
collisionsWillMove: true,
position: position,
rotation: rotation,
script: PARENT_SCRIPT_URL,
userData: JSON.stringify({
handControllerKey: {
disableReleaseVelocity: true
}
})
};
var block = Entities.addEntity(blockProperties);
return block
}
var closeButtons = [];
function createCloseButton(axisStart) {
var MARGIN = 0.10;
var VERTICAL_OFFFSET = {
x: 0,
y: 0.15,
z: 0
};
var leftVector = Vec3.multiply(-1, Quat.getRight(avatarRot));
var extension = Vec3.multiply(MARGIN, leftVector);
var position = Vec3.sum(axisStart, extension);
var buttonProperties = {
name: 'Hifi-Close-Button',
type: 'Model',
modelURL: CLOSE_BUTTON_MODEL_URL,
dimensions: CLOSE_BUTTON_DIMENSIONS,
position: Vec3.sum(position, VERTICAL_OFFFSET),
rotation: Quat.multiply(avatarRot, Quat.fromPitchYawRollDegrees(90, 0, 45)),
//rotation: Quat.fromPitchYawRollDegrees(0, 0, 90),
collisionsWillMove: false,
ignoreForCollisions: true,
script: CLOSE_BUTTON_SCRIPT_URL,
userData: JSON.stringify({
grabbableKey: {
wantsTrigger: true
}
})
}
var button = Entities.addEntity(buttonProperties);
closeButtons.push(button);
if (ROTATE_CLOSE_BUTTON === true) {
Script.update.connect(rotateCloseButtons);
}
}
function rotateCloseButtons() {
closeButtons.forEach(function(button) {
Entities.editEntity(button, {
angularVelocity: {
x: 0,
y: 0.5,
z: 0
}
})
})
}
function subScribeToNewLights() {
Messages.subscribe('Hifi-Light-Mod-Receiver');
Messages.messageReceived.connect(handleLightModMessages);
}
function subscribeToSliderMessages() {
Messages.subscribe('Hifi-Slider-Value-Reciever');
Messages.messageReceived.connect(handleValueMessages);
}
function subscribeToLightOverlayRayCheckMessages() {
Messages.subscribe('Hifi-Light-Overlay-Ray-Check');
Messages.messageReceived.connect(handleLightOverlayRayCheckMessages);
}
function subscribeToCleanupMessages() {
Messages.subscribe('Hifi-Light-Modifier-Cleanup');
Messages.messageReceived.connect(handleCleanupMessages);
}
function handleLightModMessages(channel, message, sender) {
if (channel !== 'Hifi-Light-Mod-Receiver') {
return;
}
if (sender !== MyAvatar.sessionUUID) {
return;
}
var parsedMessage = JSON.parse(message);
makeSliders(parsedMessage.light);
light = parsedMessage.light.id
if (SHOW_LIGHT_VOLUME === true) {
selectionManager.setSelections([parsedMessage.light.id]);
}
}
function handleValueMessages(channel, message, sender) {
if (channel !== 'Hifi-Slider-Value-Reciever') {
return;
}
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
return;
}
var parsedMessage = JSON.parse(message);
slidersRef[parsedMessage.sliderType].setValueFromMessage(parsedMessage);
}
var currentLight;
var block;
var oldParent = null;
var hasParent = false;
function handleLightOverlayRayCheckMessages(channel, message, sender) {
if (channel !== 'Hifi-Light-Overlay-Ray-Check') {
return;
}
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
return;
}
var pickRay = JSON.parse(message);
var doesIntersect = lightOverlayManager.findRayIntersection(pickRay);
// print('DOES INTERSECT A LIGHT WE HAVE???' + doesIntersect.intersects);
if (doesIntersect.intersects === true) {
// print('FULL MESSAGE:::' + JSON.stringify(doesIntersect))
var lightID = doesIntersect.entityID;
if (currentLight === lightID) {
// print('ALREADY HAVE A BLOCK, EXIT')
return;
}
currentLight = lightID;
var lightProperties = Entities.getEntityProperties(lightID);
if (lightProperties.parentID !== DEFAULT_PARENT_ID) {
//this light has a parent already. so lets call our block the parent and then make sure not to delete it at the end;
oldParent = lightProperties.parentID;
hasParent = true;
block = lightProperties.parentID;
if (lightProperties.parentJointIndex !== -1) {
//should make sure to retain the parent too. but i don't actually know what the
}
} else {
block = createLightModel(lightProperties.position, lightProperties.rotation);
}
var light = {
id: lightID,
type: 'spotlight',
initialProperties: lightProperties
}
makeSliders(light);
if (SHOW_LIGHT_VOLUME === true) {
selectionManager.setSelections([lightID]);
}
Entities.editEntity(lightID, {
parentID: block,
parentJointIndex: -1
});
}
}
function handleCleanupMessages(channel, message, sender) {
if (channel !== 'Hifi-Light-Modifier-Cleanup') {
return;
}
if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) {
return;
}
if (message === 'callCleanup') {
cleanup(true);
}
}
function updateSliderAxis() {
sliders.forEach(function(slider) {
})
}
function cleanup(fromMessage) {
var i;
for (i = 0; i < sliders.length; i++) {
Entities.deleteEntity(sliders[i].axis);
Entities.deleteEntity(sliders[i].sliderIndicator);
Entities.deleteEntity(sliders[i].label);
}
while (closeButtons.length > 0) {
Entities.deleteEntity(closeButtons.pop());
}
//if the light was already parented to something we will want to restore that. or come up with groups or something clever.
if (oldParent !== null) {
Entities.editEntity(currentLight, {
parentID: oldParent,
});
} else {
Entities.editEntity(currentLight, {
parentID: null,
});
}
if (fromMessage !== true) {
Messages.messageReceived.disconnect(handleLightModMessages);
Messages.messageReceived.disconnect(handleValueMessages);
Messages.messageReceived.disconnect(handleLightOverlayRayCheckMessages);
lightOverlayManager.setVisible(false);
}
Entities.deleteEntity(panel);
Entities.deleteEntity(visiblePanel);
selectionManager.clearSelections();
if (ROTATE_CLOSE_BUTTON === true) {
Script.update.disconnect(rotateCloseButtons);
}
if (hasParent === false) {
Entities.deleteEntity(block);
}
oldParent = null;
hasParent = false;
currentLight = null;
sliders = [];
}
Script.scriptEnding.connect(cleanup);
Script.scriptEnding.connect(function() {
lightOverlayManager.setVisible(false);
})
subscribeToLightOverlayRayCheckMessages();
subScribeToNewLights();
subscribeToCleanupMessages();
//other light properties
// diffuseColor: { red: 255, green: 255, blue: 255 },
// ambientColor: { red: 255, green: 255, blue: 255 },
// specularColor: { red: 255, green: 255, blue: 255 },
// constantAttenuation: 1,
// linearAttenuation: 0,
// quadraticAttenuation: 0,
// exponent: 0,
// cutoff: 180, // in degrees

View file

@ -0,0 +1,73 @@
//
// lightModifierTestScene.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Given a selected light, instantiate some entities that represent various values you can dynamically adjust.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100));
var basePosition, avatarRot;
avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0);
basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(0, Quat.getUp(avatarRot)));
var light;
function createLight() {
var position = basePosition;
position.y += 2;
var lightTransform = evalLightWorldTransform(position, avatarRot);
var lightProperties = {
name: 'Hifi-Spotlight',
type: "Light",
isSpotlight: true,
dimensions: {
x: 2,
y: 2,
z: 8
},
color: {
red: 255,
green: 0,
blue: 255
},
intensity: 0.035,
exponent: 1,
cutoff: 30,
lifetime: -1,
position: lightTransform.p,
rotation: lightTransform.q
};
light = Entities.addEntity(lightProperties);
}
function evalLightWorldTransform(modelPos, modelRot) {
var MODEL_LIGHT_POSITION = {
x: 0,
y: -0.3,
z: 0
};
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
});
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
}
function cleanup() {
Entities.deleteEntity(light);
}
Script.scriptEnding.connect(cleanup);
createLight();

View file

@ -0,0 +1,40 @@
//
// lightParent.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Entity script that tells the light parent to update the selection tool when we move it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
function LightParent() {
return this;
}
LightParent.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
this.initialProperties = entityProperties
this.userData = JSON.parse(entityProperties.userData);
},
startNearGrab: function() {},
startDistantGrab: function() {
},
continueNearGrab: function() {
this.continueDistantGrab();
},
continueDistantGrab: function() {
Messages.sendMessage('entityToolUpdates', 'callUpdate');
},
};
return new LightParent();
});

View file

@ -0,0 +1,105 @@
//
// slider.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Entity script that sends a scaled value to a light based on its distance from the start of its constraint axis.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var AXIS_SCALE = 1;
var COLOR_MAX = 255;
var INTENSITY_MAX = 0.05;
var CUTOFF_MAX = 360;
var EXPONENT_MAX = 1;
function Slider() {
return this;
}
Slider.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var entityProperties = Entities.getEntityProperties(this.entityID, "userData");
var parsedUserData = JSON.parse(entityProperties.userData);
this.userData = parsedUserData.lightModifierKey;
},
startNearGrab: function() {
this.setInitialProperties();
},
startDistantGrab: function() {
this.setInitialProperties();
},
setInitialProperties: function() {
this.initialProperties = Entities.getEntityProperties(this.entityID);
},
continueNearGrab: function() {
// this.continueDistantGrab();
},
continueDistantGrab: function() {
this.setSliderValueBasedOnDistance();
},
setSliderValueBasedOnDistance: function() {
var currentPosition = Entities.getEntityProperties(this.entityID, "position").position;
var distance = Vec3.distance(this.userData.axisStart, currentPosition);
if (this.userData.sliderType === 'color_red' || this.userData.sliderType === 'color_green' || this.userData.sliderType === 'color_blue') {
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, COLOR_MAX);
}
if (this.userData.sliderType === 'intensity') {
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, INTENSITY_MAX);
}
if (this.userData.sliderType === 'cutoff') {
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, CUTOFF_MAX);
}
if (this.userData.sliderType === 'exponent') {
this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, EXPONENT_MAX);
};
this.sendValueToSlider();
},
releaseGrab: function() {
Entities.editEntity(this.entityID, {
velocity: {
x: 0,
y: 0,
z: 0
},
angularVelocity: {
x: 0,
y: 0,
z: 0
}
})
this.sendValueToSlider();
},
scaleValueBasedOnDistanceFromStart: function(value, min2, max2) {
var min1 = 0;
var max1 = AXIS_SCALE;
var min2 = min2;
var max2 = max2;
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
},
sendValueToSlider: function() {
var _t = this;
var message = {
lightID: _t.userData.lightID,
sliderType: _t.userData.sliderType,
sliderValue: _t.sliderValue
}
Messages.sendMessage('Hifi-Slider-Value-Reciever', JSON.stringify(message));
if (_t.userData.sliderType === 'cutoff') {
Messages.sendMessage('entityToolUpdates', 'callUpdate');
}
}
};
return new Slider();
});

View file

@ -0,0 +1,40 @@
//
// visiblePanel.js
//
// Created by James Pollack @imgntn on 12/15/2015
// Copyright 2015 High Fidelity, Inc.
//
// Entity script that disables picking on this panel.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
function VisiblePanel() {
return this;
}
VisiblePanel.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var data = {
action: 'add',
id: this.entityID
};
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data))
},
unload: function() {
var data = {
action: 'remove',
id: this.entityID
};
Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data))
}
};
return new VisiblePanel();
});

View file

@ -43,6 +43,8 @@ var MAX_STROKE_WIDTH = 0.04;
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(Camera.getOrientation())));
var textureURL = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png";
function MyController(hand, triggerAction) {
@ -148,7 +150,8 @@ function MyController(hand, triggerAction) {
y: 50,
z: 50
},
lifetime: 200
lifetime: 200,
textures: textureURL
});
this.strokePoints = [];
this.strokeNormals = [];

View file

@ -29,6 +29,8 @@
var MIN_STROKE_WIDTH = 0.0005;
var MAX_STROKE_WIDTH = 0.03;
var textureURL = "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png";
var TRIGGER_CONTROLS = [
Controller.Standard.LT,
Controller.Standard.RT,
@ -168,6 +170,7 @@
type: "PolyLine",
name: "paintStroke",
color: this.strokeColor,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/paintStrokes/paintStroke.png",
dimensions: {
x: 50,
y: 50,

View file

@ -247,4 +247,4 @@ function cleanup() {
// Uncomment this line to delete whiteboard and all associated entity on script close
//Script.scriptEnding.connect(cleanup);
// Script.scriptEnding.connect(cleanup);

View file

@ -0,0 +1,73 @@
//
// rayPickingFilterExample.js
// examples
//
// Created by Eric Levin on 12/24/2015
// Copyright 2015 High Fidelity, Inc.
//
// This is an example script that demonstrates the use of filtering entities for ray picking
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
var whiteListBox = Entities.addEntity({
type: "Box",
color: {
red: 10,
green: 200,
blue: 10
},
dimensions: {
x: 0.2,
y: 0.2,
z: 0.2
},
position: center
});
var blackListBox = Entities.addEntity({
type: "Box",
color: {
red: 100,
green: 10,
blue: 10
},
dimensions: {
x: 0.2,
y: 0.2,
z: 0.2
},
position: Vec3.sum(center, {
x: 0,
y: 0.3,
z: 0
})
});
function castRay(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
// In this example every entity will be pickable except the entities in the blacklist array
// the third argument is the whitelist array,and the fourth and final is the blacklist array
var pickResults = Entities.findRayIntersection(pickRay, true, [], [blackListBox]);
// With below example, only entities added to whitelist will be pickable
// var pickResults = Entities.findRayIntersection(pickRay, true, [whiteListBox], []);
if (pickResults.intersects) {
print("INTERSECTION!");
}
}
function cleanup() {
Entities.deleteEntity(whiteListBox);
Entities.deleteEntity(blackListBox);
}
Script.scriptEnding.connect(cleanup);
Controller.mousePressEvent.connect(castRay);

View file

@ -0,0 +1,32 @@
print("Launching web window");
webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false);
print("JS Side window: " + webWindow);
print("JS Side bridge: " + webWindow.eventBridge);
webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data);
});
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() {
webWindow.eventBridge.emitScriptEvent("JS Event sent");
var size = webWindow.size;
var position = webWindow.position;
print("Window url: " + webWindow.url)
print("Window visible: " + webWindow.visible)
print("Window size: " + size.x + "x" + size.y)
print("Window pos: " + position.x + "x" + position.y)
webWindow.setVisible(!webWindow.visible);
webWindow.setTitle(titles[titleIndex]);
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
titleIndex += 1;
titleIndex %= titles.length;
}, 2 * 1000);
Script.setTimeout(function() {
print("Closing script");
webWindow.close();
Script.stop();
}, 15 * 1000)

View file

@ -241,9 +241,9 @@
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
},
creatorSessionUUID: MyAvatar.sessionUUID
})
});
var makeArrowStick = function(entityA, entityB, collision) {

View file

@ -38,6 +38,14 @@ var pingPongGun = Entities.addEntity({
collisionSoundURL: COLLISION_SOUND_URL,
userData: JSON.stringify({
grabbableKey: {
spatialKey: {
relativePosition: {
x: -0.05,
y: 0,
z: 0.0
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, -90)
},
invertSolidWhileHeld: true
}
})

View file

@ -384,10 +384,10 @@ var CHECK_MARK_COLOR = {
y: newY
});
Overlays.editOverlay(this.checkMark, {
y: newY
y: newY + (0.25 * this.thumbSize)
});
Overlays.editOverlay(this.unCheckMark, {
y: newY
y: newY + (0.25 * this.thumbSize)
});
};
@ -399,10 +399,10 @@ var CHECK_MARK_COLOR = {
y: this.y
});
Overlays.editOverlay(this.checkMark, {
y: this.y
y: this.y + (0.25 * this.thumbSize)
});
Overlays.editOverlay(this.unCheckMark, {
y: this.y
y: this.y+ (0.25 * this.thumbSize)
});
};
@ -814,12 +814,14 @@ var CHECK_MARK_COLOR = {
});
};
CollapsablePanelItem.prototype.destroy = function() {
Overlays.deleteOverlay(this.title);
Overlays.deleteOverlay(this.thumb);
};
CollapsablePanelItem.prototype.editTitle = function(opts) {
Overlays.editOverlay(this.title, opts);
};
CollapsablePanelItem.prototype.hide = function() {
Overlays.editOverlay(this.title, {
@ -1531,4 +1533,4 @@ Controller.captureKeyEvents({
});
Controller.captureKeyEvents({
text: "right"
});
});

View file

@ -1,6 +1,7 @@
//
// SunLightExample.js
// examples
// renderEngineDebug.js
// examples/utilities/tools
//
// Sam Gateau
// Copyright 2015 High Fidelity, Inc.
//
@ -10,78 +11,93 @@
Script.include("cookies.js");
var MENU = "Developer>Render>Debug Deferred Buffer";
var ACTIONS = ["Off", "Diffuse", "Alpha", "Specular", "Roughness", "Normal", "Depth", "Lighting", "Custom"];
var SETTINGS_KEY = "EngineDebugScript.DebugMode";
Number.prototype.clamp = function(min, max) {
return Math.min(Math.max(this, min), max);
};
var panel = new Panel(10, 100);
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
this.subPanel = panel.newSubPanel(name);
function CounterWidget(parentPanel, name, counter) {
var subPanel = parentPanel.newSubPanel(name);
var widget = parentPanel.items[name];
widget.editTitle({ width: 270 });
this.subPanel.newSlider("Num Feed", 0, 1,
function(value) { },
feedGetter,
function(value) { return (value); });
this.subPanel.newSlider("Num Drawn", 0, 1,
function(value) { },
drawGetter,
function(value) { return (value); });
this.subPanel.newSlider("Max Drawn", -1, 1,
capSetter,
capGetter,
function(value) { return (value); });
subPanel.newSlider('Max Drawn', -1, 1,
function(value) { counter.maxDrawn = value; }, // setter
function() { return counter.maxDrawn; }, // getter
function(value) { return value; });
this.update = function () {
var numFeed = this.subPanel.get("Num Feed");
this.subPanel.set("Num Feed", numFeed);
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
var slider = subPanel.getWidget('Max Drawn');
var numMax = Math.max(numFeed, 1);
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
this.update = function () {
var numDrawn = counter.numDrawn; // avoid double polling
var numMax = Math.max(numDrawn, 1);
var title = [
' ' + name,
numDrawn + ' / ' + counter.numFeed
].join('\t');
widget.editTitle({ text: title });
slider.setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques",
function () { return Scene.getEngineNumFeedOpaqueItems(); },
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
);
var opaquesCounter = new CounterWidget(panel, "Opaques", Render.opaque);
var transparentsCounter = new CounterWidget(panel, "Transparents", Render.transparent);
var overlaysCounter = new CounterWidget(panel, "Overlays", Render.overlay3D);
var transparentsCounter = new CounterWidget(panel, "Transparents",
function () { return Scene.getEngineNumFeedTransparentItems(); },
function () { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
);
var resizing = false;
var previousMode = Settings.getValue(SETTINGS_KEY, -1);
Menu.addActionGroup(MENU, ACTIONS, ACTIONS[previousMode + 1]);
Render.deferredDebugMode = previousMode;
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 }; // Reset to default size
var overlaysCounter = new CounterWidget(panel, "Overlays",
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
);
function setEngineDeferredDebugSize(eventX) {
var scaledX = (2.0 * (eventX / Window.innerWidth) - 1.0).clamp(-1.0, 1.0);
Render.deferredDebugSize = { x: scaledX, y: -1.0, z: 1.0, w: 1.0 };
}
function shouldStartResizing(eventX) {
var x = Math.abs(eventX - Window.innerWidth * (1.0 + Render.deferredDebugSize.x) / 2.0);
var mode = Render.deferredDebugMode;
return mode !== -1 && x < 20;
}
function menuItemEvent(menuItem) {
var index = ACTIONS.indexOf(menuItem);
if (index >= 0) {
Render.deferredDebugMode = (index - 1);
}
}
// see libraries/render/src/render/Engine.h
var showDisplayStatusFlag = 1;
var showNetworkStatusFlag = 2;
panel.newCheckbox("Display status",
function(value) { Scene.setEngineDisplayItemStatus(value ?
Scene.doEngineDisplayItemStatus() | showDisplayStatusFlag :
Scene.doEngineDisplayItemStatus() & ~showDisplayStatusFlag); },
function() { return (Scene.doEngineDisplayItemStatus() & showDisplayStatusFlag) > 0; },
function(value) { Render.displayItemStatus = (value ?
Render.displayItemStatus | showDisplayStatusFlag :
Render.displayItemStatus & ~showDisplayStatusFlag); },
function() { return (Render.displayItemStatus & showDisplayStatusFlag) > 0; },
function(value) { return (value & showDisplayStatusFlag) > 0; }
);
panel.newCheckbox("Network/Physics status",
function(value) { Scene.setEngineDisplayItemStatus(value ?
Scene.doEngineDisplayItemStatus() | showNetworkStatusFlag :
Scene.doEngineDisplayItemStatus() & ~showNetworkStatusFlag); },
function() { return (Scene.doEngineDisplayItemStatus() & showNetworkStatusFlag) > 0; },
function(value) { Render.displayItemStatus = (value ?
Render.displayItemStatus | showNetworkStatusFlag :
Render.displayItemStatus & ~showNetworkStatusFlag); },
function() { return (Render.displayItemStatus & showNetworkStatusFlag) > 0; },
function(value) { return (value & showNetworkStatusFlag) > 0; }
);
panel.newSlider("Tone Mapping Exposure", -10, 10,
function (value) { Render.tone.exposure = value; },
function() { return Render.tone.exposure; },
function (value) { return (value); });
var tickTackPeriod = 500;
function updateCounters() {
@ -91,11 +107,51 @@ function updateCounters() {
}
Script.setInterval(updateCounters, tickTackPeriod);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function mouseMoveEvent(event) {
if (resizing) {
setEngineDeferredDebugSize(event.x);
} else {
panel.mouseMoveEvent(event);
}
}
function mousePressEvent(event) {
if (shouldStartResizing(event.x)) {
resizing = true;
} else {
panel.mousePressEvent(event);
}
}
function mouseReleaseEvent(event) {
if (resizing) {
resizing = false;
} else {
panel.mouseReleaseEvent(event);
}
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Menu.menuItemEvent.connect(menuItemEvent);
function scriptEnding() {
panel.destroy();
Menu.removeActionGroup(MENU);
// Reset
Settings.setValue(SETTINGS_KEY, Render.deferredDebugMode);
Render.deferredDebugMode = -1;
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 };
Render.opaque.maxDrawn = -1;
Render.transparent.maxDrawn = -1;
Render.overlay3D.maxDrawn = -1;
}
Script.scriptEnding.connect(scriptEnding);
// Collapse items
panel.mousePressEvent({ x: panel.x, y: panel.items["Overlays"].y});
panel.mousePressEvent({ x: panel.x, y: panel.items["Transparents"].y});
panel.mousePressEvent({ x: panel.x, y: panel.items["Opaques"].y});

View file

@ -0,0 +1,73 @@
//
// shooterPlatform.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This is the platform that spawns the bow and attaches it to the avatars hand,
// then de-rez the bow on leaving to prevent walking up to the targets with the bow.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
const GAME_CHANNEL = 'winterSmashUpGame';
const SCRIPT_URL = Script.resolvePath('../../toybox/bow/bow.js');
const MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow-deadly.fbx";
const COLLISION_HULL_URL = "https://hifi-public.s3.amazonaws.com/models/bow/new/bow_collision_hull.obj";
const RIGHT_HAND = 1;
const LEFT_HAND = 0;
var bowEntity = undefined;
_this.enterEntity = function(entityID) {
print('entered bow game entity');
// Triggers a recording on an assignment client:
Messages.sendMessage('PlayBackOnAssignment', 'BowShootingGameExplaination');
bowEntity = Entities.addEntity({
name: 'Hifi-Bow-For-Game',
type: 'Model',
modelURL: MODEL_URL,
position: MyAvatar.position,
dimensions: {x: 0.04, y: 1.3, z: 0.21},
collisionsWillMove: true,
gravity: {x: 0, y: 0, z: 0},
shapeType: 'compound',
compoundShapeURL: COLLISION_HULL_URL,
script: SCRIPT_URL,
userData: JSON.stringify({
grabbableKey: {
invertSolidWhileHeld: true,
spatialKey: {
leftRelativePosition: {
x: 0.05,
y: 0.06,
z: -0.05
},
rightRelativePosition: {
x: -0.05,
y: 0.06,
z: -0.05
},
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
}
}
})
});
Messages.sendMessage('Hifi-Hand-Grab', JSON.stringify({hand: 'left', entityID: bowEntity}));
};
_this.leaveEntity = function(entityID) {
if (bowEntity !== undefined) {
Entities.deleteEntity(bowEntity);
bowEntity = undefined;
}
};
});

View file

@ -0,0 +1,93 @@
//
// startTargetPractice.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This script starts the game, when the entity that contains the script gets shot.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
var waitForScriptLoad = false;
const MAX_GAME_TIME = 60; //seconds
const SCRIPT_URL = 'http://s3.amazonaws.com/hifi-public/scripts/winterSmashUp/targetPractice/targetPracticeGame.js';
const GAME_CHANNEL = 'winterSmashUpGame';
var isScriptRunning = function(script) {
script = script.toLowerCase().trim();
var runningScripts = ScriptDiscoveryService.getRunning();
for (i in runningScripts) {
if (runningScripts[i].url.toLowerCase().trim() == script) {
return true;
}
}
return false;
};
var sendStartSignal = function() {
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({
action: 'start',
gameEntityID: _this.entityID,
playerSessionUUID: MyAvatar.sessionUUID
}));
}
var startGame = function() {
//TODO: check here if someone is already playing this game instance by verifying the userData for playerSessionID
// and startTime with a maximum timeout of X seconds (30?)
if (!isScriptRunning(SCRIPT_URL)) {
// Loads the script for the player if this isn't yet loaded
Script.load(SCRIPT_URL);
waitForScriptLoad = true;
return;
}
sendStartSignal();
};
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == GAME_CHANNEL) {
var data = JSON.parse(message);
switch (data.action) {
case 'scriptLoaded':
if (waitForPing) {
sendStartSignal();
waitForPing = false;
}
break;
}
}
});
_this.preload = function(entityID) {
_this.entityID = entityID;
};
_this.collisionWithEntity = function(entityA, entityB, collisionInfo) {
if (entityA == _this.entityID) {
try {
var data = JSON.parse(Entities.getEntityProperties(entityB).userData);
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
print('attempting to startGame by collisionWithEntity.');
startGame();
}
} catch(e) {
}
}
};
_this.onShot = function(forceDirection) {
print('attempting to startGame by onShot.');
startGame();
};
Messages.subscribe(GAME_CHANNEL);
});

View file

@ -0,0 +1,227 @@
//
// targetPracticeGame.js
// examples/winterSmashUp/targetPractice
//
// Created by Thijs Wenker on 12/21/15.
// Copyright 2015 High Fidelity, Inc.
//
// The Winter Smash Up Target Practice Game using a bow.
// This script runs on the client side (it is loaded through the platform trigger entity)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const GAME_CHANNEL = 'winterSmashUpGame';
const SCORE_POST_URL = 'https://script.google.com/macros/s/AKfycbwZAMx6cMBx6-8NGEhR8ELUA-dldtpa_4P55z38Q4vYHW6kneg/exec';
const MODEL_URL = 'http://cdn.highfidelity.com/chris/production/winter/game/';
const MAX_GAME_TIME = 120; //seconds
const TARGET_CLOSE_OFFSET = 0.5;
const MILLISECS_IN_SEC = 1000;
const HIT_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Clay_Pigeon_02.L.wav';
const GRAVITY = -9.8;
const MESSAGE_WIDTH = 100;
const MESSAGE_HEIGHT = 50;
const TARGETS = [
{pitch: -1, yaw: -20, maxDistance: 17},
{pitch: -1, yaw: -15, maxDistance: 17},
{pitch: -1, yaw: -10, maxDistance: 17},
{pitch: -2, yaw: -5, maxDistance: 17},
{pitch: -1, yaw: 0, maxDistance: 17},
{pitch: 3, yaw: 10, maxDistance: 17},
{pitch: -1, yaw: 15, maxDistance: 17},
{pitch: -1, yaw: 20, maxDistance: 17},
{pitch: 2, yaw: 25, maxDistance: 17},
{pitch: 0, yaw: 30, maxDistance: 17}
];
var gameRunning = false;
var gameStartTime;
var gameEntityID;
var gameTimeoutTimer;
var targetEntities = [];
var hitSound = SoundCache.getSound(HIT_SOUND_URL);
var messageOverlay = undefined;
var messageExpire = 0;
var clearMessage = function() {
if (messageOverlay !== undefined) {
Overlays.deleteOverlay(messageOverlay);
messageOverlay = undefined;
}
};
var displayMessage = function(message, timeout) {
clearMessage();
messageExpire = Date.now() + timeout;
messageOverlay = Overlays.addOverlay('text', {
text: message,
x: (Window.innerWidth / 2) - (MESSAGE_WIDTH / 2),
y: (Window.innerHeight / 2) - (MESSAGE_HEIGHT / 2),
width: MESSAGE_WIDTH,
height: MESSAGE_HEIGHT,
alpha: 1,
backgroundAlpha: 0.0,
font: {size: 20}
});
};
var getStatsText = function() {
var timeLeft = MAX_GAME_TIME - ((Date.now() - gameStartTime) / MILLISECS_IN_SEC);
return 'Time remaining: ' + timeLeft.toFixed(1) + 's\nTargets hit: ' + (TARGETS.length - targetEntities.length) + '/' + TARGETS.length;
};
const timerOverlayWidth = 50;
var timerOverlay = Overlays.addOverlay('text', {
text: '',
x: Window.innerWidth / 2 - (timerOverlayWidth / 2),
y: 100,
width: timerOverlayWidth,
alpha: 1,
backgroundAlpha: 0.0,
visible: false,
font: {size: 20}
});
var cleanRemainingTargets = function() {
while (targetEntities.length > 0) {
Entities.deleteEntity(targetEntities.pop());
}
};
var createTarget = function(position, rotation, scale) {
var initialDimensions = {x: 1.8437, y: 0.1614, z: 1.8438};
var dimensions = Vec3.multiply(initialDimensions, scale);
return Entities.addEntity({
type: 'Model',
rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
lifetime: MAX_GAME_TIME,
modelURL: MODEL_URL + 'target.fbx',
shapeType: 'compound',
compoundShapeURL: MODEL_URL + 'targetCollision.obj',
dimensions: dimensions,
position: position
});
};
var createTargetInDirection = function(startPosition, startRotation, pitch, yaw, maxDistance, scale) {
var directionQuat = Quat.multiply(startRotation, Quat.fromPitchYawRollDegrees(pitch, yaw, 0.0));
var directionVec = Vec3.multiplyQbyV(directionQuat, Vec3.FRONT);
var intersection = Entities.findRayIntersection({direction: directionVec, origin: startPosition}, true);
var distance = maxDistance;
if (intersection.intersects && intersection.distance <= maxDistance) {
distance = intersection.distance - TARGET_CLOSE_OFFSET;
}
return createTarget(Vec3.sum(startPosition, Vec3.multiplyQbyV(directionQuat, Vec3.multiply(Vec3.FRONT, distance))), startRotation, scale);
};
var killTimer = function() {
if (gameTimeoutTimer !== undefined) {
Script.clearTimeout(gameTimeoutTimer);
gameTimeoutTimer = undefined;
}
};
var submitScore = function() {
gameRunning = false;
killTimer();
Overlays.editOverlay(timerOverlay, {visible: false});
if (!GlobalServices.username) {
displayMessage('Could not submit score, you are not logged in.', 5000);
return;
}
var timeItTook = Date.now() - gameStartTime;
var req = new XMLHttpRequest();
req.open('POST', SCORE_POST_URL, false);
req.send(JSON.stringify({
username: GlobalServices.username,
time: timeItTook / MILLISECS_IN_SEC
}));
displayMessage('Your score has been submitted!', 5000);
};
var onTargetHit = function(targetEntity, projectileEntity, collision) {
var targetIndex = targetEntities.indexOf(targetEntity);
if (targetIndex !== -1) {
try {
var data = JSON.parse(Entities.getEntityProperties(projectileEntity).userData);
if (data.creatorSessionUUID === MyAvatar.sessionUUID) {
this.audioInjector = Audio.playSound(hitSound, {
position: collision.contactPoint,
volume: 0.5
});
// Attach arrow to target for the nice effect
Entities.editEntity(projectileEntity, {
ignoreForCollisions: true,
parentID: targetEntity
});
Entities.editEntity(targetEntity, {
collisionsWillMove: true,
gravity: {x: 0, y: GRAVITY, z: 0},
velocity: {x: 0, y: -0.01, z: 0}
});
targetEntities.splice(targetIndex, 1);
if (targetEntities.length === 0) {
submitScore();
}
}
} catch(e) {
}
}
};
var startGame = function(entityID) {
cleanRemainingTargets();
killTimer();
gameEntityID = entityID;
targetEntities = [];
var parentEntity = Entities.getEntityProperties(gameEntityID);
for (var i in TARGETS) {
var target = TARGETS[i];
var targetEntity = createTargetInDirection(parentEntity.position, parentEntity.rotation, target.pitch, target.yaw, target.maxDistance, 0.67);
Script.addEventHandler(targetEntity, 'collisionWithEntity', onTargetHit);
targetEntities.push(targetEntity);
}
gameStartTime = Date.now();
gameTimeoutTimer = Script.setTimeout(function() {
cleanRemainingTargets();
Overlays.editOverlay(timerOverlay, {visible: false});
gameRunning = false;
displayMessage('Game timed out.', 5000);
}, MAX_GAME_TIME * MILLISECS_IN_SEC);
gameRunning = true;
Overlays.editOverlay(timerOverlay, {visible: true, text: getStatsText()});
displayMessage('Game started! GO GO GO!', 3000);
};
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == GAME_CHANNEL) {
var data = JSON.parse(message);
switch (data.action) {
case 'start':
if (data.playerSessionUUID === MyAvatar.sessionUUID) {
startGame(data.gameEntityID);
}
break;
}
}
});
Messages.subscribe(GAME_CHANNEL);
Messages.sendMessage(GAME_CHANNEL, JSON.stringify({action: 'scriptLoaded'}));
Script.update.connect(function(deltaTime) {
if (gameRunning) {
Overlays.editOverlay(timerOverlay, {text: getStatsText()});
}
if (messageOverlay !== undefined && Date.now() > messageExpire) {
clearMessage();
}
});
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(timerOverlay);
cleanRemainingTargets();
clearMessage();
});

View file

@ -45,7 +45,9 @@ else ()
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
endif ()
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
find_package(Qt5 COMPONENTS
Gui Multimedia Network OpenGL Qml Quick Script Svg
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
# grab the ui files in resources/ui
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
@ -175,9 +177,17 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
target_link_libraries(
${TARGET_NAME}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
)
# Issue causes build failure unless we add this directory.
# See https://bugreports.qt.io/browse/QTBUG-43351
if (WIN32)
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
endif()
# assume we are using a Qt build without bearer management
add_definitions(-DQT_NO_BEARERMANAGEMENT)
@ -209,5 +219,9 @@ else (APPLE)
endif()
endif (APPLE)
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml")
endif()
package_libraries_for_deployment()
consolidate_stack_components()

View file

@ -415,10 +415,25 @@
"states": [
{
"id": "idle",
"interpTarget": 15,
"interpDuration": 15,
"interpTarget": 10,
"interpDuration": 10,
"transitions": [
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingForward", "state": "idleToWalkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" }
]
},
{
"id": "idleToWalkFwd",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
@ -429,7 +444,7 @@
},
{
"id": "walkFwd",
"interpTarget": 6,
"interpTarget": 15,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
@ -638,6 +653,18 @@
}
]
},
{
"id": "idleToWalkFwd",
"type": "clip",
"data": {
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims/idle_to_walk.fbx",
"startFrame": 1.0,
"endFrame": 19.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
},
{
"id": "walkBwd",
"type": "blendLinearMove",

View file

@ -1,6 +1,6 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebKit 3.0
import QtWebEngine 1.1
import "controls"
import "styles"
@ -39,9 +39,10 @@ VrDialog {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: scrollView.top
anchors.bottom: webview.top
color: "white"
}
Row {
id: buttons
spacing: 4
@ -112,26 +113,22 @@ VrDialog {
}
}
ScrollView {
id: scrollView
WebEngineView {
id: webview
url: "http://highfidelity.com"
anchors.top: buttons.bottom
anchors.topMargin: 8
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
WebView {
id: webview
url: "http://highfidelity.com"
anchors.fill: parent
onLoadingChanged: {
if (loadRequest.status == WebView.LoadSucceededStarted) {
addressBar.text = loadRequest.url
}
}
onIconChanged: {
barIcon.source = icon
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
addressBar.text = loadRequest.url
}
}
onIconChanged: {
console.log("New icon: " + icon)
}
}
} // item
@ -146,5 +143,4 @@ VrDialog {
break;
}
}
} // dialog

View file

@ -2,7 +2,7 @@ import Hifi 1.0 as Hifi
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0
import QtWebEngine 1.1
import "controls"
VrDialog {
@ -18,15 +18,11 @@ VrDialog {
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
ScrollView {
WebEngineView {
id: webview
objectName: "WebView"
anchors.fill: parent
WebView {
objectName: "WebView"
id: webview
url: infoView.url
anchors.fill: parent
}
}
url: infoView.url
}
}
}

View file

@ -2,7 +2,7 @@ import Hifi 1.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0
import QtWebEngine 1.1
import "controls"
VrDialog {
@ -24,27 +24,22 @@ VrDialog {
anchors.margins: parent.margins
anchors.topMargin: parent.topMargin
ScrollView {
WebEngineView {
objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
anchors.fill: parent
WebView {
objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
anchors.fill: parent
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
return;
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
return;
}
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return;
}
}
}
}
}
}

View file

@ -0,0 +1,73 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import QtWebChannel 1.0
import QtWebSockets 1.0
import "controls"
import "styles"
VrDialog {
id: root
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
backgroundColor: "#7f000000"
property url source: "about:blank"
signal navigating(string url)
function stop() {
webview.stop();
}
Component.onCompleted: {
enabled = true
console.log("Web Window Created " + root);
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
webview.loadingChanged.connect(handleWebviewLoading)
}
function handleWebviewLoading(loadRequest) {
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var newUrl = loadRequest.url.toString();
root.navigating(newUrl)
}
}
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
WebEngineView {
id: webview
url: root.source
anchors.fill: parent
onUrlChanged: {
var currentUrl = url.toString();
var newUrl = urlFixer.fixupUrl(currentUrl);
if (newUrl != currentUrl) {
url = newUrl;
}
}
profile: WebEngineProfile {
id: webviewProfile
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
storageName: "qmlWebEngine"
}
}
} // item
} // dialog

View file

@ -4,116 +4,7 @@ import Hifi 1.0
// Currently for testing a pure QML replacement menu
Item {
Item {
objectName: "AllActions"
Action {
id: aboutApp
objectName: "HifiAction_" + MenuConstants.AboutApp
text: qsTr("About Interface")
}
//
// File Menu
//
Action {
id: login
objectName: "HifiAction_" + MenuConstants.Login
text: qsTr("Login")
}
Action {
id: quit
objectName: "HifiAction_" + MenuConstants.Quit
text: qsTr("Quit")
//shortcut: StandardKey.Quit
shortcut: "Ctrl+Q"
}
//
// Edit menu
//
Action {
id: undo
text: "Undo"
shortcut: StandardKey.Undo
}
Action {
id: redo
text: "Redo"
shortcut: StandardKey.Redo
}
Action {
id: animations
objectName: "HifiAction_" + MenuConstants.Animations
text: qsTr("Animations...")
}
Action {
id: attachments
text: qsTr("Attachments...")
}
Action {
id: explode
text: qsTr("Explode on quit")
checkable: true
checked: true
}
Action {
id: freeze
text: qsTr("Freeze on quit")
checkable: true
checked: false
}
ExclusiveGroup {
Action {
id: visibleToEveryone
objectName: "HifiAction_" + MenuConstants.VisibleToEveryone
text: qsTr("Everyone")
checkable: true
checked: true
}
Action {
id: visibleToFriends
objectName: "HifiAction_" + MenuConstants.VisibleToFriends
text: qsTr("Friends")
checkable: true
}
Action {
id: visibleToNoOne
objectName: "HifiAction_" + MenuConstants.VisibleToNoOne
text: qsTr("No one")
checkable: true
}
}
}
Menu {
objectName: "rootMenu";
Menu {
title: "File"
MenuItem { action: login }
MenuItem { action: explode }
MenuItem { action: freeze }
MenuItem { action: quit }
}
Menu {
title: "Tools"
Menu {
title: "I Am Visible To"
MenuItem { action: visibleToEveryone }
MenuItem { action: visibleToFriends }
MenuItem { action: visibleToNoOne }
}
MenuItem { action: animations }
}
Menu {
title: "Long menu name top menu"
MenuItem { action: aboutApp }
}
Menu {
title: "Help"
MenuItem { action: aboutApp }
}
}
}

View file

@ -1,10 +1,10 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebKit 3.0
import QtWebEngine 1.1
WebView {
WebEngineView {
id: root
objectName: "webview"
anchors.fill: parent
objectName: "webview"
url: "about:blank"
}

View file

@ -89,6 +89,7 @@
#include <RenderableWebEntityItem.h>
#include <RenderDeferredTask.h>
#include <ResourceCache.h>
#include <RenderScriptingInterface.h>
#include <SceneScriptingInterface.h>
#include <RecordingScriptingInterface.h>
#include <ScriptCache.h>
@ -101,6 +102,7 @@
#include <VrMenu.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <QmlWebWindowClass.h>
#include "AnimDebugDraw.h"
#include "AudioClient.h"
@ -341,6 +343,7 @@ bool setupEssentials(int& argc, char** argv) {
#endif
DependencyManager::set<DiscoverabilityManager>();
DependencyManager::set<SceneScriptingInterface>();
DependencyManager::set<RenderScriptingInterface>();
DependencyManager::set<OffscreenUi>();
DependencyManager::set<AutoUpdater>();
DependencyManager::set<PathUtils>();
@ -362,6 +365,17 @@ Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 };
PluginContainer* _pluginContainer;
// FIXME hack access to the internal share context for the Chromium helper
// Normally we'd want to use QWebEngine::initialize(), but we can't because
// our primary context is a QGLWidget, which can't easily be initialized to share
// from a QOpenGLContext.
//
// So instead we create a new offscreen context to share with the QGLWidget,
// and manually set THAT to be the shared context for the Chromium helper
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
@ -623,6 +637,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_glWidget->makeCurrent();
_glWidget->initializeGL();
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->create(_glWidget->context()->contextHandle());
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->create(_glWidget->context()->contextHandle());
_offscreenContext->makeCurrent();
@ -686,13 +705,37 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
VrMenu::toggle(); // show context menu even on non-stereo displays
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
auto globalPos = QCursor::pos();
globalPos.setX(globalPos.x() + state);
QCursor::setPos(globalPos);
auto oldPos = QCursor::pos();
auto newPos = oldPos;
newPos.setX(oldPos.x() + state);
QCursor::setPos(newPos);
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
glm::vec2 newPosG = { newPos.x(), newPos.y() };
auto distance = glm::distance(oldPosG, newPosG);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Action::RETICLE_X... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
}
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
auto globalPos = QCursor::pos();
globalPos.setY(globalPos.y() + state);
QCursor::setPos(globalPos);
auto oldPos = QCursor::pos();
auto newPos = oldPos;
newPos.setY(oldPos.y() + state);
QCursor::setPos(newPos);
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
glm::vec2 newPosG = { newPos.x(), newPos.y() };
auto distance = glm::distance(oldPosG, newPosG);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Action::RETICLE_Y... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
}
}
}
});
@ -706,9 +749,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_applicationStateDevice->addInputVariant(QString("ComfortMode"), controller::StateController::ReadLambda([]() -> float {
return (float)Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode);
}));
_applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float {
return (float)qApp->getMyAvatar()->getCharacterController()->onGround();
}));
_applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float {
return (float)qApp->getMyAvatar()->getCharacterController()->onGround();
}));
userInputMapper->registerDevice(_applicationStateDevice);
@ -1161,26 +1204,15 @@ void Application::paintGL() {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
PerformanceTimer perfTimer("Mirror");
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebufferDepthColor();
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer();
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
renderArgs._blitFramebuffer = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
renderRearViewMirror(&renderArgs, _mirrorViewRect);
renderArgs._blitFramebuffer.reset();
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
{
float ratio = ((float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale());
// Flip the src and destination rect horizontally to do the mirror
auto mirrorRect = glm::ivec4(0, 0, _mirrorViewRect.width() * ratio, _mirrorViewRect.height() * ratio);
auto mirrorRectDest = glm::ivec4(mirrorRect.z, mirrorRect.y, mirrorRect.x, mirrorRect.w);
auto selfieFbo = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
batch.setFramebuffer(selfieFbo);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f));
batch.blit(primaryFbo, mirrorRect, selfieFbo, mirrorRectDest);
batch.setFramebuffer(nullptr);
});
}
}
{
@ -1260,15 +1292,14 @@ void Application::paintGL() {
hmdOffset.x = -hmdOffset.x;
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0)
+ mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
+ mirrorBodyOrientation * hmdOffset);
} else {
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
+ glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
@ -1352,65 +1383,11 @@ void Application::paintGL() {
renderArgs._context->setStereoProjections(eyeProjections);
renderArgs._context->setStereoViews(eyeOffsets);
}
renderArgs._blitFramebuffer = finalFramebuffer;
displaySide(&renderArgs, _myCamera);
renderArgs._blitFramebuffer.reset();
renderArgs._context->enableStereo(false);
// Blit primary to final FBO
auto primaryFbo = framebufferCache->getPrimaryFramebuffer();
if (renderArgs._renderMode == RenderArgs::MIRROR_RENDER_MODE) {
if (displayPlugin->isStereo()) {
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
gpu::Vec4i srcRectLeft;
srcRectLeft.z = size.width() / 2;
srcRectLeft.w = size.height();
gpu::Vec4i srcRectRight;
srcRectRight.x = size.width() / 2;
srcRectRight.z = size.width();
srcRectRight.w = size.height();
gpu::Vec4i destRectLeft;
destRectLeft.x = srcRectLeft.z;
destRectLeft.z = srcRectLeft.x;
destRectLeft.y = srcRectLeft.y;
destRectLeft.w = srcRectLeft.w;
gpu::Vec4i destRectRight;
destRectRight.x = srcRectRight.z;
destRectRight.z = srcRectRight.x;
destRectRight.y = srcRectRight.y;
destRectRight.w = srcRectRight.w;
batch.setFramebuffer(finalFramebuffer);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 1.0f, 0.0f));
// BLit left to right and right to left in stereo
batch.blit(primaryFbo, srcRectRight, finalFramebuffer, destRectLeft);
batch.blit(primaryFbo, srcRectLeft, finalFramebuffer, destRectRight);
});
} else {
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
gpu::Vec4i srcRect;
srcRect.z = size.width();
srcRect.w = size.height();
gpu::Vec4i destRect;
destRect.x = size.width();
destRect.y = 0;
destRect.z = 0;
destRect.w = size.height();
batch.setFramebuffer(finalFramebuffer);
batch.blit(primaryFbo, srcRect, finalFramebuffer, destRect);
});
}
} else {
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
gpu::Vec4i rect;
rect.z = size.width();
rect.w = size.height();
batch.setFramebuffer(finalFramebuffer);
batch.blit(primaryFbo, rect, finalFramebuffer, rect);
});
}
}
// Overlay Composition, needs to occur after screen space effects have completed
@ -1418,7 +1395,9 @@ void Application::paintGL() {
{
PROFILE_RANGE(__FUNCTION__ "/compositor");
PerformanceTimer perfTimer("compositor");
auto primaryFbo = finalFramebuffer;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo));
if (displayPlugin->isStereo()) {
QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height()));
@ -1443,7 +1422,9 @@ void Application::paintGL() {
{
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
auto finalTexturePointer = finalFramebuffer->getRenderBuffer(0);
GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer);
Q_ASSERT(0 != finalTexture);
@ -2571,7 +2552,7 @@ void Application::init() {
_environment.init();
DependencyManager::get<DeferredLightingEffect>()->init(this);
DependencyManager::get<DeferredLightingEffect>()->init();
DependencyManager::get<AvatarManager>()->init();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
@ -2989,6 +2970,9 @@ void Application::update(float deltaTime) {
_physicsEngine->changeObjects(motionStates);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->forEachAction([&](EntityActionPointer action) {
action->prepareForPhysicsSimulation();
});
getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
@ -3401,7 +3385,7 @@ QImage Application::renderAvatarBillboard(RenderArgs* renderArgs) {
renderArgs->_renderMode = RenderArgs::DEFAULT_RENDER_MODE;
renderRearViewMirror(renderArgs, QRect(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebufferDepthColor();
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer();
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
renderArgs->_context->downloadFramebuffer(primaryFbo, glm::ivec4(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE), image);
@ -3671,34 +3655,19 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
// For now every frame pass the renderContext
{
PerformanceTimer perfTimer("EngineRun");
render::RenderContext renderContext;
auto sceneInterface = DependencyManager::get<SceneScriptingInterface>();
renderContext._cullOpaque = sceneInterface->doEngineCullOpaque();
renderContext._sortOpaque = sceneInterface->doEngineSortOpaque();
renderContext._renderOpaque = sceneInterface->doEngineRenderOpaque();
renderContext._cullTransparent = sceneInterface->doEngineCullTransparent();
renderContext._sortTransparent = sceneInterface->doEngineSortTransparent();
renderContext._renderTransparent = sceneInterface->doEngineRenderTransparent();
renderContext._maxDrawnOpaqueItems = sceneInterface->getEngineMaxDrawnOpaqueItems();
renderContext._maxDrawnTransparentItems = sceneInterface->getEngineMaxDrawnTransparentItems();
renderContext._maxDrawnOverlay3DItems = sceneInterface->getEngineMaxDrawnOverlay3DItems();
renderContext._drawItemStatus = sceneInterface->doEngineDisplayItemStatus();
if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned)) {
renderContext._drawItemStatus |= render::showNetworkStatusFlag;
}
renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect();
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
renderContext._fxaaStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
auto renderInterface = DependencyManager::get<RenderScriptingInterface>();
auto renderContext = renderInterface->getRenderContext();
renderArgs->_shouldRender = LODManager::shouldRender;
renderContext.args = renderArgs;
renderArgs->_viewFrustum = getDisplayViewFrustum();
renderContext.setArgs(renderArgs);
bool occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
bool antialiasingStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
bool showOwnedStatus = Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowOwned);
renderContext.setOptions(occlusionStatus, antialiasingStatus, showOwnedStatus);
_renderEngine->setRenderContext(renderContext);
// Before the deferred pass, let's try to use the render engine
@ -3706,15 +3675,8 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
_renderEngine->run();
myAvatar->endRenderRun();
auto engineRC = _renderEngine->getRenderContext();
sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems);
sceneInterface->setEngineDrawnOpaqueItems(engineRC->_numDrawnOpaqueItems);
sceneInterface->setEngineFeedTransparentItems(engineRC->_numFeedTransparentItems);
sceneInterface->setEngineDrawnTransparentItems(engineRC->_numDrawnTransparentItems);
sceneInterface->setEngineFeedOverlay3DItems(engineRC->_numFeedOverlay3DItems);
sceneInterface->setEngineDrawnOverlay3DItems(engineRC->_numDrawnOverlay3DItems);
auto engineContext = _renderEngine->getRenderContext();
renderInterface->setItemCounts(engineContext->getItemsConfig());
}
activeRenderingThread = nullptr;
@ -4140,6 +4102,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
LocationScriptingInterface::locationSetter);
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
@ -4168,11 +4131,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
scriptEngine->registerGlobalObject("Render", DependencyManager::get<RenderScriptingInterface>().data());
scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget());
}
bool Application::canAcceptURL(const QString& urlString) {
bool Application::canAcceptURL(const QString& urlString) const {
QUrl url(urlString);
if (urlString.startsWith(HIFI_URL_SCHEME)) {
return true;

View file

@ -38,6 +38,7 @@
#include <SimpleMovingAverage.h>
#include <StDev.h>
#include <ViewFrustum.h>
#include <AbstractUriHandler.h>
#include "avatar/AvatarUpdate.h"
#include "avatar/MyAvatar.h"
@ -88,7 +89,7 @@ class Application;
#endif
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface {
class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, public AbstractUriHandler {
Q_OBJECT
// TODO? Get rid of those
@ -219,8 +220,8 @@ public:
QString getScriptsLocation();
void setScriptsLocation(const QString& scriptsLocation);
bool canAcceptURL(const QString& url);
bool acceptURL(const QString& url, bool defaultUpload = false);
virtual bool canAcceptURL(const QString& url) const override;
virtual bool acceptURL(const QString& url, bool defaultUpload = false) override;
void setMaxOctreePacketsPerSecond(int maxOctreePPS);
int getMaxOctreePacketsPerSecond();

View file

@ -1085,6 +1085,26 @@ void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
QMenuBar::repaint();
}
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected) {
auto menu = addMenu(groupName);
QActionGroup* actionGroup = new QActionGroup(menu);
actionGroup->setExclusive(true);
auto menuScriptingInterface = MenuScriptingInterface::getInstance();
for (auto action : actionList) {
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected,
menuScriptingInterface,
SLOT(menuItemTriggered()));
actionGroup->addAction(item);
}
QMenuBar::repaint();
}
void Menu::removeActionGroup(const QString& groupName) {
removeMenu(groupName);
}
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {

View file

@ -105,6 +105,8 @@ public slots:
void addMenuItem(const MenuItemProperties& properties);
void removeMenuItem(const QString& menuName, const QString& menuitem);
bool menuItemExists(const QString& menuName, const QString& menuitem);
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString());
void removeActionGroup(const QString& groupName);
bool isOptionChecked(const QString& menuOption) const;
void setIsOptionChecked(const QString& menuOption, bool isChecked);

View file

@ -99,7 +99,8 @@ Avatar::Avatar(RigPointer rig) :
// we may have been created in the network thread, but we live in the main thread
moveToThread(qApp->thread());
setAvatarScale(1.0f);
setScale(glm::vec3(1.0f)); // avatar scale is uniform
// give the pointer to our head to inherited _headData variable from AvatarData
_headData = static_cast<HeadData*>(new Head(this));
_handData = static_cast<HandData*>(new Hand(this));
@ -143,15 +144,35 @@ AABox Avatar::getBounds() const {
float Avatar::getLODDistance() const {
return DependencyManager::get<LODManager>()->getAvatarLODDistanceMultiplier() *
glm::distance(qApp->getCamera()->getPosition(), getPosition()) / getAvatarScale();
glm::distance(qApp->getCamera()->getPosition(), getPosition()) / getUniformScale();
}
void Avatar::animateScaleChanges(float deltaTime) {
float currentScale = getUniformScale();
if (currentScale != _targetScale) {
// use exponential decay toward _targetScale
const float SCALE_ANIMATION_TIMESCALE = 0.5f;
float blendFactor = glm::clamp(deltaTime / SCALE_ANIMATION_TIMESCALE, 0.0f, 1.0f);
float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * _targetScale;
// snap to the end when we get close enough
const float MIN_RELATIVE_SCALE_ERROR = 0.03f;
if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) {
animatedScale = _targetScale;
}
setScale(glm::vec3(animatedScale)); // avatar scale is uniform
rebuildCollisionShape();
}
}
void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
if (getAvatarScale() != _targetScale) {
setAvatarScale(_targetScale);
if (!isDead() && !_motionState) {
DependencyManager::get<AvatarManager>()->addAvatarToSimulation(this);
}
animateScaleChanges(deltaTime);
// update the billboard render flag
const float BILLBOARD_HYSTERESIS_PROPORTION = 0.1f;
@ -164,7 +185,7 @@ void Avatar::simulate(float deltaTime) {
_shouldRenderBillboard = true;
qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance();
}
const bool isControllerLogging = DependencyManager::get<AvatarManager>()->getRenderDistanceControllerIsLogging();
float renderDistance = DependencyManager::get<AvatarManager>()->getRenderDistance();
const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION;
@ -210,7 +231,7 @@ void Avatar::simulate(float deltaTime) {
_skeletonModel.getHeadPosition(headPosition);
Head* head = getHead();
head->setPosition(headPosition);
head->setScale(getAvatarScale());
head->setScale(getUniformScale());
head->simulate(deltaTime, false, _shouldRenderBillboard);
}
}
@ -238,12 +259,12 @@ void Avatar::simulate(float deltaTime) {
measureMotionDerivatives(deltaTime);
}
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) {
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
const float HEAD_SPHERE_RADIUS = 0.1f;
glm::vec3 theirLookAt = dynamic_pointer_cast<Avatar>(avatar)->getHead()->getLookAtPosition();
glm::vec3 myEyePosition = getHead()->getEyePosition();
return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getAvatarScale());
return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getUniformScale());
}
void Avatar::slamPosition(const glm::vec3& newPosition) {
@ -423,7 +444,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
const float BASE_LIGHT_DISTANCE = 2.0f;
const float LIGHT_EXPONENT = 1.0f;
const float LIGHT_CUTOFF = glm::radians(80.0f);
float distance = BASE_LIGHT_DISTANCE * getAvatarScale();
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
glm::vec3 position = glm::mix(_skeletonModel.getTranslation(), getHead()->getFaceModel().getTranslation(), 0.9f);
glm::quat orientation = getOrientation();
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
@ -436,7 +457,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f);
const float BOUNDING_SHAPE_ALPHA = 0.7f;
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA);
}
// If this is the avatar being looked at, render a little ball above their head
@ -479,7 +501,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
}
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT),
Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT),
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
position = getHead()->getRightEyePosition();
@ -489,7 +511,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
eyeDiameter = DEFAULT_EYE_DIAMETER;
}
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT),
Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT),
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
}
@ -590,9 +612,9 @@ void Avatar::simulateAttachments(float deltaTime) {
glm::quat jointRotation;
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * getAvatarScale());
model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale());
model->setRotation(jointRotation * attachment.rotation);
model->setScaleToFit(true, getAvatarScale() * attachment.scale, true); // hack to force rescale
model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale
model->setSnapModelToCenter(false); // hack to force resnap
model->setSnapModelToCenter(true);
model->simulate(deltaTime);
@ -647,7 +669,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) {
}
float Avatar::getBillboardSize() const {
return getAvatarScale() * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
return getUniformScale() * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
}
#ifdef DEBUG
@ -796,7 +818,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
}
void Avatar::setSkeletonOffset(const glm::vec3& offset) {
const float MAX_OFFSET_LENGTH = getAvatarScale() * 0.5f;
const float MAX_OFFSET_LENGTH = getUniformScale() * 0.5f;
float offsetLength = glm::length(offset);
if (offsetLength > MAX_OFFSET_LENGTH) {
_skeletonOffset = (MAX_OFFSET_LENGTH / offsetLength) * offset;
@ -905,7 +927,7 @@ glm::vec3 Avatar::getJointPosition(const QString& name) const {
void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
//Scale a world space vector as if it was relative to the position
positionToScale = getPosition() + getAvatarScale() * (positionToScale - getPosition());
positionToScale = getPosition() + getUniformScale() * (positionToScale - getPosition());
}
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
@ -945,7 +967,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
for (int i = 0; i < attachmentData.size(); i++) {
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
_attachmentModels[i]->setSnapModelToCenter(true);
_attachmentModels[i]->setScaleToFit(true, getAvatarScale() * _attachmentData.at(i).scale);
_attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale);
}
}
@ -1033,15 +1055,6 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g
}
}
void Avatar::setAvatarScale(float scale) {
if (_targetScale * (1.0f - RESCALING_TOLERANCE) < scale &&
scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) {
setScale(glm::vec3(_targetScale));
} else {
setScale(glm::vec3(scale));
}
}
float Avatar::getSkeletonHeight() const {
Extents extents = _skeletonModel.getBindExtents();
return extents.maximum.y - extents.minimum.y;
@ -1053,7 +1066,7 @@ float Avatar::getHeadHeight() const {
// HACK: We have a really odd case when fading out for some models where this value explodes
float result = extents.maximum.y - extents.minimum.y;
if (result >= 0.0f && result < 100.0f * getAvatarScale() ) {
if (result >= 0.0f && result < 100.0f * getUniformScale() ) {
return result;
}
}
@ -1096,13 +1109,21 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
// virtual
void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
shapeInfo.setCapsuleY(_skeletonModel.getBoundingCapsuleRadius(), 0.5f * _skeletonModel.getBoundingCapsuleHeight());
shapeInfo.setOffset(_skeletonModel.getBoundingCapsuleOffset());
float uniformScale = getUniformScale();
shapeInfo.setCapsuleY(uniformScale * _skeletonModel.getBoundingCapsuleRadius(),
0.5f * uniformScale * _skeletonModel.getBoundingCapsuleHeight());
shapeInfo.setOffset(uniformScale * _skeletonModel.getBoundingCapsuleOffset());
}
void Avatar::setMotionState(AvatarMotionState* motionState) {
_motionState = motionState;
}
// virtual
void Avatar::rebuildSkeletonBody() {
DependencyManager::get<AvatarManager>()->updateAvatarPhysicsShape(this);
void Avatar::rebuildCollisionShape() {
if (_motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
}
}
glm::vec3 Avatar::getLeftPalmPosition() {

View file

@ -36,7 +36,6 @@ namespace render {
static const float SCALING_RATIO = .05f;
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
static const float RESCALING_TOLERANCE = .02f;
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
static const float BILLBOARD_DISTANCE = 5.56f; // meters
@ -89,7 +88,7 @@ public:
SkeletonModel& getSkeletonModel() { return _skeletonModel; }
const SkeletonModel& getSkeletonModel() const { return _skeletonModel; }
glm::vec3 getChestPosition() const;
float getAvatarScale() const { return getScale().y; }
float getUniformScale() const { return getScale().y; }
const Head* getHead() const { return static_cast<const Head*>(_headData); }
Head* getHead() { return static_cast<Head*>(_headData); }
Hand* getHand() { return static_cast<Hand*>(_handData); }
@ -154,11 +153,10 @@ public:
// (otherwise floating point error will cause problems at large positions).
void applyPositionDelta(const glm::vec3& delta);
virtual void rebuildSkeletonBody();
virtual void rebuildCollisionShape();
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
void setMotionState(AvatarMotionState* motionState) { _motionState = motionState; }
AvatarMotionState* getMotionState() { return _motionState; }
virtual void setPosition(const glm::vec3& position) override;
@ -173,6 +171,10 @@ public slots:
glm::quat getRightPalmRotation();
protected:
friend class AvatarManager;
void setMotionState(AvatarMotionState* motionState);
SkeletonModel _skeletonModel;
glm::vec3 _skeletonOffset;
QVector<Model*> _attachmentModels;
@ -199,14 +201,15 @@ protected:
float _stringLength;
bool _moving; ///< set when position is changing
bool isLookingAtMe(AvatarSharedPointer avatar);
// protected methods...
bool isLookingAtMe(AvatarSharedPointer avatar) const;
virtual void animateScaleChanges(float deltaTime);
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setAvatarScale(float scale);
void measureMotionDerivatives(float deltaTime);
float getSkeletonHeight() const;

View file

@ -14,6 +14,7 @@
#include <QVariantGLM.h>
#include "avatar/AvatarManager.h"
#include "CharacterController.h"
const uint16_t AvatarActionHold::holdVersion = 1;
@ -32,6 +33,64 @@ AvatarActionHold::~AvatarActionHold() {
#endif
}
bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr;
if (!controller) {
qDebug() << "AvatarActionHold::getAvatarRigidBodyLocation failed to get character controller";
return false;
}
controller->getRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
return true;
}
void AvatarActionHold::prepareForPhysicsSimulation() {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
if (!holdingAvatar || !holdingAvatar->isMyAvatar()) {
return;
}
withWriteLock([&]{
if (_ignoreIK) {
return;
}
glm::vec3 palmPosition;
glm::quat palmRotation;
if (_hand == "right") {
palmPosition = holdingAvatar->getRightPalmPosition();
palmRotation = holdingAvatar->getRightPalmRotation();
} else {
palmPosition = holdingAvatar->getLeftPalmPosition();
palmRotation = holdingAvatar->getLeftPalmRotation();
}
glm::vec3 avatarRigidBodyPosition;
glm::quat avatarRigidBodyRotation;
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
// determine the difference in translation and rotation between the avatar's
// rigid body and the palm position. The avatar's rigid body will be moved by bullet
// between this call and the call to getTarget, below. A call to get*PalmPosition in
// getTarget would get the palm position of the previous location of the avatar (because
// bullet has moved the av's rigid body but the rigid body's location has not yet been
// copied out into the Avatar class.
//glm::quat avatarRotationInverse = glm::inverse(avatarRigidBodyRotation);
// the offset should be in the frame of the avatar, but something about the order
// things are updated makes this wrong:
// _palmOffsetFromRigidBody = avatarRotationInverse * (palmPosition - avatarRigidBodyPosition);
// I'll leave it here as a comment in case avatar handling changes.
_palmOffsetFromRigidBody = palmPosition - avatarRigidBodyPosition;
// rotation should also be needed, but again, the order of updates makes this unneeded. leaving
// code here for future reference.
// _palmRotationFromRigidBody = avatarRotationInverse * palmRotation;
});
}
std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
@ -40,11 +99,11 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::ve
return holdingAvatar;
}
withTryReadLock([&]{
withReadLock([&]{
bool isRightHand = (_hand == "right");
glm::vec3 palmPosition { Vectors::ZERO };
glm::quat palmRotation { Quaternions::IDENTITY };
if (_ignoreIK && holdingAvatar->isMyAvatar()) {
// We cannot ignore other avatars IK and this is not the point of this option
// This is meant to make the grabbing behavior more reactive.
@ -55,6 +114,31 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::ve
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition();
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation();
}
} else if (holdingAvatar->isMyAvatar()) {
glm::vec3 avatarRigidBodyPosition;
glm::quat avatarRigidBodyRotation;
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
// the offset and rotation between the avatar's rigid body and the palm were determined earlier
// in prepareForPhysicsSimulation. At this point, the avatar's rigid body has been moved by bullet
// and the data in the Avatar class is stale. This means that the result of get*PalmPosition will
// be stale. Instead, determine the current palm position with the current avatar's rigid body
// location and the saved offsets.
// this line is more correct but breaks for the current way avatar data is updated.
// palmPosition = avatarRigidBodyPosition + avatarRigidBodyRotation * _palmOffsetFromRigidBody;
// instead, use this for now:
palmPosition = avatarRigidBodyPosition + _palmOffsetFromRigidBody;
// the item jitters the least by getting the rotation based on the opinion of Avatar.h rather
// than that of the rigid body. leaving this next line here for future reference:
// palmRotation = avatarRigidBodyRotation * _palmRotationFromRigidBody;
if (isRightHand) {
palmRotation = holdingAvatar->getRightPalmRotation();
} else {
palmRotation = holdingAvatar->getLeftPalmRotation();
}
} else {
if (isRightHand) {
palmPosition = holdingAvatar->getRightPalmPosition();
@ -103,21 +187,19 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
if (valid && holdCount > 0) {
position /= holdCount;
bool gotLock = withTryWriteLock([&]{
withWriteLock([&]{
_positionalTarget = position;
_rotationalTarget = rotation;
_positionalTargetSet = true;
_rotationalTargetSet = true;
_active = true;
});
if (gotLock) {
if (_kinematic) {
doKinematicUpdate(deltaTimeStep);
} else {
activateBody();
forceBodyNonStatic();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
if (_kinematic) {
doKinematicUpdate(deltaTimeStep);
} else {
activateBody();
forceBodyNonStatic();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
}
}

View file

@ -25,18 +25,21 @@ public:
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AvatarActionHold();
virtual bool updateArguments(QVariantMap arguments);
virtual QVariantMap getArguments();
virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments() override;
virtual void updateActionWorker(float deltaTimeStep);
virtual void updateActionWorker(float deltaTimeStep) override;
QByteArray serialize() const;
virtual void deserialize(QByteArray serializedArguments);
virtual void deserialize(QByteArray serializedArguments) override;
virtual bool shouldSuppressLocationEdits() { return _active && !_ownerEntity.expired(); }
virtual bool shouldSuppressLocationEdits() override { return _active && !_ownerEntity.expired(); }
bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position);
virtual void prepareForPhysicsSimulation() override;
private:
void doKinematicUpdate(float deltaTimeStep);
@ -56,6 +59,10 @@ private:
float _previousDeltaTimeStep = 0.0f;
glm::vec3 _previousPositionalDelta;
glm::vec3 _palmOffsetFromRigidBody;
// leaving this here for future refernece.
// glm::quat _palmRotationFromRigidBody;
};
#endif // hifi_AvatarActionHold_h

View file

@ -203,7 +203,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
while (fadingIterator != _avatarFades.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*fadingIterator);
avatar->startUpdate();
avatar->setTargetScale(avatar->getAvatarScale() * SHRINK_RATE);
avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE);
if (avatar->getTargetScale() <= MIN_FADE_SCALE) {
avatar->removeFromScene(*fadingIterator, scene, pendingChanges);
fadingIterator = _avatarFades.erase(fadingIterator);
@ -223,14 +223,14 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() {
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
auto rawRenderableAvatar = std::static_pointer_cast<Avatar>(newAvatar);
render::ScenePointer scene = qApp->getMain3DScene();
render::PendingChanges pendingChanges;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
rawRenderableAvatar->addToScene(rawRenderableAvatar, scene, pendingChanges);
}
scene->enqueuePendingChanges(pendingChanges);
return newAvatar;
}
@ -251,7 +251,7 @@ void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) {
// virtual
void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
handleRemovedAvatar(removedAvatar);
@ -260,7 +260,8 @@ void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
AvatarHashMap::handleRemovedAvatar(removedAvatar);
removedAvatar->die();
removeAvatarMotionState(removedAvatar);
_avatarFades.push_back(removedAvatar);
}
@ -268,7 +269,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
void AvatarManager::clearOtherAvatars() {
// clear any avatars that came from an avatar-mixer
QWriteLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
@ -278,7 +279,7 @@ void AvatarManager::clearOtherAvatars() {
} else {
auto removedAvatar = avatarIterator.value();
avatarIterator = _avatarHash.erase(avatarIterator);
handleRemovedAvatar(removedAvatar);
}
}
@ -374,20 +375,18 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
}
}
void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) {
AvatarMotionState* motionState = avatar->getMotionState();
if (motionState) {
motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
} else {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
avatar->setMotionState(motionState);
_motionStatesToAdd.insert(motionState);
_avatarMotionStates.insert(motionState);
}
void AvatarManager::addAvatarToSimulation(Avatar* avatar) {
assert(!avatar->getMotionState());
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
if (shape) {
// we don't add to the simulation now, we put it on a list to be added later
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
avatar->setMotionState(motionState);
_motionStatesToAdd.insert(motionState);
_avatarMotionStates.insert(motionState);
}
}
@ -416,6 +415,6 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
if (sessionID == _myAvatar->getSessionUUID()) {
return _myAvatar;
}
return findAvatar(sessionID);
}

View file

@ -52,7 +52,7 @@ public:
glm::vec3 color;
glm::vec3 direction;
};
Q_INVOKABLE void setLocalLights(const QVector<AvatarManager::LocalLight>& localLights);
Q_INVOKABLE QVector<AvatarManager::LocalLight> getLocalLights() const;
// Currently, your own avatar will be included as the null avatar id.
@ -66,8 +66,8 @@ public:
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
void handleCollisionEvents(const CollisionEvents& collisionEvents);
void updateAvatarPhysicsShape(Avatar* avatar);
void addAvatarToSimulation(Avatar* avatar);
// Expose results and parameter-tuning operations to other systems, such as stats and javascript.
Q_INVOKABLE float getRenderDistance() { return _renderDistance; }
Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
@ -80,7 +80,7 @@ public:
Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); }
Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue);
public slots:
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
void updateAvatarRenderStatus(bool shouldRenderAvatars);
@ -90,19 +90,19 @@ private:
AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);
// virtual overrides
virtual AvatarSharedPointer newSharedAvatar();
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
void removeAvatarMotionState(AvatarSharedPointer avatar);
virtual void removeAvatar(const QUuid& sessionUUID);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar);
QVector<AvatarSharedPointer> _avatarFades;
std::shared_ptr<MyAvatar> _myAvatar;
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
QVector<AvatarManager::LocalLight> _localLights;
bool _shouldShowReceiveStats = false;

View file

@ -46,7 +46,7 @@ public:
virtual float getObjectFriction() const;
virtual float getObjectLinearDamping() const;
virtual float getObjectAngularDamping() const;
virtual glm::vec3 getObjectPosition() const;
virtual glm::quat getObjectRotation() const;
virtual glm::vec3 getObjectLinearVelocity() const;

View file

@ -37,7 +37,7 @@ void Hand::simulate(float deltaTime, bool isMine) {
void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
float avatarScale = 1.0f;
if (_owningAvatar) {
avatarScale = _owningAvatar->getAvatarScale();
avatarScale = _owningAvatar->getUniformScale();
}
const float alpha = 1.0f;
@ -62,7 +62,7 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
transform.setRotation(palm.getRotation());
transform.postScale(SPHERE_RADIUS);
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, transform, grayColor);
// draw a green sphere at the old "finger tip"
transform = Transform();
position = palm.getTipPosition();
@ -72,7 +72,7 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, transform, greenColor);
}
}
const float AXIS_RADIUS = 0.1f * SPHERE_RADIUS;
const float AXIS_LENGTH = 10.0f * SPHERE_RADIUS;

View file

@ -287,10 +287,7 @@ extern void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avata
void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
if (getAvatarScale() != _targetScale) {
float scale = (1.0f - SMOOTHING_RATIO) * getAvatarScale() + SMOOTHING_RATIO * _targetScale;
setAvatarScale(scale);
}
animateScaleChanges(deltaTime);
{
PerformanceTimer perfTimer("transform");
@ -337,7 +334,7 @@ void MyAvatar::simulate(float deltaTime) {
headPosition = getPosition();
}
head->setPosition(headPosition);
head->setScale(getAvatarScale());
head->setScale(getUniformScale());
head->simulate(deltaTime, true);
}
@ -512,25 +509,25 @@ glm::vec3 MyAvatar::getRightHandTipPosition() const {
controller::Pose MyAvatar::getLeftHandPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose();
palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
}
controller::Pose MyAvatar::getRightHandPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose();
palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
}
controller::Pose MyAvatar::getLeftHandTipPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose();
palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
}
controller::Pose MyAvatar::getRightHandTipPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose();
palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
}
// virtual
@ -539,7 +536,7 @@ void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
if (!_shouldRender) {
return; // exit early
}
Avatar::render(renderArgs, cameraPosition);
}
@ -681,7 +678,7 @@ void MyAvatar::loadData() {
_leanScale = loadSetting(settings, "leanScale", 0.05f);
_targetScale = loadSetting(settings, "scale", 1.0f);
setAvatarScale(getAvatarScale());
setScale(glm::vec3(_targetScale));
_animGraphUrl = settings.value("animGraphURL", "").toString();
_fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
@ -802,14 +799,14 @@ void MyAvatar::updateLookAtTargetAvatar() {
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f;
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
foreach (const AvatarSharedPointer& avatarPointer, hash) {
auto avatar = static_pointer_cast<Avatar>(avatarPointer);
bool isCurrentTarget = avatar->getIsLookAtTarget();
float distanceTo = glm::length(avatar->getHead()->getEyePosition() - cameraPosition);
avatar->setIsLookAtTarget(false);
if (!avatar->isMyAvatar() && avatar->isInitialized() &&
(distanceTo < GREATEST_LOOKING_AT_DISTANCE * getAvatarScale())) {
(distanceTo < GREATEST_LOOKING_AT_DISTANCE * getUniformScale())) {
float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - cameraPosition));
if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) {
_lookAtTargetAvatar = avatarPointer;
@ -1036,14 +1033,15 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
return Avatar::getPosition();
}
void MyAvatar::rebuildSkeletonBody() {
void MyAvatar::rebuildCollisionShape() {
// compute localAABox
float radius = _skeletonModel.getBoundingCapsuleRadius();
float height = _skeletonModel.getBoundingCapsuleHeight() + 2.0f * radius;
float scale = getUniformScale();
float radius = scale * _skeletonModel.getBoundingCapsuleRadius();
float height = scale * _skeletonModel.getBoundingCapsuleHeight() + 2.0f * radius;
glm::vec3 corner(-radius, -0.5f * height, -radius);
corner += _skeletonModel.getBoundingCapsuleOffset();
glm::vec3 scale(2.0f * radius, height, 2.0f * radius);
_characterController.setLocalBoundingBox(corner, scale);
corner += scale * _skeletonModel.getBoundingCapsuleOffset();
glm::vec3 diagonal(2.0f * radius, height, 2.0f * radius);
_characterController.setLocalBoundingBox(corner, diagonal);
}
void MyAvatar::prepareForPhysicsSimulation() {
@ -1177,7 +1175,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
if (!_skeletonModel.isRenderable()) {
return; // wait until all models are loaded
}
fixupModelsInScene();
// Render head so long as the camera isn't inside it
@ -1331,7 +1329,7 @@ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f;
bool MyAvatar::cameraInsideHead() const {
const Head* head = getHead();
const glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getAvatarScale());
return glm::length(cameraPosition - head->getEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
}
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
@ -1455,11 +1453,11 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
if (isHovering) {
// we're flying --> complex acceleration curve with high max speed
float motorSpeed = glm::length(_keyboardMotorVelocity);
float finalMaxMotorSpeed = getAvatarScale() * MAX_KEYBOARD_MOTOR_SPEED;
float finalMaxMotorSpeed = getUniformScale() * MAX_KEYBOARD_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = getAvatarScale() * MAX_BOOST_SPEED;
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
if (motorSpeed < maxBoostSpeed) {
// an active keyboard motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;

View file

@ -249,7 +249,7 @@ public slots:
Q_INVOKABLE void updateMotionBehaviorFromMenu();
virtual void rebuildSkeletonBody() override;
virtual void rebuildCollisionShape() override;
Q_INVOKABLE QUrl getAnimGraphUrl() const { return _animGraphUrl; }

View file

@ -32,7 +32,7 @@ MyCharacterController::~MyCharacterController() {
void MyCharacterController::updateShapeIfNecessary() {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
_pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE;
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
// compute new dimensions from avatar's bounding box
float x = _boxScale.x;

View file

@ -69,7 +69,7 @@ void SkeletonModel::initJointStates() {
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
_headClipDistance = std::max(_headClipDistance, DEFAULT_NEAR_CLIP);
_owningAvatar->rebuildSkeletonBody();
_owningAvatar->rebuildCollisionShape();
emit skeletonLoaded();
}
@ -338,35 +338,38 @@ void SkeletonModel::computeBoundingShape() {
return;
}
_rig->computeAvatarBoundingCapsule(geometry,
_boundingCapsuleRadius,
_boundingCapsuleHeight,
_boundingCapsuleLocalOffset);
float radius, height;
glm::vec3 offset;
_rig->computeAvatarBoundingCapsule(geometry, radius, height, offset);
float invScale = 1.0f / _owningAvatar->getUniformScale();
_boundingCapsuleRadius = invScale * radius;
_boundingCapsuleHeight = invScale * height;
_boundingCapsuleLocalOffset = invScale * offset;
}
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float scale, float alpha) {
auto geometryCache = DependencyManager::get<GeometryCache>();
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
// draw a blue sphere at the capsule top point
glm::vec3 topPoint = _translation + getRotation() * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 topPoint = _translation + getRotation() * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
deferredLighting->renderSolidSphereInstance(batch,
Transform().setTranslation(topPoint).postScale(_boundingCapsuleRadius),
Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius),
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
// draw a yellow sphere at the capsule bottom point
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f);
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
glm::vec3 axis = topPoint - bottomPoint;
deferredLighting->renderSolidSphereInstance(batch,
Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius),
Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius),
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
batch.setModelTransform(Transform().setTranslation(bottomPoint));
deferredLighting->bindSimpleProgram(batch);
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingCapsuleRadius, _boundingCapsuleRadius,
Avatar::renderJointConnectingCone(batch, origin, axis, scale * _boundingCapsuleRadius, scale * _boundingCapsuleRadius,
glm::vec4(0.6f, 0.8f, 0.6f, alpha));
}

View file

@ -89,7 +89,7 @@ public:
/// \return whether or not the head was found.
glm::vec3 getDefaultEyeModelPosition() const;
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
void renderBoundingCollisionShapes(gpu::Batch& batch, float scale, float alpha);
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
const glm::vec3 getBoundingCapsuleOffset() const { return _boundingCapsuleLocalOffset; }

View file

@ -84,6 +84,19 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
return result;
}
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected) {
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
Q_ARG(const QString&, groupName),
Q_ARG(const QStringList&, actionList),
Q_ARG(const QString&, selected));
}
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
QMetaObject::invokeMethod(Menu::getInstance(), "removeActionGroup",
Q_ARG(const QString&, groupName));
}
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
bool result;
QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection,

View file

@ -42,6 +42,10 @@ public slots:
void removeMenuItem(const QString& menuName, const QString& menuitem);
bool menuItemExists(const QString& menuName, const QString& menuitem);
void addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected = QString());
void removeActionGroup(const QString& groupName);
bool isOptionChecked(const QString& menuOption);
void setIsOptionChecked(const QString& menuOption, bool isChecked);

View file

@ -34,7 +34,6 @@
static const quint64 MSECS_TO_USECS = 1000ULL;
static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f };
static const float reticleSize = TWO_PI / 100.0f;
static const float CURSOR_PIXEL_SIZE = 32.0f;
@ -386,7 +385,7 @@ bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& positi
glm::vec3 relativeDirection = glm::normalize(inverseOrientation * direction);
float t;
if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getAvatarScale(), &t)){
if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getUniformScale(), &t)){
result = position + direction * t;
return true;
}

View file

@ -199,7 +199,7 @@ void PreferencesDialog::loadPreferences() {
ui.leanScaleSpin->setValue(myAvatar->getLeanScale());
ui.avatarScaleSpin->setValue(myAvatar->getAvatarScale());
ui.avatarScaleSpin->setValue(myAvatar->getUniformScale());
ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl().toString());
ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond());
@ -256,7 +256,7 @@ void PreferencesDialog::savePreferences() {
myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum());
myAvatar->setLeanScale(ui.leanScaleSpin->value());
myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());
myAvatar->setTargetScaleVerbose(ui.avatarScaleSpin->value());
if (myAvatar->getAnimGraphUrl() != ui.avatarAnimationEdit->text()) { // If changed, destroy the old and start with the new
myAvatar->setAnimGraphUrl(ui.avatarAnimationEdit->text());
}

View file

@ -26,13 +26,15 @@
QString const Image3DOverlay::TYPE = "image3d";
Image3DOverlay::Image3DOverlay() {
_isLoaded = false;
_isLoaded = false;
_emissive = false;
}
Image3DOverlay::Image3DOverlay(const Image3DOverlay* image3DOverlay) :
Billboard3DOverlay(image3DOverlay),
_url(image3DOverlay->_url),
_texture(image3DOverlay->_texture),
_emissive(image3DOverlay->_emissive),
_fromImage(image3DOverlay->_fromImage)
{
}
@ -93,8 +95,8 @@ void Image3DOverlay::render(RenderArgs* args) {
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(*batch, true, false, false, true);
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(*batch, true, false, _emissive, true);
DependencyManager::get<GeometryCache>()->renderQuad(
*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha)
@ -144,6 +146,11 @@ void Image3DOverlay::setProperties(const QScriptValue &properties) {
setClipFromSource(subImageRect);
}
}
QScriptValue emissiveValue = properties.property("emissive");
if (emissiveValue.isValid()) {
_emissive = emissiveValue.toBool();
}
}
QScriptValue Image3DOverlay::getProperty(const QString& property) {
@ -156,6 +163,9 @@ QScriptValue Image3DOverlay::getProperty(const QString& property) {
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
if (property == "emissive") {
return _emissive;
}
return Billboard3DOverlay::getProperty(property);
}

View file

@ -46,6 +46,7 @@ public:
private:
QString _url;
NetworkTexturePointer _texture;
bool _emissive;
QRect _fromImage; // where from in the image to sample
};

View file

@ -68,7 +68,7 @@ namespace render {
glm::vec3 myAvatarPosition = avatar->getPosition();
float angle = glm::degrees(glm::angle(myAvatarRotation));
glm::vec3 axis = glm::axis(myAvatarRotation);
float myAvatarScale = avatar->getAvatarScale();
float myAvatarScale = avatar->getUniformScale();
Transform transform = Transform();
transform.setTranslation(myAvatarPosition);
transform.setRotation(glm::angleAxis(angle, axis));

View file

@ -122,5 +122,5 @@ void AnimBlendLinearMove::setCurrentFrameInternal(float frame) {
auto clipNode = std::dynamic_pointer_cast<AnimClip>(_children.front());
assert(clipNode);
const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f;
_phase = fmodf(frame, NUM_FRAMES);
_phase = fmodf(frame / NUM_FRAMES, 1.0f);
}

View file

@ -140,6 +140,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints)
}
#ifndef NDEBUG
#define DUMP_FBX_JOINTS
void AnimSkeleton::dump() const {
qCDebug(animation) << "[";
for (int i = 0; i < getNumJoints(); i++) {
@ -151,21 +152,22 @@ void AnimSkeleton::dump() const {
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
#ifdef DUMP_FBX_JOINTS
qCDebug(animation) << " isFree =" << _joints[i].isFree;
qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage;
qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex;
qCDebug(animation) << " translation =" << _joints[i].translation;
qCDebug(animation) << " preTransform =" << _joints[i].preTransform;
qCDebug(animation) << " preRotation =" << _joints[i].preRotation;
qCDebug(animation) << " rotation =" << _joints[i].rotation;
qCDebug(animation) << " postRotation =" << _joints[i].postRotation;
qCDebug(animation) << " postTransform =" << _joints[i].postTransform;
qCDebug(animation) << " transform =" << _joints[i].transform;
qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax;
qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation;
qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation;
qCDebug(animation) << " bindTransform" << _joints[i].bindTransform;
qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint;
qCDebug(animation) << " fbxJoint =";
qCDebug(animation) << " isFree =" << _joints[i].isFree;
qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage;
qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex;
qCDebug(animation) << " translation =" << _joints[i].translation;
qCDebug(animation) << " preTransform =" << _joints[i].preTransform;
qCDebug(animation) << " preRotation =" << _joints[i].preRotation;
qCDebug(animation) << " rotation =" << _joints[i].rotation;
qCDebug(animation) << " postRotation =" << _joints[i].postRotation;
qCDebug(animation) << " postTransform =" << _joints[i].postTransform;
qCDebug(animation) << " transform =" << _joints[i].transform;
qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax;
qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation;
qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation;
qCDebug(animation) << " bindTransform" << _joints[i].bindTransform;
qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint;
#endif
if (getParentIndex(i) >= 0) {
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));

View file

@ -289,7 +289,6 @@ uint64_t AudioInjector::injectNextFrame() {
_currentSendOffset = 0;
} else {
// we weren't to loop, say that we're done now
qDebug() << "AudioInjector::injectNextFrame has sent all data and was not asked to loop - calling finish().";
finish();
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
}

View file

@ -90,7 +90,7 @@ const QUrl& AvatarData::defaultFullAvatarModelUrl() {
// There are a number of possible strategies for this set of tools through endRender, below.
void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) {
avatarLock.lock();
Transform trans;
Transform trans = getTransform();
trans.setTranslation(position);
trans.setRotation(orientation);
SpatiallyNestable::setTransform(trans);
@ -129,13 +129,10 @@ float AvatarData::getTargetScale() const {
}
void AvatarData::setTargetScale(float targetScale) {
_targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, targetScale));
_targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
}
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
void AvatarData::setTargetScaleVerbose(float targetScale) {
setTargetScale(targetScale);
qCDebug(avatars) << "Changed scale to " << _targetScale;
}

View file

@ -172,7 +172,7 @@ public:
AvatarData();
virtual ~AvatarData();
static const QUrl& defaultFullAvatarModelUrl();
virtual bool isMyAvatar() const { return false; }
@ -237,7 +237,7 @@ public:
// Scale
float getTargetScale() const;
void setTargetScale(float targetScale);
void setClampedTargetScale(float targetScale);
void setTargetScaleVerbose(float targetScale);
// Hand State
Q_INVOKABLE void setHandState(char s) { _handState = s; }
@ -261,7 +261,7 @@ public:
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
Q_INVOKABLE virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
@ -342,6 +342,9 @@ public:
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
void die() { _isDead = true; }
bool isDead() const { return _isDead; }
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -413,6 +416,8 @@ protected:
// updates about one avatar to another.
glm::vec3 _globalPosition;
bool _isDead { false };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -14,7 +14,7 @@
#include <GeometryUtil.h>
#include <SharedUtil.h>
#include "AvatarData.h"
#include "AvatarData.h"
#include "HandData.h"
@ -38,7 +38,7 @@ PalmData& HandData::addNewPalm(Hand whichHand) {
PalmData HandData::getCopyOfPalmData(Hand hand) const {
QReadLocker locker(&_palmsLock);
// the palms are not necessarily added in left-right order,
// the palms are not necessarily added in left-right order,
// so we have to search for the correct hand
for (const auto& palm : _palms) {
if (palm.whichHand() == hand && palm.isActive()) {
@ -64,7 +64,7 @@ void PalmData::addToPosition(const glm::vec3& delta) {
_rawPosition += _owningHandData->worldToLocalVector(delta);
}
bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
const PalmData*& collidingPalm) const {
QReadLocker locker(&_palmsLock);
@ -93,7 +93,7 @@ glm::vec3 HandData::getBasePosition() const {
float HandData::getBaseScale() const {
return _owningAvatarData->getTargetScale();
}
glm::vec3 PalmData::getFingerDirection() const {
// finger points along yAxis in hand-frame
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);

View file

@ -38,7 +38,7 @@ public:
HandData(AvatarData* owningAvatar);
virtual ~HandData() {}
// position conversion
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale();
@ -60,7 +60,7 @@ public:
/// \param penetration[out] the vector in which to store the penetration
/// \param collidingPalm[out] a const PalmData* to the palm that was collided with
/// \return whether or not the sphere penetrated
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
const PalmData*& collidingPalm) const;
glm::quat getBaseOrientation() const;
@ -74,7 +74,7 @@ protected:
AvatarData* _owningAvatarData;
std::vector<PalmData> _palms;
mutable QReadWriteLock _palmsLock{ QReadWriteLock::Recursive };
glm::vec3 getBasePosition() const;
float getBaseScale() const;
@ -112,13 +112,12 @@ public:
void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; }
const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; }
glm::quat getRawAngularVelocityAsQuat() const { return glm::quat(_rawAngularVelocity); }
void addToPosition(const glm::vec3& delta);
void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; }
void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.0f); }
void setTipPosition(const glm::vec3& position) { _tipPosition = position; }
const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); }
const glm::vec3& getTipRawPosition() const { return _tipPosition; }
@ -126,16 +125,16 @@ public:
void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; }
const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); }
const glm::vec3& getTipRawVelocity() const { return _tipVelocity; }
void incrementFramesWithoutData() { _numFramesWithoutData++; }
void resetFramesWithoutData() { _numFramesWithoutData = 0; }
int getFramesWithoutData() const { return _numFramesWithoutData; }
// FIXME - these are used in SkeletonModel::updateRig() the skeleton/rig should probably get this information
// from an action and/or the UserInputMapper instead of piping it through here.
void setTrigger(float trigger) { _trigger = trigger; }
float getTrigger() const { return _trigger; }
// return world-frame:
glm::vec3 getFingerDirection() const;
glm::vec3 getNormal() const;
@ -148,13 +147,13 @@ private:
glm::vec3 _rawAngularVelocity;
glm::quat _rawDeltaRotation;
glm::quat _lastRotation;
glm::vec3 _tipPosition;
glm::vec3 _tipVelocity;
glm::vec3 _totalPenetration; /// accumulator for per-frame penetrations
float _trigger;
bool _isActive; /// This has current valid data
int _numFramesWithoutData; /// after too many frames without data, this tracked object assumed lost.
HandData* _owningHandData;

View file

@ -16,7 +16,7 @@
namespace controller {
Pose::Pose(const vec3& translation, const quat& rotation,
const vec3& velocity, const quat& angularVelocity) :
const vec3& velocity, const vec3& angularVelocity) :
translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { }
bool Pose::operator==(const Pose& right) const {
@ -26,7 +26,7 @@ namespace controller {
}
// FIXME add margin of error? Or add an additional withinEpsilon function?
return translation == right.getTranslation() && rotation == right.getRotation() &&
return translation == right.getTranslation() && rotation == right.getRotation() &&
velocity == right.getVelocity() && angularVelocity == right.getAngularVelocity();
}
@ -35,14 +35,29 @@ namespace controller {
obj.setProperty("translation", vec3toScriptValue(engine, pose.translation));
obj.setProperty("rotation", quatToScriptValue(engine, pose.rotation));
obj.setProperty("velocity", vec3toScriptValue(engine, pose.velocity));
obj.setProperty("angularVelocity", quatToScriptValue(engine, pose.angularVelocity));
obj.setProperty("angularVelocity", vec3toScriptValue(engine, pose.angularVelocity));
obj.setProperty("valid", pose.valid);
return obj;
}
void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) {
// nothing for now...
auto translation = object.property("translation");
auto rotation = object.property("rotation");
auto velocity = object.property("velocity");
auto angularVelocity = object.property("angularVelocity");
if (translation.isValid() &&
rotation.isValid() &&
velocity.isValid() &&
angularVelocity.isValid()) {
vec3FromScriptValue(translation, pose.translation);
quatFromScriptValue(rotation, pose.rotation);
vec3FromScriptValue(velocity, pose.velocity);
vec3FromScriptValue(angularVelocity, pose.angularVelocity);
pose.valid = true;
} else {
pose.valid = false;
}
}
}

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