mirror of
https://github.com/overte-org/overte.git
synced 2025-04-26 08:56:26 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into HMDLightingBug
This commit is contained in:
commit
08546fcc08
42 changed files with 1609 additions and 1136 deletions
|
@ -41,10 +41,6 @@ Agent::Agent(NLPacket& packet) :
|
||||||
DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES,
|
DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES,
|
||||||
DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false))
|
DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false))
|
||||||
{
|
{
|
||||||
// be the parent of the script engine so it gets moved when we do
|
|
||||||
_scriptEngine.setParent(this);
|
|
||||||
_scriptEngine.setIsAgent(true);
|
|
||||||
|
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||||
|
|
||||||
DependencyManager::set<ResourceCacheSharedItems>();
|
DependencyManager::set<ResourceCacheSharedItems>();
|
||||||
|
@ -157,8 +153,11 @@ void Agent::run() {
|
||||||
|
|
||||||
qDebug() << "Downloaded script:" << scriptContents;
|
qDebug() << "Downloaded script:" << scriptContents;
|
||||||
|
|
||||||
|
_scriptEngine = new ScriptEngine(scriptContents, _payload);
|
||||||
|
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
|
||||||
|
|
||||||
// setup an Avatar for the script to use
|
// setup an Avatar for the script to use
|
||||||
ScriptableAvatar scriptedAvatar(&_scriptEngine);
|
ScriptableAvatar scriptedAvatar(_scriptEngine);
|
||||||
scriptedAvatar.setForceFaceTrackerConnected(true);
|
scriptedAvatar.setForceFaceTrackerConnected(true);
|
||||||
|
|
||||||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||||
|
@ -166,11 +165,10 @@ void Agent::run() {
|
||||||
scriptedAvatar.setSkeletonModelURL(QUrl());
|
scriptedAvatar.setSkeletonModelURL(QUrl());
|
||||||
|
|
||||||
// give this AvatarData object to the script engine
|
// give this AvatarData object to the script engine
|
||||||
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
setAvatarData(&scriptedAvatar, "Avatar");
|
||||||
|
|
||||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||||
|
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||||
_scriptEngine.setAvatarHashMap(avatarHashMap.data(), "AvatarList");
|
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
||||||
|
@ -179,33 +177,188 @@ void Agent::run() {
|
||||||
packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
|
packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
|
||||||
|
|
||||||
// register ourselves to the script engine
|
// register ourselves to the script engine
|
||||||
_scriptEngine.registerGlobalObject("Agent", this);
|
_scriptEngine->registerGlobalObject("Agent", this);
|
||||||
|
|
||||||
if (!_payload.isEmpty()) {
|
// FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why
|
||||||
_scriptEngine.setParentURL(_payload);
|
// viewers would need this called.
|
||||||
}
|
//_scriptEngine->init(); // must be done before we set up the viewers
|
||||||
|
|
||||||
_scriptEngine.init(); // must be done before we set up the viewers
|
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||||
|
|
||||||
_scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
|
||||||
|
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||||
QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor);
|
|
||||||
_scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
|
||||||
|
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
|
||||||
_scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer);
|
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||||
_entityViewer.init();
|
_entityViewer.init();
|
||||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||||
|
|
||||||
_scriptEngine.setScriptContents(scriptContents);
|
// wire up our additional agent related processing to the update signal
|
||||||
_scriptEngine.run();
|
QObject::connect(_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio);
|
||||||
|
|
||||||
|
_scriptEngine->run();
|
||||||
setFinished(true);
|
setFinished(true);
|
||||||
|
|
||||||
|
// kill the avatar identity timer
|
||||||
|
delete _avatarIdentityTimer;
|
||||||
|
|
||||||
|
// delete the script engine
|
||||||
|
delete _scriptEngine;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::setIsAvatar(bool isAvatar) {
|
||||||
|
_isAvatar = isAvatar;
|
||||||
|
|
||||||
|
if (_isAvatar && !_avatarIdentityTimer) {
|
||||||
|
// set up the avatar timers
|
||||||
|
_avatarIdentityTimer = new QTimer(this);
|
||||||
|
_avatarBillboardTimer = new QTimer(this);
|
||||||
|
|
||||||
|
// connect our slot
|
||||||
|
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||||
|
connect(_avatarBillboardTimer, &QTimer::timeout, this, &Agent::sendAvatarBillboardPacket);
|
||||||
|
|
||||||
|
// start the timers
|
||||||
|
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||||
|
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isAvatar) {
|
||||||
|
delete _avatarIdentityTimer;
|
||||||
|
_avatarIdentityTimer = NULL;
|
||||||
|
delete _avatarBillboardTimer;
|
||||||
|
_avatarBillboardTimer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
||||||
|
_avatarData = avatarData;
|
||||||
|
_scriptEngine->registerGlobalObject(objectName, avatarData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::sendAvatarIdentityPacket() {
|
||||||
|
if (_isAvatar && _avatarData) {
|
||||||
|
_avatarData->sendIdentityPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Agent::sendAvatarBillboardPacket() {
|
||||||
|
if (_isAvatar && _avatarData) {
|
||||||
|
_avatarData->sendBillboardPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
||||||
|
if (!_scriptEngine->isFinished() && _isAvatar && _avatarData) {
|
||||||
|
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
|
||||||
|
/ (1000 * 1000)) + 0.5);
|
||||||
|
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||||
|
|
||||||
|
QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
||||||
|
_avatarData->doneEncoding(true);
|
||||||
|
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
|
||||||
|
|
||||||
|
avatarPacket->write(avatarByteArray);
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||||
|
|
||||||
|
if (_isListeningToAudioStream || _avatarSound) {
|
||||||
|
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||||
|
bool silentFrame = true;
|
||||||
|
|
||||||
|
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||||
|
const int16_t* nextSoundOutput = NULL;
|
||||||
|
|
||||||
|
if (_avatarSound) {
|
||||||
|
|
||||||
|
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||||
|
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||||
|
+ _numAvatarSoundSentBytes);
|
||||||
|
|
||||||
|
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||||
|
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||||
|
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||||
|
|
||||||
|
|
||||||
|
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||||
|
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||||
|
if (nextSoundOutput[i] != 0) {
|
||||||
|
silentFrame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||||
|
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||||
|
// we're done with this sound object - so set our pointer back to NULL
|
||||||
|
// and our sent bytes back to zero
|
||||||
|
_avatarSound = NULL;
|
||||||
|
_numAvatarSoundSentBytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto audioPacket = NLPacket::create(silentFrame
|
||||||
|
? PacketType::SilentAudioFrame
|
||||||
|
: PacketType::MicrophoneAudioNoEcho);
|
||||||
|
|
||||||
|
// seek past the sequence number, will be packed when destination node is known
|
||||||
|
audioPacket->seek(sizeof(quint16));
|
||||||
|
|
||||||
|
if (silentFrame) {
|
||||||
|
if (!_isListeningToAudioStream) {
|
||||||
|
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the number of silent samples so the audio-mixer can uphold timing
|
||||||
|
audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
||||||
|
|
||||||
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
audioPacket->writePrimitive(_avatarData->getPosition());
|
||||||
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
audioPacket->writePrimitive(headOrientation);
|
||||||
|
|
||||||
|
}else if (nextSoundOutput) {
|
||||||
|
// assume scripted avatar audio is mono and set channel flag to zero
|
||||||
|
audioPacket->writePrimitive((quint8)0);
|
||||||
|
|
||||||
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
|
audioPacket->writePrimitive(_avatarData->getPosition());
|
||||||
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
audioPacket->writePrimitive(headOrientation);
|
||||||
|
|
||||||
|
// write the raw audio data
|
||||||
|
audioPacket->write(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write audio packet to AudioMixer nodes
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){
|
||||||
|
// only send to nodes of type AudioMixer
|
||||||
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
// pack sequence number
|
||||||
|
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
||||||
|
audioPacket->seek(0);
|
||||||
|
audioPacket->writePrimitive(sequence);
|
||||||
|
|
||||||
|
// send audio packet
|
||||||
|
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::aboutToFinish() {
|
void Agent::aboutToFinish() {
|
||||||
_scriptEngine.stop();
|
_scriptEngine->stop();
|
||||||
|
|
||||||
_pingTimer->stop();
|
_pingTimer->stop();
|
||||||
delete _pingTimer;
|
delete _pingTimer;
|
||||||
|
|
|
@ -37,14 +37,13 @@ class Agent : public ThreadedAssignment {
|
||||||
public:
|
public:
|
||||||
Agent(NLPacket& packet);
|
Agent(NLPacket& packet);
|
||||||
|
|
||||||
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
void setIsAvatar(bool isAvatar);
|
||||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
bool isAvatar() const { return _isAvatar; }
|
||||||
|
|
||||||
bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); }
|
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||||
|
|
||||||
bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); }
|
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||||
void setIsListeningToAudioStream(bool isListeningToAudioStream)
|
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||||
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
|
|
||||||
|
|
||||||
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
||||||
|
|
||||||
|
@ -52,22 +51,39 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); }
|
void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
||||||
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||||
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||||
void sendPingRequests();
|
void sendPingRequests();
|
||||||
|
void processAgentAvatarAndAudio(float deltaTime);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptEngine _scriptEngine;
|
ScriptEngine* _scriptEngine;
|
||||||
EntityEditPacketSender _entityEditSender;
|
EntityEditPacketSender _entityEditSender;
|
||||||
EntityTreeHeadlessViewer _entityViewer;
|
EntityTreeHeadlessViewer _entityViewer;
|
||||||
QTimer* _pingTimer;
|
QTimer* _pingTimer;
|
||||||
|
|
||||||
MixedAudioStream _receivedAudioStream;
|
MixedAudioStream _receivedAudioStream;
|
||||||
float _lastReceivedAudioLoudness;
|
float _lastReceivedAudioLoudness;
|
||||||
|
|
||||||
|
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||||
|
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||||
|
|
||||||
|
void sendAvatarIdentityPacket();
|
||||||
|
void sendAvatarBillboardPacket();
|
||||||
|
|
||||||
|
AvatarData* _avatarData = nullptr;
|
||||||
|
bool _isListeningToAudioStream = false;
|
||||||
|
Sound* _avatarSound = nullptr;
|
||||||
|
int _numAvatarSoundSentBytes = 0;
|
||||||
|
bool _isAvatar = false;
|
||||||
|
QTimer* _avatarIdentityTimer = nullptr;
|
||||||
|
QTimer* _avatarBillboardTimer = nullptr;
|
||||||
|
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Agent_h
|
#endif // hifi_Agent_h
|
||||||
|
|
|
@ -142,7 +142,7 @@ void EntityServer::pruneDeletedEntities() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) {
|
bool EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) {
|
||||||
bool wantEditLogging = false;
|
bool wantEditLogging = false;
|
||||||
readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging);
|
readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging);
|
||||||
qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging));
|
qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging));
|
||||||
|
@ -150,4 +150,6 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
||||||
|
|
||||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||||
tree->setWantEditLogging(wantEditLogging);
|
tree->setWantEditLogging(wantEditLogging);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public:
|
||||||
virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
|
virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
|
||||||
|
|
||||||
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
||||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject);
|
virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void pruneDeletedEntities();
|
void pruneDeletedEntities();
|
||||||
|
|
|
@ -242,7 +242,8 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||||
|
|
||||||
if (0.0f != getCameraAspectRatio() &&
|
if (0.0f != getCameraAspectRatio() &&
|
||||||
0.0f != getCameraNearClip() &&
|
0.0f != getCameraNearClip() &&
|
||||||
0.0f != getCameraFarClip()) {
|
0.0f != getCameraFarClip() &&
|
||||||
|
getCameraNearClip() != getCameraFarClip()) {
|
||||||
newestViewFrustum.setProjection(glm::perspective(
|
newestViewFrustum.setProjection(glm::perspective(
|
||||||
glm::radians(wideFOV), // hack
|
glm::radians(wideFOV), // hack
|
||||||
getCameraAspectRatio(),
|
getCameraAspectRatio(),
|
||||||
|
|
|
@ -887,7 +887,7 @@ bool OctreeServer::readOptionString(const QString& optionName, const QJsonObject
|
||||||
return optionAvailable;
|
return optionAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::readConfiguration() {
|
bool OctreeServer::readConfiguration() {
|
||||||
// if the assignment had a payload, read and parse that
|
// if the assignment had a payload, read and parse that
|
||||||
if (getPayload().size() > 0) {
|
if (getPayload().size() > 0) {
|
||||||
parsePayload();
|
parsePayload();
|
||||||
|
@ -907,8 +907,11 @@ void OctreeServer::readConfiguration() {
|
||||||
loop.exec();
|
loop.exec();
|
||||||
|
|
||||||
if (domainHandler.getSettingsObject().isEmpty()) {
|
if (domainHandler.getSettingsObject().isEmpty()) {
|
||||||
qDebug() << "No settings object from domain-server.";
|
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
|
||||||
|
setFinished(true);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
|
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
|
||||||
QString settingsKey = getMyDomainSettingsKey();
|
QString settingsKey = getMyDomainSettingsKey();
|
||||||
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
|
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
|
||||||
|
@ -1017,7 +1020,7 @@ void OctreeServer::readConfiguration() {
|
||||||
packetsPerSecondTotalMax, _packetsTotalPerInterval);
|
packetsPerSecondTotalMax, _packetsTotalPerInterval);
|
||||||
|
|
||||||
|
|
||||||
readAdditionalConfiguration(settingsSectionObject);
|
return readAdditionalConfiguration(settingsSectionObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::run() {
|
void OctreeServer::run() {
|
||||||
|
@ -1043,7 +1046,9 @@ void OctreeServer::run() {
|
||||||
commonInit(getMyLoggingServerTargetName(), getMyNodeType());
|
commonInit(getMyLoggingServerTargetName(), getMyNodeType());
|
||||||
|
|
||||||
// read the configuration from either the payload or the domain server configuration
|
// read the configuration from either the payload or the domain server configuration
|
||||||
readConfiguration();
|
if (!readConfiguration()) {
|
||||||
|
return; // bailing on run, because readConfiguration failed
|
||||||
|
}
|
||||||
|
|
||||||
beforeRun(); // after payload has been processed
|
beforeRun(); // after payload has been processed
|
||||||
|
|
||||||
|
@ -1334,18 +1339,21 @@ void OctreeServer::sendStatsPacket() {
|
||||||
statsObject2["data"] = dataObject1;
|
statsObject2["data"] = dataObject1;
|
||||||
statsObject2["timing"] = timingArray1;
|
statsObject2["timing"] = timingArray1;
|
||||||
|
|
||||||
// Stats Object 3
|
|
||||||
QJsonObject dataArray2;
|
QJsonObject dataArray2;
|
||||||
|
QJsonObject timingArray2;
|
||||||
|
|
||||||
|
// Stats Object 3
|
||||||
|
if (_octreeInboundPacketProcessor) {
|
||||||
dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount();
|
dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount();
|
||||||
dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
||||||
dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed();
|
dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed();
|
||||||
|
|
||||||
QJsonObject timingArray2;
|
|
||||||
timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
|
timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
|
||||||
timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
|
timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
|
||||||
timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
|
timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
|
||||||
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
||||||
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject statsObject3;
|
QJsonObject statsObject3;
|
||||||
statsObject3["data"] = dataArray2;
|
statsObject3["data"] = dataArray2;
|
||||||
|
|
|
@ -135,8 +135,8 @@ protected:
|
||||||
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
|
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
|
||||||
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
|
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
|
||||||
bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result);
|
bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result);
|
||||||
void readConfiguration();
|
bool readConfiguration();
|
||||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { };
|
virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { return true; };
|
||||||
void parsePayload();
|
void parsePayload();
|
||||||
void initHTTPManager(int port);
|
void initHTTPManager(int port);
|
||||||
void resetSendingStats();
|
void resetSendingStats();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Created by Eric Levin on 9/2/15
|
// Created by Eric Levin on 9/2/15
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller.
|
// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
Script.include("../libraries/utils.js");
|
Script.include("../libraries/utils.js");
|
||||||
|
|
||||||
|
var RADIUS_FACTOR = 4;
|
||||||
|
|
||||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||||
var rightTriggerAction = RIGHT_HAND_CLICK;
|
var rightTriggerAction = RIGHT_HAND_CLICK;
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ var INTERSECT_COLOR = {
|
||||||
blue: 10
|
blue: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
var GRAB_RADIUS = 1.5;
|
var GRAB_RADIUS = 0.3;
|
||||||
|
|
||||||
var GRAB_COLOR = {
|
var GRAB_COLOR = {
|
||||||
red: 250,
|
red: 250,
|
||||||
|
@ -58,12 +60,11 @@ var DISTANCE_HOLD_THRESHOLD = 0.8;
|
||||||
var right4Action = 18;
|
var right4Action = 18;
|
||||||
var left4Action = 17;
|
var left4Action = 17;
|
||||||
|
|
||||||
var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
|
|
||||||
|
|
||||||
var RIGHT = 1;
|
var RIGHT = 1;
|
||||||
var LEFT = 0;
|
var LEFT = 0;
|
||||||
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right");
|
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right");
|
||||||
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left");
|
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left");
|
||||||
|
var startTime = Date.now();
|
||||||
|
|
||||||
|
|
||||||
//Need to wait before calling these methods for some reason...
|
//Need to wait before calling these methods for some reason...
|
||||||
|
@ -78,20 +79,31 @@ function controller(side, triggerAction, pullAction, hand) {
|
||||||
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
||||||
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
this.getHandPosition = MyAvatar.getLeftPalmPosition;
|
this.getHandPosition = MyAvatar.getLeftPalmPosition;
|
||||||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||||
}
|
}
|
||||||
this.triggerAction = triggerAction;
|
this.triggerAction = triggerAction;
|
||||||
this.pullAction = pullAction;
|
this.pullAction = pullAction;
|
||||||
this.actionID = null;
|
this.actionID = null;
|
||||||
this.tractorBeamActive = false;
|
|
||||||
this.distanceHolding = false;
|
this.distanceHolding = false;
|
||||||
this.closeGrabbing = false;
|
this.closeGrabbing = false;
|
||||||
this.triggerValue = 0;
|
this.triggerValue = 0;
|
||||||
this.prevTriggerValue = 0;
|
this.prevTriggerValue = 0;
|
||||||
this.palm = 2 * side;
|
this.palm = 2 * side;
|
||||||
this.tip = 2 * side + 1;
|
this.tip = 2 * side + 1;
|
||||||
|
this.pointer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
controller.prototype.updateLine = function() {
|
||||||
|
if (this.pointer != null) {
|
||||||
|
if (Entities.getEntityProperties(this.pointer).id != this.poitner) {
|
||||||
|
this.pointer == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pointer == null) {
|
||||||
|
this.lineCreationTime = Date.now();
|
||||||
this.pointer = Entities.addEntity({
|
this.pointer = Entities.addEntity({
|
||||||
type: "Line",
|
type: "Line",
|
||||||
name: "pointer",
|
name: "pointer",
|
||||||
|
@ -101,33 +113,33 @@ function controller(side, triggerAction, pullAction, hand) {
|
||||||
y: 1000,
|
y: 1000,
|
||||||
z: 1000
|
z: 1000
|
||||||
},
|
},
|
||||||
visible: false,
|
visible: true,
|
||||||
lifetime: LIFETIME
|
lifetime: LIFETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
controller.prototype.updateLine = function() {
|
var handPosition = this.getHandPosition();
|
||||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
var direction = Quat.getUp(this.getHandRotation());
|
||||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
|
||||||
|
|
||||||
Entities.editEntity(this.pointer, {
|
|
||||||
position: handPosition,
|
|
||||||
linePoints: [
|
|
||||||
ZERO_VEC,
|
|
||||||
Vec3.multiply(direction, LINE_LENGTH)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
//only check if we havent already grabbed an object
|
//only check if we havent already grabbed an object
|
||||||
if (this.distanceHolding) {
|
if (this.distanceHolding) {
|
||||||
|
Entities.editEntity(this.pointer, {
|
||||||
|
position: handPosition,
|
||||||
|
linePoints: [ ZERO_VEC, Vec3.subtract(Entities.getEntityProperties(this.grabbedEntity).position, handPosition) ],
|
||||||
|
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//move origin a bit away from hand so nothing gets in way
|
Entities.editEntity(this.pointer, {
|
||||||
var origin = Vec3.sum(handPosition, direction);
|
position: handPosition,
|
||||||
if (this.checkForIntersections(origin, direction)) {
|
linePoints: [ ZERO_VEC, Vec3.multiply(direction, LINE_LENGTH) ],
|
||||||
|
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.checkForIntersections(handPosition, direction)) {
|
||||||
Entities.editEntity(this.pointer, {
|
Entities.editEntity(this.pointer, {
|
||||||
color: INTERSECT_COLOR,
|
color: INTERSECT_COLOR,
|
||||||
});
|
});
|
||||||
|
@ -144,7 +156,7 @@ controller.prototype.checkPointer = function() {
|
||||||
Script.setTimeout(function() {
|
Script.setTimeout(function() {
|
||||||
var props = Entities.getEntityProperties(self.pointer);
|
var props = Entities.getEntityProperties(self.pointer);
|
||||||
Entities.editEntity(self.pointer, {
|
Entities.editEntity(self.pointer, {
|
||||||
lifetime: props.age + EXTRA_TIME + LIFETIME
|
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||||
});
|
});
|
||||||
self.checkPointer();
|
self.checkPointer();
|
||||||
}, POINTER_CHECK_TIME);
|
}, POINTER_CHECK_TIME);
|
||||||
|
@ -172,28 +184,54 @@ controller.prototype.checkForIntersections = function(origin, direction) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
controller.prototype.attemptMove = function() {
|
controller.prototype.attemptMove = function() {
|
||||||
if (this.tractorBeamActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.grabbedEntity || this.distanceHolding) {
|
if (this.grabbedEntity || this.distanceHolding) {
|
||||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||||
|
|
||||||
var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity))
|
|
||||||
this.distanceHolding = true;
|
this.distanceHolding = true;
|
||||||
if (this.actionID === null) {
|
if (this.actionID === null) {
|
||||||
|
this.currentObjectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
|
||||||
|
this.currentObjectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
|
||||||
|
|
||||||
|
this.handPreviousPosition = handPosition;
|
||||||
|
this.handPreviousRotation = handRotation;
|
||||||
|
|
||||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||||
targetPosition: newPosition,
|
targetPosition: this.currentObjectPosition,
|
||||||
linearTimeScale: .1
|
linearTimeScale: .1,
|
||||||
|
targetRotation: this.currentObjectRotation,
|
||||||
|
angularTimeScale: .1
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handPosition) * RADIUS_FACTOR, 1.0);
|
||||||
|
|
||||||
|
var handMoved = Vec3.subtract(handPosition, this.handPreviousPosition);
|
||||||
|
this.handPreviousPosition = handPosition;
|
||||||
|
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||||
|
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||||
|
|
||||||
|
// ---------------- this tracks hand rotation
|
||||||
|
// var handChange = Quat.multiply(handRotation, Quat.inverse(this.handPreviousRotation));
|
||||||
|
// this.handPreviousRotation = handRotation;
|
||||||
|
// this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
// ---------------- this doubles hand rotation
|
||||||
|
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation, 2.0),
|
||||||
|
Quat.inverse(this.handPreviousRotation));
|
||||||
|
this.handPreviousRotation = handRotation;
|
||||||
|
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
|
||||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||||
targetPosition: newPosition
|
targetPosition: this.currentObjectPosition, linearTimeScale: .1,
|
||||||
|
targetRotation: this.currentObjectRotation, angularTimeScale: .1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.prototype.showPointer = function() {
|
controller.prototype.showPointer = function() {
|
||||||
|
@ -218,19 +256,10 @@ controller.prototype.letGo = function() {
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
this.actionID = null;
|
this.actionID = null;
|
||||||
this.distanceHolding = false;
|
this.distanceHolding = false;
|
||||||
this.tractorBeamActive = false;
|
|
||||||
this.checkForEntityArrival = false;
|
|
||||||
this.closeGrabbing = false;
|
this.closeGrabbing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.prototype.update = function() {
|
controller.prototype.update = function() {
|
||||||
if (this.tractorBeamActive && this.checkForEntityArrival) {
|
|
||||||
var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity
|
|
||||||
if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) {
|
|
||||||
this.letGo();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.triggerValue = Controller.getActionValue(this.triggerAction);
|
this.triggerValue = Controller.getActionValue(this.triggerAction);
|
||||||
if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
|
if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
|
||||||
//First check if an object is within close range and then run the close grabbing logic
|
//First check if an object is within close range and then run the close grabbing logic
|
||||||
|
@ -253,7 +282,6 @@ controller.prototype.update = function() {
|
||||||
this.attemptMove();
|
this.attemptMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.prevTriggerValue = this.triggerValue;
|
this.prevTriggerValue = this.triggerValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,27 +359,6 @@ controller.prototype.deactivateEntity = function(entity) {
|
||||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.prototype.onActionEvent = function(action, state) {
|
|
||||||
if (this.pullAction === action && state === 1) {
|
|
||||||
if (this.actionID !== null) {
|
|
||||||
var self = this;
|
|
||||||
this.tractorBeamActive = true;
|
|
||||||
//We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some
|
|
||||||
//low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving!
|
|
||||||
Script.setTimeout(function() {
|
|
||||||
self.checkForEntityArrival = true;
|
|
||||||
}, 500);
|
|
||||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
|
||||||
var direction = Vec3.normalize(Controller.getSpatialControlNormal(this.tip));
|
|
||||||
//move final destination along line a bit, so it doesnt hit avatar hand
|
|
||||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
|
||||||
targetPosition: Vec3.sum(handPosition, Vec3.multiply(3, direction))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.prototype.cleanup = function() {
|
controller.prototype.cleanup = function() {
|
||||||
Entities.deleteEntity(this.pointer);
|
Entities.deleteEntity(this.pointer);
|
||||||
if (this.grabbedEntity) {
|
if (this.grabbedEntity) {
|
||||||
|
@ -364,13 +371,6 @@ function update() {
|
||||||
leftController.update();
|
leftController.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onActionEvent(action, state) {
|
|
||||||
rightController.onActionEvent(action, state);
|
|
||||||
leftController.onActionEvent(action, state);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
rightController.cleanup();
|
rightController.cleanup();
|
||||||
leftController.cleanup();
|
leftController.cleanup();
|
||||||
|
@ -378,4 +378,3 @@ function cleanup() {
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
Script.scriptEnding.connect(cleanup);
|
||||||
Script.update.connect(update)
|
Script.update.connect(update)
|
||||||
Controller.actionEvent.connect(onActionEvent);
|
|
|
@ -2868,8 +2868,8 @@ void Application::update(float deltaTime) {
|
||||||
UserInputMapper::PoseValue leftHand = userInputMapper->getPoseState(UserInputMapper::LEFT_HAND);
|
UserInputMapper::PoseValue leftHand = userInputMapper->getPoseState(UserInputMapper::LEFT_HAND);
|
||||||
UserInputMapper::PoseValue rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND);
|
UserInputMapper::PoseValue rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND);
|
||||||
Hand* hand = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHand();
|
Hand* hand = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHand();
|
||||||
setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX);
|
setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK));
|
||||||
setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX);
|
setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::RIGHT_HAND_CLICK));
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) {
|
||||||
emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK),
|
emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK),
|
||||||
userInputMapper->getActionState(UserInputMapper::SHIFT), LEFT_HAND_INDEX);
|
userInputMapper->getActionState(UserInputMapper::SHIFT), LEFT_HAND_INDEX);
|
||||||
|
@ -4026,8 +4026,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
AvatarManager::registerMetaTypes(scriptEngine);
|
AvatarManager::registerMetaTypes(scriptEngine);
|
||||||
|
|
||||||
// hook our avatar and avatar hash map object into this script engine
|
// hook our avatar and avatar hash map object into this script engine
|
||||||
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
|
scriptEngine->registerGlobalObject("MyAvatar", _myAvatar);
|
||||||
scriptEngine->setAvatarHashMap(DependencyManager::get<AvatarManager>().data(), "AvatarList");
|
scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Camera", &_myCamera);
|
scriptEngine->registerGlobalObject("Camera", &_myCamera);
|
||||||
|
|
||||||
|
@ -4051,9 +4051,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||||
|
|
||||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter, windowValue);
|
LocationScriptingInterface::locationSetter, "Window");
|
||||||
// register `location` on the global object.
|
// register `location` on the global object.
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter);
|
LocationScriptingInterface::locationSetter);
|
||||||
|
@ -4083,9 +4083,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
||||||
|
|
||||||
QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||||
|
|
||||||
|
@ -4094,31 +4094,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
#ifdef HAVE_RTMIDI
|
#ifdef HAVE_RTMIDI
|
||||||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// TODO: Consider moving some of this functionality into the ScriptEngine class instead. It seems wrong that this
|
|
||||||
// work is being done in the Application class when really these dependencies are more related to the ScriptEngine's
|
|
||||||
// implementation
|
|
||||||
QThread* workerThread = new QThread(this);
|
|
||||||
QString scriptEngineName = QString("Script Thread:") + scriptEngine->getFilename();
|
|
||||||
workerThread->setObjectName(scriptEngineName);
|
|
||||||
|
|
||||||
// when the worker thread is started, call our engine's run..
|
|
||||||
connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run);
|
|
||||||
|
|
||||||
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
|
|
||||||
connect(scriptEngine, &ScriptEngine::doneRunning, scriptEngine, &ScriptEngine::deleteLater);
|
|
||||||
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
|
||||||
|
|
||||||
// tell the thread to stop when the script engine is done
|
|
||||||
connect(scriptEngine, &ScriptEngine::destroyed, workerThread, &QThread::quit);
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
connect(nodeList.data(), &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
|
|
||||||
|
|
||||||
scriptEngine->moveToThread(workerThread);
|
|
||||||
|
|
||||||
// Starts an event loop, and emits workerThread->started()
|
|
||||||
workerThread->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::initializeAcceptedFiles() {
|
void Application::initializeAcceptedFiles() {
|
||||||
|
@ -4264,11 +4239,14 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
|
||||||
scriptEngine->setUserLoaded(isUserLoaded);
|
scriptEngine->setUserLoaded(isUserLoaded);
|
||||||
|
|
||||||
if (scriptFilename.isNull()) {
|
if (scriptFilename.isNull()) {
|
||||||
|
// This appears to be the script engine used by the script widget's evaluation window before the file has been saved...
|
||||||
|
|
||||||
// this had better be the script editor (we should de-couple so somebody who thinks they are loading a script
|
// this had better be the script editor (we should de-couple so somebody who thinks they are loading a script
|
||||||
// doesn't just get an empty script engine)
|
// doesn't just get an empty script engine)
|
||||||
|
|
||||||
// we can complete setup now since there isn't a script we have to load
|
// we can complete setup now since there isn't a script we have to load
|
||||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||||
|
scriptEngine->runInThread();
|
||||||
} else {
|
} else {
|
||||||
// connect to the appropriate signals of this script engine
|
// connect to the appropriate signals of this script engine
|
||||||
connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded);
|
connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &Application::handleScriptEngineLoaded);
|
||||||
|
@ -4290,6 +4268,7 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) {
|
||||||
loadScript(scriptName, isUserLoaded, false, false, true);
|
loadScript(scriptName, isUserLoaded, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - change to new version of ScriptCache loading notification
|
||||||
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||||
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
|
ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
|
||||||
|
|
||||||
|
@ -4299,8 +4278,10 @@ void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||||
|
|
||||||
// register our application services and set it off on its own thread
|
// register our application services and set it off on its own thread
|
||||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||||
|
scriptEngine->runInThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - change to new version of ScriptCache loading notification
|
||||||
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
||||||
qCDebug(interfaceapp) << "Application::loadScript(), script failed to load...";
|
qCDebug(interfaceapp) << "Application::loadScript(), script failed to load...";
|
||||||
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
||||||
|
@ -4970,7 +4951,7 @@ mat4 Application::getHMDSensorPose() const {
|
||||||
return mat4();
|
return mat4();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index) {
|
void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue) {
|
||||||
PalmData* palm;
|
PalmData* palm;
|
||||||
bool foundHand = false;
|
bool foundHand = false;
|
||||||
for (size_t j = 0; j < hand->getNumPalms(); j++) {
|
for (size_t j = 0; j < hand->getNumPalms(); j++) {
|
||||||
|
@ -5040,6 +5021,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float
|
||||||
palm->setTipVelocity(glm::vec3(0.0f));
|
palm->setTipVelocity(glm::vec3(0.0f));
|
||||||
}
|
}
|
||||||
palm->setTipPosition(newTipPosition);
|
palm->setTipPosition(newTipPosition);
|
||||||
|
palm->setTrigger(triggerValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::emulateMouse(Hand* hand, float click, float shift, int index) {
|
void Application::emulateMouse(Hand* hand, float click, float shift, int index) {
|
||||||
|
|
|
@ -485,7 +485,7 @@ private:
|
||||||
|
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
|
|
||||||
void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index);
|
void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue);
|
||||||
void emulateMouse(Hand* hand, float click, float shift, int index);
|
void emulateMouse(Hand* hand, float click, float shift, int index);
|
||||||
|
|
||||||
// Various helper functions called during update()
|
// Various helper functions called during update()
|
||||||
|
|
|
@ -1296,10 +1296,13 @@ void MyAvatar::initAnimGraph() {
|
||||||
// ik-avatar.json
|
// ik-avatar.json
|
||||||
// https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060
|
// https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060
|
||||||
//
|
//
|
||||||
|
// ik-avatar-hands.json
|
||||||
|
// https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb
|
||||||
|
//
|
||||||
// or run a local web-server
|
// or run a local web-server
|
||||||
// python -m SimpleHTTPServer&
|
// python -m SimpleHTTPServer&
|
||||||
//auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
//auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
||||||
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/8f824da2908fd89ad1befadd1d8f5d7b3b6efa66/ik-avatar.json");
|
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb/raw/883c7ce8e75ad3f72a0d513c317fe4b74a41c3b8/ik-avatar-hands.json");
|
||||||
_rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
_rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,17 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
emit skeletonLoaded();
|
emit skeletonLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const PalmData* getPalmWithIndex(Hand* hand, int index) {
|
||||||
|
const PalmData* palm = nullptr;
|
||||||
|
for (size_t j = 0; j < hand->getNumPalms(); j++) {
|
||||||
|
if (hand->getPalms()[j].getSixenseID() == index) {
|
||||||
|
palm = &(hand->getPalms()[j]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return palm;
|
||||||
|
}
|
||||||
|
|
||||||
const float PALM_PRIORITY = DEFAULT_PRIORITY;
|
const float PALM_PRIORITY = DEFAULT_PRIORITY;
|
||||||
// Called within Model::simulate call, below.
|
// Called within Model::simulate call, below.
|
||||||
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
@ -108,34 +119,59 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
|
||||||
Rig::HeadParameters params;
|
Rig::HeadParameters headParams;
|
||||||
params.modelRotation = getRotation();
|
headParams.modelRotation = getRotation();
|
||||||
params.modelTranslation = getTranslation();
|
headParams.modelTranslation = getTranslation();
|
||||||
params.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
|
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
|
||||||
params.leanSideways = head->getFinalLeanSideways();
|
headParams.leanSideways = head->getFinalLeanSideways();
|
||||||
params.leanForward = head->getFinalLeanForward();
|
headParams.leanForward = head->getFinalLeanForward();
|
||||||
params.torsoTwist = head->getTorsoTwist();
|
headParams.torsoTwist = head->getTorsoTwist();
|
||||||
params.localHeadOrientation = head->getFinalOrientationInLocalFrame();
|
headParams.localHeadOrientation = head->getFinalOrientationInLocalFrame();
|
||||||
params.localHeadPitch = head->getFinalPitch();
|
headParams.localHeadPitch = head->getFinalPitch();
|
||||||
params.localHeadYaw = head->getFinalYaw();
|
headParams.localHeadYaw = head->getFinalYaw();
|
||||||
params.localHeadRoll = head->getFinalRoll();
|
headParams.localHeadRoll = head->getFinalRoll();
|
||||||
params.isInHMD = qApp->getAvatarUpdater()->isHMDMode();
|
headParams.isInHMD = qApp->getAvatarUpdater()->isHMDMode();
|
||||||
|
|
||||||
// get HMD position from sensor space into world space, and back into model space
|
// get HMD position from sensor space into world space, and back into model space
|
||||||
glm::mat4 worldToModel = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()));
|
glm::mat4 worldToModel = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()));
|
||||||
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||||
glm::vec3 hmdPosition = glm::angleAxis((float)M_PI, yAxis) * transformPoint(worldToModel * myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition());
|
glm::vec3 hmdPosition = glm::angleAxis((float)M_PI, yAxis) * transformPoint(worldToModel * myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition());
|
||||||
params.localHeadPosition = hmdPosition;
|
headParams.localHeadPosition = hmdPosition;
|
||||||
|
|
||||||
params.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
||||||
params.eyeLookAt = head->getLookAtPosition();
|
headParams.eyeLookAt = head->getLookAtPosition();
|
||||||
params.eyeSaccade = head->getSaccade();
|
headParams.eyeSaccade = head->getSaccade();
|
||||||
params.leanJointIndex = geometry.leanJointIndex;
|
headParams.leanJointIndex = geometry.leanJointIndex;
|
||||||
params.neckJointIndex = geometry.neckJointIndex;
|
headParams.neckJointIndex = geometry.neckJointIndex;
|
||||||
params.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
headParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||||
params.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
headParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||||
|
|
||||||
|
_rig->updateFromHeadParameters(headParams, deltaTime);
|
||||||
|
|
||||||
|
Rig::HandParameters handParams;
|
||||||
|
|
||||||
|
const PalmData* leftPalm = getPalmWithIndex(myAvatar->getHand(), LEFT_HAND_INDEX);
|
||||||
|
if (leftPalm && leftPalm->isActive()) {
|
||||||
|
handParams.isLeftEnabled = true;
|
||||||
|
handParams.leftPosition = leftPalm->getRawPosition();
|
||||||
|
handParams.leftOrientation = leftPalm->getRawRotation();
|
||||||
|
handParams.leftTrigger = leftPalm->getTrigger();
|
||||||
|
} else {
|
||||||
|
handParams.isLeftEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PalmData* rightPalm = getPalmWithIndex(myAvatar->getHand(), RIGHT_HAND_INDEX);
|
||||||
|
if (rightPalm && rightPalm->isActive()) {
|
||||||
|
handParams.isRightEnabled = true;
|
||||||
|
handParams.rightPosition = rightPalm->getRawPosition();
|
||||||
|
handParams.rightOrientation = rightPalm->getRawRotation();
|
||||||
|
handParams.rightTrigger = rightPalm->getTrigger();
|
||||||
|
} else {
|
||||||
|
handParams.isRightEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rig->updateFromHandParameters(handParams, deltaTime);
|
||||||
|
|
||||||
_rig->updateFromHeadParameters(params);
|
|
||||||
} else {
|
} else {
|
||||||
// This is a little more work than we really want.
|
// This is a little more work than we really want.
|
||||||
//
|
//
|
||||||
|
|
|
@ -172,7 +172,12 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
|
||||||
qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
node->addChild(loadNode(childValue.toObject(), jsonUrl));
|
AnimNode::Pointer child = loadNode(childValue.toObject(), jsonUrl);
|
||||||
|
if (child) {
|
||||||
|
node->addChild(child);
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
|
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
|
||||||
|
@ -232,13 +237,15 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = {
|
||||||
"fullBody",
|
"fullBody",
|
||||||
"upperBody",
|
"upperBody",
|
||||||
"lowerBody",
|
"lowerBody",
|
||||||
"rightArm",
|
|
||||||
"leftArm",
|
"leftArm",
|
||||||
|
"rightArm",
|
||||||
"aboveTheHead",
|
"aboveTheHead",
|
||||||
"belowTheHead",
|
"belowTheHead",
|
||||||
"headOnly",
|
"headOnly",
|
||||||
"spineOnly",
|
"spineOnly",
|
||||||
"empty"
|
"empty",
|
||||||
|
"leftHand",
|
||||||
|
"rightHand"
|
||||||
};
|
};
|
||||||
|
|
||||||
static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
|
static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
|
||||||
|
|
|
@ -26,12 +26,14 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) {
|
||||||
case FullBodyBoneSet: buildFullBodyBoneSet(); break;
|
case FullBodyBoneSet: buildFullBodyBoneSet(); break;
|
||||||
case UpperBodyBoneSet: buildUpperBodyBoneSet(); break;
|
case UpperBodyBoneSet: buildUpperBodyBoneSet(); break;
|
||||||
case LowerBodyBoneSet: buildLowerBodyBoneSet(); break;
|
case LowerBodyBoneSet: buildLowerBodyBoneSet(); break;
|
||||||
case RightArmBoneSet: buildRightArmBoneSet(); break;
|
|
||||||
case LeftArmBoneSet: buildLeftArmBoneSet(); break;
|
case LeftArmBoneSet: buildLeftArmBoneSet(); break;
|
||||||
|
case RightArmBoneSet: buildRightArmBoneSet(); break;
|
||||||
case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break;
|
case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break;
|
||||||
case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break;
|
case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break;
|
||||||
case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break;
|
case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break;
|
||||||
case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break;
|
case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break;
|
||||||
|
case LeftHandBoneSet: buildLeftHandBoneSet(); break;
|
||||||
|
case RightHandBoneSet: buildRightHandBoneSet(); break;
|
||||||
default:
|
default:
|
||||||
case EmptyBoneSet: buildEmptyBoneSet(); break;
|
case EmptyBoneSet: buildEmptyBoneSet(); break;
|
||||||
}
|
}
|
||||||
|
@ -110,15 +112,6 @@ void AnimOverlay::buildLowerBodyBoneSet() {
|
||||||
_boneSetVec[hipsJoint] = 0.0f;
|
_boneSetVec[hipsJoint] = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimOverlay::buildRightArmBoneSet() {
|
|
||||||
assert(_skeleton);
|
|
||||||
buildEmptyBoneSet();
|
|
||||||
int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder");
|
|
||||||
for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) {
|
|
||||||
_boneSetVec[i] = 1.0f;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimOverlay::buildLeftArmBoneSet() {
|
void AnimOverlay::buildLeftArmBoneSet() {
|
||||||
assert(_skeleton);
|
assert(_skeleton);
|
||||||
buildEmptyBoneSet();
|
buildEmptyBoneSet();
|
||||||
|
@ -128,6 +121,15 @@ void AnimOverlay::buildLeftArmBoneSet() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimOverlay::buildRightArmBoneSet() {
|
||||||
|
assert(_skeleton);
|
||||||
|
buildEmptyBoneSet();
|
||||||
|
int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder");
|
||||||
|
for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) {
|
||||||
|
_boneSetVec[i] = 1.0f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void AnimOverlay::buildAboveTheHeadBoneSet() {
|
void AnimOverlay::buildAboveTheHeadBoneSet() {
|
||||||
assert(_skeleton);
|
assert(_skeleton);
|
||||||
buildEmptyBoneSet();
|
buildEmptyBoneSet();
|
||||||
|
@ -168,13 +170,31 @@ void AnimOverlay::buildEmptyBoneSet() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimOverlay::buildLeftHandBoneSet() {
|
||||||
|
assert(_skeleton);
|
||||||
|
buildEmptyBoneSet();
|
||||||
|
int headJoint = _skeleton->nameToJointIndex("LeftHand");
|
||||||
|
for_each_child_joint(_skeleton, headJoint, [&](int i) {
|
||||||
|
_boneSetVec[i] = 1.0f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimOverlay::buildRightHandBoneSet() {
|
||||||
|
assert(_skeleton);
|
||||||
|
buildEmptyBoneSet();
|
||||||
|
int headJoint = _skeleton->nameToJointIndex("RightHand");
|
||||||
|
for_each_child_joint(_skeleton, headJoint, [&](int i) {
|
||||||
|
_boneSetVec[i] = 1.0f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// for AnimDebugDraw rendering
|
// for AnimDebugDraw rendering
|
||||||
const AnimPoseVec& AnimOverlay::getPosesInternal() const {
|
const AnimPoseVec& AnimOverlay::getPosesInternal() const {
|
||||||
return _poses;
|
return _poses;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||||
_skeleton = skeleton;
|
AnimNode::setSkeletonInternal(skeleton);
|
||||||
|
|
||||||
// we have to re-build the bone set when the skeleton changes.
|
// we have to re-build the bone set when the skeleton changes.
|
||||||
buildBoneSet(_boneSet);
|
buildBoneSet(_boneSet);
|
||||||
|
|
|
@ -28,14 +28,16 @@ public:
|
||||||
FullBodyBoneSet = 0,
|
FullBodyBoneSet = 0,
|
||||||
UpperBodyBoneSet,
|
UpperBodyBoneSet,
|
||||||
LowerBodyBoneSet,
|
LowerBodyBoneSet,
|
||||||
RightArmBoneSet,
|
|
||||||
LeftArmBoneSet,
|
LeftArmBoneSet,
|
||||||
|
RightArmBoneSet,
|
||||||
AboveTheHeadBoneSet,
|
AboveTheHeadBoneSet,
|
||||||
BelowTheHeadBoneSet,
|
BelowTheHeadBoneSet,
|
||||||
HeadOnlyBoneSet,
|
HeadOnlyBoneSet,
|
||||||
SpineOnlyBoneSet,
|
SpineOnlyBoneSet,
|
||||||
EmptyBoneSet,
|
EmptyBoneSet,
|
||||||
NumBoneSets,
|
LeftHandBoneSet,
|
||||||
|
RightHandBoneSet,
|
||||||
|
NumBoneSets
|
||||||
};
|
};
|
||||||
|
|
||||||
AnimOverlay(const std::string& id, BoneSet boneSet, float alpha);
|
AnimOverlay(const std::string& id, BoneSet boneSet, float alpha);
|
||||||
|
@ -64,13 +66,15 @@ public:
|
||||||
void buildFullBodyBoneSet();
|
void buildFullBodyBoneSet();
|
||||||
void buildUpperBodyBoneSet();
|
void buildUpperBodyBoneSet();
|
||||||
void buildLowerBodyBoneSet();
|
void buildLowerBodyBoneSet();
|
||||||
void buildRightArmBoneSet();
|
|
||||||
void buildLeftArmBoneSet();
|
void buildLeftArmBoneSet();
|
||||||
|
void buildRightArmBoneSet();
|
||||||
void buildAboveTheHeadBoneSet();
|
void buildAboveTheHeadBoneSet();
|
||||||
void buildBelowTheHeadBoneSet();
|
void buildBelowTheHeadBoneSet();
|
||||||
void buildHeadOnlyBoneSet();
|
void buildHeadOnlyBoneSet();
|
||||||
void buildSpineOnlyBoneSet();
|
void buildSpineOnlyBoneSet();
|
||||||
void buildEmptyBoneSet();
|
void buildEmptyBoneSet();
|
||||||
|
void buildLeftHandBoneSet();
|
||||||
|
void buildRightHandBoneSet();
|
||||||
|
|
||||||
// no copies
|
// no copies
|
||||||
AnimOverlay(const AnimOverlay&) = delete;
|
AnimOverlay(const AnimOverlay&) = delete;
|
||||||
|
|
|
@ -952,7 +952,7 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
||||||
return _jointStates[jointIndex].getDefaultRotationInParentFrame();
|
return _jointStates[jointIndex].getDefaultRotationInParentFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateFromHeadParameters(const HeadParameters& params) {
|
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||||
if (params.enableLean) {
|
if (params.enableLean) {
|
||||||
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
|
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
|
||||||
}
|
}
|
||||||
|
@ -1048,6 +1048,52 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
|
||||||
|
|
||||||
|
if (_enableAnimGraph && _animSkeleton) {
|
||||||
|
|
||||||
|
// set leftHand grab vars
|
||||||
|
_animVars.set("isLeftHandIdle", false);
|
||||||
|
_animVars.set("isLeftHandPoint", false);
|
||||||
|
_animVars.set("isLeftHandGrab", false);
|
||||||
|
|
||||||
|
// Split the trigger range into three zones.
|
||||||
|
bool rampOut = false;
|
||||||
|
if (params.leftTrigger > 0.6666f) {
|
||||||
|
_animVars.set("isLeftHandGrab", true);
|
||||||
|
} else if (params.leftTrigger > 0.3333f) {
|
||||||
|
_animVars.set("isLeftHandPoint", true);
|
||||||
|
} else {
|
||||||
|
_animVars.set("isLeftHandIdle", true);
|
||||||
|
rampOut = true;
|
||||||
|
}
|
||||||
|
const float OVERLAY_RAMP_OUT_SPEED = 6.0f; // ramp in and out over 1/6th of a sec
|
||||||
|
_leftHandOverlayAlpha = glm::clamp(_leftHandOverlayAlpha + (rampOut ? -1.0f : 1.0f) * OVERLAY_RAMP_OUT_SPEED * dt, 0.0f, 1.0f);
|
||||||
|
_animVars.set("leftHandOverlayAlpha", _leftHandOverlayAlpha);
|
||||||
|
_animVars.set("leftHandGrabBlend", params.leftTrigger);
|
||||||
|
|
||||||
|
|
||||||
|
// set leftHand grab vars
|
||||||
|
_animVars.set("isRightHandIdle", false);
|
||||||
|
_animVars.set("isRightHandPoint", false);
|
||||||
|
_animVars.set("isRightHandGrab", false);
|
||||||
|
|
||||||
|
// Split the trigger range into three zones
|
||||||
|
rampOut = false;
|
||||||
|
if (params.rightTrigger > 0.6666f) {
|
||||||
|
_animVars.set("isRightHandGrab", true);
|
||||||
|
} else if (params.rightTrigger > 0.3333f) {
|
||||||
|
_animVars.set("isRightHandPoint", true);
|
||||||
|
} else {
|
||||||
|
_animVars.set("isRightHandIdle", true);
|
||||||
|
rampOut = true;
|
||||||
|
}
|
||||||
|
_rightHandOverlayAlpha = glm::clamp(_rightHandOverlayAlpha + (rampOut ? -1.0f : 1.0f) * OVERLAY_RAMP_OUT_SPEED * dt, 0.0f, 1.0f);
|
||||||
|
_animVars.set("rightHandOverlayAlpha", _rightHandOverlayAlpha);
|
||||||
|
_animVars.set("rightHandGrabBlend", params.rightTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
||||||
if (!_enableAnimGraph) {
|
if (!_enableAnimGraph) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -76,6 +76,17 @@ public:
|
||||||
void dump() const;
|
void dump() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HandParameters {
|
||||||
|
bool isLeftEnabled;
|
||||||
|
bool isRightEnabled;
|
||||||
|
glm::vec3 leftPosition = glm::vec3();
|
||||||
|
glm::quat leftOrientation = glm::quat();
|
||||||
|
glm::vec3 rightPosition = glm::vec3();
|
||||||
|
glm::quat rightOrientation = glm::quat();
|
||||||
|
float leftTrigger = 0.0f;
|
||||||
|
float rightTrigger = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
virtual ~Rig() {}
|
virtual ~Rig() {}
|
||||||
|
|
||||||
RigPointer getRigPointer() { return shared_from_this(); }
|
RigPointer getRigPointer() { return shared_from_this(); }
|
||||||
|
@ -168,10 +179,12 @@ public:
|
||||||
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
|
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
|
||||||
bool getEnableAnimGraph() const { return _enableAnimGraph; }
|
bool getEnableAnimGraph() const { return _enableAnimGraph; }
|
||||||
|
|
||||||
void updateFromHeadParameters(const HeadParameters& params);
|
void updateFromHeadParameters(const HeadParameters& params, float dt);
|
||||||
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
|
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
|
||||||
const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade = glm::vec3(0.0f));
|
const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade = glm::vec3(0.0f));
|
||||||
|
|
||||||
|
void updateFromHandParameters(const HandParameters& params, float dt);
|
||||||
|
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority) = 0;
|
float scale, float priority) = 0;
|
||||||
|
|
||||||
|
@ -215,6 +228,8 @@ public:
|
||||||
Move
|
Move
|
||||||
};
|
};
|
||||||
RigRole _state = RigRole::Idle;
|
RigRole _state = RigRole::Idle;
|
||||||
|
float _leftHandOverlayAlpha = 0.0f;
|
||||||
|
float _rightHandOverlayAlpha = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__Rig__) */
|
#endif /* defined(__hifi__Rig__) */
|
||||||
|
|
|
@ -72,7 +72,7 @@ SwingTwistConstraint::SwingTwistConstraint() :
|
||||||
_swingLimitFunction(),
|
_swingLimitFunction(),
|
||||||
_minTwist(-PI),
|
_minTwist(-PI),
|
||||||
_maxTwist(PI),
|
_maxTwist(PI),
|
||||||
_lastTwistBoundary(0)
|
_lastTwistBoundary(LAST_CLAMP_NO_BOUNDARY)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,3 +249,6 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SwingTwistConstraint::clearHistory() {
|
||||||
|
_lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY;
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ public:
|
||||||
/// \return reference to SwingLimitFunction instance for unit-testing
|
/// \return reference to SwingLimitFunction instance for unit-testing
|
||||||
const SwingLimitFunction& getSwingLimitFunction() const { return _swingLimitFunction; }
|
const SwingLimitFunction& getSwingLimitFunction() const { return _swingLimitFunction; }
|
||||||
|
|
||||||
|
/// \brief exposed for unit testing
|
||||||
|
void clearHistory();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SwingLimitFunction _swingLimitFunction;
|
SwingLimitFunction _swingLimitFunction;
|
||||||
float _minTwist;
|
float _minTwist;
|
||||||
|
|
|
@ -125,12 +125,12 @@ public:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int scale = (2 << ((8 * sizeof(int16_t)) - 1));
|
const int scale = (1 << ((8 * sizeof(int16_t)) - 1));
|
||||||
|
|
||||||
// de-interleave and convert int16_t to float32 (normalized to -1. ... 1.)
|
// de-interleave and convert int16_t to float32 (normalized to -1. ... 1.)
|
||||||
for (uint32_t i = 0; i < frameCount; ++i) {
|
for (uint32_t i = 0; i < frameCount; ++i) {
|
||||||
for (uint32_t j = 0; j < _channelCount; ++j) {
|
for (uint32_t j = 0; j < _channelCount; ++j) {
|
||||||
_buffer[j][i] = ((float)(*in++)) / scale;
|
_buffer[j][i] = ((float)(*in++)) * (1.0f / scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
OctreeRenderer(),
|
OctreeRenderer(),
|
||||||
_wantScripts(wantScripts),
|
_wantScripts(wantScripts),
|
||||||
_entitiesScriptEngine(NULL),
|
_entitiesScriptEngine(NULL),
|
||||||
_sandboxScriptEngine(NULL),
|
|
||||||
_lastMouseEventValid(false),
|
_lastMouseEventValid(false),
|
||||||
_viewState(viewState),
|
_viewState(viewState),
|
||||||
_scriptingServices(scriptingServices),
|
_scriptingServices(scriptingServices),
|
||||||
|
@ -77,23 +76,11 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
||||||
EntityTreeRenderer::~EntityTreeRenderer() {
|
EntityTreeRenderer::~EntityTreeRenderer() {
|
||||||
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a
|
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a
|
||||||
// signal tied to call it's deleteLater on doneRunning
|
// signal tied to call it's deleteLater on doneRunning
|
||||||
if (_sandboxScriptEngine) {
|
|
||||||
// TODO: consider reworking how _sandboxScriptEngine is managed. It's treated differently than _entitiesScriptEngine
|
|
||||||
// because we don't call registerScriptEngineWithApplicationServices() for it. This implementation is confusing and
|
|
||||||
// potentially error prone because it's not a full fledged ScriptEngine that has been fully connected to the
|
|
||||||
// application. We did this so that scripts that were ill-formed could be evaluated but not execute against the
|
|
||||||
// application services. But this means it's shutdown behavior is different from other ScriptEngines
|
|
||||||
delete _sandboxScriptEngine;
|
|
||||||
_sandboxScriptEngine = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::clear() {
|
void EntityTreeRenderer::clear() {
|
||||||
leaveAllEntities();
|
leaveAllEntities();
|
||||||
foreach (const EntityItemID& entityID, _entityScripts.keys()) {
|
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||||
checkAndCallUnload(entityID);
|
|
||||||
}
|
|
||||||
_entityScripts.clear();
|
|
||||||
|
|
||||||
auto scene = _viewState->getMain3DScene();
|
auto scene = _viewState->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
|
@ -113,10 +100,9 @@ void EntityTreeRenderer::init() {
|
||||||
|
|
||||||
if (_wantScripts) {
|
if (_wantScripts) {
|
||||||
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
|
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
|
||||||
_scriptingServices->getControllerScriptingInterface(), false);
|
_scriptingServices->getControllerScriptingInterface());
|
||||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
||||||
|
_entitiesScriptEngine->runInThread();
|
||||||
_sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure our "last avatar position" is something other than our current position, so that on our
|
// make sure our "last avatar position" is something other than our current position, so that on our
|
||||||
|
@ -134,176 +120,6 @@ void EntityTreeRenderer::shutdown() {
|
||||||
_shuttingDown = true;
|
_shuttingDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
|
||||||
if (_waitingOnPreload.contains(url)) {
|
|
||||||
QList<EntityItemID> entityIDs = _waitingOnPreload.values(url);
|
|
||||||
_waitingOnPreload.remove(url);
|
|
||||||
foreach(EntityItemID entityID, entityIDs) {
|
|
||||||
checkAndCallPreload(entityID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) {
|
|
||||||
if (_waitingOnPreload.contains(url)) {
|
|
||||||
_waitingOnPreload.remove(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload, bool reload) {
|
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityTree>(_tree)->findEntityByEntityItemID(entityItemID);
|
|
||||||
return loadEntityScript(entity, isPreload, reload);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut,
|
|
||||||
bool& reload) {
|
|
||||||
isPending = false;
|
|
||||||
QUrl url(scriptMaybeURLorText);
|
|
||||||
|
|
||||||
// If the url is not valid, this must be script text...
|
|
||||||
// We document "direct injection" scripts as starting with "(function...", and that would never be a valid url.
|
|
||||||
// But QUrl thinks it is.
|
|
||||||
if (!url.isValid() || scriptMaybeURLorText.startsWith("(")) {
|
|
||||||
isURL = false;
|
|
||||||
return scriptMaybeURLorText;
|
|
||||||
}
|
|
||||||
isURL = true;
|
|
||||||
urlOut = url;
|
|
||||||
|
|
||||||
QString scriptContents; // assume empty
|
|
||||||
|
|
||||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
|
||||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
|
||||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
|
||||||
url = QUrl::fromLocalFile(scriptMaybeURLorText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok, let's see if it's valid... and if so, load it
|
|
||||||
if (url.isValid()) {
|
|
||||||
if (url.scheme() == "file") {
|
|
||||||
QString fileName = url.toLocalFile();
|
|
||||||
QFile scriptFile(fileName);
|
|
||||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
|
||||||
qCDebug(entitiesrenderer) << "Loading file:" << fileName;
|
|
||||||
QTextStream in(&scriptFile);
|
|
||||||
scriptContents = in.readAll();
|
|
||||||
} else {
|
|
||||||
qCDebug(entitiesrenderer) << "ERROR Loading file:" << fileName;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
|
||||||
|
|
||||||
if (!scriptCache->isInBadScriptList(url)) {
|
|
||||||
scriptContents = scriptCache->getScript(url, this, isPending, reload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scriptContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItemPointer entity, bool isPreload, bool reload) {
|
|
||||||
if (_shuttingDown) {
|
|
||||||
return QScriptValue(); // since we're shutting down, we don't load any more scripts
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
return QScriptValue(); // no entity...
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: we keep local variables for the entityID and the script because
|
|
||||||
// below in loadScriptContents() it's possible for us to execute the
|
|
||||||
// application event loop, which may cause our entity to be deleted on
|
|
||||||
// us. We don't really need access the entity after this point, can
|
|
||||||
// can accomplish all we need to here with just the script "text" and the ID.
|
|
||||||
EntityItemID entityID = entity->getEntityItemID();
|
|
||||||
QString entityScript = entity->getScript();
|
|
||||||
|
|
||||||
if (_entityScripts.contains(entityID)) {
|
|
||||||
EntityScriptDetails details = _entityScripts[entityID];
|
|
||||||
|
|
||||||
// check to make sure our script text hasn't changed on us since we last loaded it and we're not redownloading it
|
|
||||||
if (details.scriptText == entityScript && !reload) {
|
|
||||||
return details.scriptObject; // previously loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here, then we previously loaded a script, but the entity's script value
|
|
||||||
// has changed and so we need to reload it.
|
|
||||||
_entityScripts.remove(entityID);
|
|
||||||
}
|
|
||||||
if (entityScript.isEmpty()) {
|
|
||||||
return QScriptValue(); // no script
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text.
|
|
||||||
bool isPending = false;
|
|
||||||
QUrl url;
|
|
||||||
QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url, reload);
|
|
||||||
|
|
||||||
if (isPending && isPreload && isURL) {
|
|
||||||
_waitingOnPreload.insert(url, entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
|
||||||
|
|
||||||
if (isURL && scriptCache->isInBadScriptList(url)) {
|
|
||||||
return QScriptValue(); // no script contents...
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scriptContents.isEmpty()) {
|
|
||||||
return QScriptValue(); // no script contents...
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents);
|
|
||||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
|
||||||
qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
|
|
||||||
qCDebug(entitiesrenderer) << " " << syntaxCheck.errorMessage() << ":"
|
|
||||||
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
|
|
||||||
qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript;
|
|
||||||
|
|
||||||
scriptCache->addScriptToBadScriptList(url);
|
|
||||||
|
|
||||||
return QScriptValue(); // invalid script
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isURL) {
|
|
||||||
_entitiesScriptEngine->setParentURL(entity->getScript());
|
|
||||||
}
|
|
||||||
QScriptValue entityScriptConstructor = _sandboxScriptEngine->evaluate(scriptContents);
|
|
||||||
|
|
||||||
if (!entityScriptConstructor.isFunction()) {
|
|
||||||
qCDebug(entitiesrenderer) << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
|
|
||||||
qCDebug(entitiesrenderer) << " NOT CONSTRUCTOR";
|
|
||||||
qCDebug(entitiesrenderer) << " SCRIPT:" << entityScript;
|
|
||||||
|
|
||||||
scriptCache->addScriptToBadScriptList(url);
|
|
||||||
|
|
||||||
return QScriptValue(); // invalid script
|
|
||||||
} else {
|
|
||||||
entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue entityScriptObject = entityScriptConstructor.construct();
|
|
||||||
EntityScriptDetails newDetails = { entityScript, entityScriptObject };
|
|
||||||
_entityScripts[entityID] = newDetails;
|
|
||||||
|
|
||||||
if (isURL) {
|
|
||||||
_entitiesScriptEngine->setParentURL("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return entityScriptObject; // newly constructed
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue EntityTreeRenderer::getPreviouslyLoadedEntityScript(const EntityItemID& entityID) {
|
|
||||||
if (_entityScripts.contains(entityID)) {
|
|
||||||
EntityScriptDetails details = _entityScripts[entityID];
|
|
||||||
return details.scriptObject; // previously loaded
|
|
||||||
}
|
|
||||||
return QScriptValue(); // no script
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityTreeRenderer::setTree(OctreePointer newTree) {
|
void EntityTreeRenderer::setTree(OctreePointer newTree) {
|
||||||
OctreeRenderer::setTree(newTree);
|
OctreeRenderer::setTree(newTree);
|
||||||
std::static_pointer_cast<EntityTree>(_tree)->setFBXService(this);
|
std::static_pointer_cast<EntityTree>(_tree)->setFBXService(this);
|
||||||
|
@ -322,11 +138,7 @@ void EntityTreeRenderer::update() {
|
||||||
// and we want to simulate this message here as well as in mouse move
|
// and we want to simulate this message here as well as in mouse move
|
||||||
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent);
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -355,19 +167,14 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||||
// EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts
|
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
||||||
// for entity IDs that no longer exist.
|
// for entity IDs that no longer exist.
|
||||||
|
|
||||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("leaveEntity").isValid()) {
|
|
||||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,11 +182,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||||
if (!_currentEntitiesInside.contains(entityID)) {
|
if (!_currentEntitiesInside.contains(entityID)) {
|
||||||
emit enterEntity(entityID);
|
emit enterEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("enterEntity").isValid()) {
|
|
||||||
entityScript.property("enterEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentEntitiesInside = entitiesContainingAvatar;
|
_currentEntitiesInside = entitiesContainingAvatar;
|
||||||
|
@ -394,11 +197,7 @@ void EntityTreeRenderer::leaveAllEntities() {
|
||||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
QScriptValue entityScript = loadEntityScript(entityID);
|
|
||||||
if (entityScript.property("leaveEntity").isValid()) {
|
|
||||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_currentEntitiesInside.clear();
|
_currentEntitiesInside.clear();
|
||||||
|
|
||||||
|
@ -811,27 +610,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
|
||||||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) {
|
|
||||||
QScriptValueList args;
|
|
||||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine);
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent) {
|
|
||||||
QScriptValueList args;
|
|
||||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << mouseEvent.toScriptValue(_entitiesScriptEngine);
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) {
|
|
||||||
QScriptValueList args;
|
|
||||||
args << entityID.toScriptValue(_entitiesScriptEngine);
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
|
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
|
||||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||||
// process these events.
|
// process these events.
|
||||||
|
@ -854,18 +632,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
||||||
}
|
}
|
||||||
|
|
||||||
emit mousePressOnEntity(rayPickResult, event, deviceID);
|
emit mousePressOnEntity(rayPickResult, event, deviceID);
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
|
||||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
|
||||||
if (entityScript.property("mousePressOnEntity").isValid()) {
|
|
||||||
entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentClickingOnEntityID = rayPickResult.entityID;
|
_currentClickingOnEntityID = rayPickResult.entityID;
|
||||||
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("clickDownOnEntity").isValid()) {
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event, deviceID));
|
||||||
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
||||||
}
|
}
|
||||||
|
@ -886,24 +657,14 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||||
emit mouseReleaseOnEntity(rayPickResult, event, deviceID);
|
emit mouseReleaseOnEntity(rayPickResult, event, deviceID);
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
|
||||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
|
||||||
if (entityScript.property("mouseReleaseOnEntity").isValid()) {
|
|
||||||
entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
||||||
// we're releasing the button, then this is considered a clickOn event
|
// we're releasing the button, then this is considered a clickOn event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes it the unknown ID, we just released so we can't be clicking on anything
|
// makes it the unknown ID, we just released so we can't be clicking on anything
|
||||||
|
@ -925,17 +686,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
||||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||||
if (rayPickResult.intersects) {
|
if (rayPickResult.intersects) {
|
||||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
|
||||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event, deviceID));
|
||||||
// load the entity script if needed...
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
|
|
||||||
if (entityScript.property("mouseMoveEvent").isValid()) {
|
|
||||||
entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
emit mouseMoveOnEntity(rayPickResult, event, deviceID);
|
|
||||||
if (entityScript.property("mouseMoveOnEntity").isValid()) {
|
|
||||||
entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the hover logic...
|
// handle the hover logic...
|
||||||
|
|
||||||
|
@ -943,30 +696,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// then we need to send the hover leave.
|
// then we need to send the hover leave.
|
||||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
|
||||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
|
||||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
||||||
// this is true if the _currentHoverOverEntityID is known or unknown
|
// this is true if the _currentHoverOverEntityID is known or unknown
|
||||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("hoverEnterEntity").isValid()) {
|
|
||||||
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
|
||||||
// we should send our hover over event
|
// we should send our hover over event
|
||||||
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||||
if (entityScript.property("hoverOverEntity").isValid()) {
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event, deviceID));
|
||||||
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember what we're hovering over
|
// remember what we're hovering over
|
||||||
_currentHoverOverEntityID = rayPickResult.entityID;
|
_currentHoverOverEntityID = rayPickResult.entityID;
|
||||||
|
@ -977,14 +719,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// send the hover leave for our previous entity
|
// send the hover leave for our previous entity
|
||||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
|
||||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
|
||||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -993,13 +728,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||||
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||||
|
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event, deviceID));
|
||||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
|
||||||
|
|
||||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
|
||||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
|
||||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_lastMouseEvent = MouseEvent(*event, deviceID);
|
_lastMouseEvent = MouseEvent(*event, deviceID);
|
||||||
_lastMouseEventValid = true;
|
_lastMouseEventValid = true;
|
||||||
|
@ -1007,9 +736,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
||||||
|
|
||||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
checkAndCallUnload(entityID);
|
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||||
}
|
}
|
||||||
_entityScripts.remove(entityID);
|
|
||||||
|
|
||||||
// here's where we remove the entity payload from the scene
|
// here's where we remove the entity payload from the scene
|
||||||
if (_entitiesInScene.contains(entityID)) {
|
if (_entitiesInScene.contains(entityID)) {
|
||||||
|
@ -1042,28 +770,16 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
||||||
|
|
||||||
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
checkAndCallUnload(entityID);
|
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||||
checkAndCallPreload(entityID, reload);
|
checkAndCallPreload(entityID, reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
// load the entity script if needed...
|
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||||
QScriptValue entityScript = loadEntityScript(entityID, true, reload); // is preload!
|
if (entity && !entity->getScript().isEmpty()) {
|
||||||
if (entityScript.property("preload").isValid()) {
|
_entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload);
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
|
||||||
entityScript.property("preload").call(entityScript, entityArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) {
|
|
||||||
if (_tree && !_shuttingDown) {
|
|
||||||
QScriptValue entityScript = getPreviouslyLoadedEntityScript(entityID);
|
|
||||||
if (entityScript.property("unload").isValid()) {
|
|
||||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
|
||||||
entityScript.property("unload").call(entityScript, entityArgs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1143,24 +859,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
||||||
|
|
||||||
// And now the entity scripts
|
// And now the entity scripts
|
||||||
emit collisionWithEntity(idA, idB, collision);
|
emit collisionWithEntity(idA, idB, collision);
|
||||||
QScriptValue entityScriptA = loadEntityScript(idA);
|
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||||
if (entityScriptA.property("collisionWithEntity").isValid()) {
|
|
||||||
QScriptValueList args;
|
|
||||||
args << idA.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << idB.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << collisionToScriptValue(_entitiesScriptEngine, collision);
|
|
||||||
entityScriptA.property("collisionWithEntity").call(entityScriptA, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit collisionWithEntity(idB, idA, collision);
|
emit collisionWithEntity(idB, idA, collision);
|
||||||
QScriptValue entityScriptB = loadEntityScript(idB);
|
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||||
if (entityScriptB.property("collisionWithEntity").isValid()) {
|
|
||||||
QScriptValueList args;
|
|
||||||
args << idB.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << idA.toScriptValue(_entitiesScriptEngine);
|
|
||||||
args << collisionToScriptValue(_entitiesScriptEngine, collision);
|
|
||||||
entityScriptB.property("collisionWithEntity").call(entityScriptA, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
||||||
|
|
|
@ -28,14 +28,9 @@ class Model;
|
||||||
class ScriptEngine;
|
class ScriptEngine;
|
||||||
class ZoneEntityItem;
|
class ZoneEntityItem;
|
||||||
|
|
||||||
class EntityScriptDetails {
|
|
||||||
public:
|
|
||||||
QString scriptText;
|
|
||||||
QScriptValue scriptObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generic client side Octree renderer class.
|
// Generic client side Octree renderer class.
|
||||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
|
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||||
|
@ -87,9 +82,6 @@ public:
|
||||||
/// hovering over, and entering entities
|
/// hovering over, and entering entities
|
||||||
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
||||||
|
|
||||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
|
||||||
virtual void errorInLoadingScript(const QUrl& url);
|
|
||||||
|
|
||||||
// For Scene.shouldRenderEntities
|
// For Scene.shouldRenderEntities
|
||||||
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
||||||
|
|
||||||
|
@ -137,7 +129,6 @@ private:
|
||||||
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
||||||
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
||||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||||
void checkAndCallUnload(const EntityItemID& entityID);
|
|
||||||
|
|
||||||
QList<Model*> _releasedModels;
|
QList<Model*> _releasedModels;
|
||||||
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
||||||
|
@ -155,16 +146,6 @@ private:
|
||||||
|
|
||||||
bool _wantScripts;
|
bool _wantScripts;
|
||||||
ScriptEngine* _entitiesScriptEngine;
|
ScriptEngine* _entitiesScriptEngine;
|
||||||
ScriptEngine* _sandboxScriptEngine;
|
|
||||||
|
|
||||||
QScriptValue loadEntityScript(EntityItemPointer entity, bool isPreload = false, bool reload = false);
|
|
||||||
QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false, bool reload = false);
|
|
||||||
QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID);
|
|
||||||
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url, bool& reload);
|
|
||||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
|
|
||||||
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent);
|
|
||||||
|
|
||||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
|
||||||
|
|
||||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||||
const EntityItemID& id, const Collision& collision);
|
const EntityItemID& id, const Collision& collision);
|
||||||
|
|
|
@ -17,21 +17,31 @@
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
QUrl ResourceManager::normalizeURL(const QUrl& url) {
|
||||||
auto scheme = url.scheme();
|
auto scheme = url.scheme();
|
||||||
|
if (!(scheme == URL_SCHEME_FILE ||
|
||||||
|
scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP ||
|
||||||
|
scheme == URL_SCHEME_ATP)) {
|
||||||
|
|
||||||
|
// check the degenerative file case: on windows we can often have urls of the form c:/filename
|
||||||
|
// this checks for and works around that case.
|
||||||
|
QUrl urlWithFileScheme{ URL_SCHEME_FILE + ":///" + url.toString() };
|
||||||
|
if (!urlWithFileScheme.toLocalFile().isEmpty()) {
|
||||||
|
return urlWithFileScheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
||||||
|
auto normalizedURL = normalizeURL(url);
|
||||||
|
auto scheme = normalizedURL.scheme();
|
||||||
if (scheme == URL_SCHEME_FILE) {
|
if (scheme == URL_SCHEME_FILE) {
|
||||||
return new FileResourceRequest(parent, url);
|
return new FileResourceRequest(parent, url);
|
||||||
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
||||||
return new HTTPResourceRequest(parent, url);
|
return new HTTPResourceRequest(parent, url);
|
||||||
} else if (scheme == URL_SCHEME_ATP) {
|
} else if (scheme == URL_SCHEME_ATP) {
|
||||||
return new AssetResourceRequest(parent, url);
|
return new AssetResourceRequest(parent, url);
|
||||||
} else {
|
|
||||||
// check the degenerative file case: on windows we can often have urls of the form c:/filename
|
|
||||||
// this checks for and works around that case.
|
|
||||||
QUrl urlWithFileScheme { URL_SCHEME_FILE + ":///" + url.toString() };
|
|
||||||
if (!urlWithFileScheme.toLocalFile().isEmpty()) {
|
|
||||||
return new FileResourceRequest(parent, urlWithFileScheme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url();
|
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url();
|
||||||
|
|
|
@ -24,6 +24,7 @@ const QString URL_SCHEME_ATP = "atp";
|
||||||
|
|
||||||
class ResourceManager {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
|
static QUrl normalizeURL(const QUrl& url);
|
||||||
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
|
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
nodeList->setOwnerType(nodeType);
|
nodeList->setOwnerType(nodeType);
|
||||||
|
|
||||||
_domainServerTimer = new QTimer();
|
_domainServerTimer = new QTimer(this);
|
||||||
connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
||||||
_domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
_domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultCC::DefaultCC() :
|
DefaultCC::DefaultCC() :
|
||||||
_lastRCTime(p_high_resolution_clock::now()),
|
|
||||||
_slowStartLastAck(_sendCurrSeqNum),
|
_slowStartLastAck(_sendCurrSeqNum),
|
||||||
_lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX })
|
_lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX })
|
||||||
{
|
{
|
||||||
|
|
|
@ -108,7 +108,8 @@ public:
|
||||||
private:
|
private:
|
||||||
void stopSlowStart(); // stops the slow start on loss or timeout
|
void stopSlowStart(); // stops the slow start on loss or timeout
|
||||||
|
|
||||||
p_high_resolution_clock::time_point _lastRCTime; // last rate increase time
|
p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::now(); // last rate increase time
|
||||||
|
|
||||||
bool _slowStart { true }; // if in slow start phase
|
bool _slowStart { true }; // if in slow start phase
|
||||||
SequenceNumber _slowStartLastAck; // last ACKed seq num
|
SequenceNumber _slowStartLastAck; // last ACKed seq num
|
||||||
bool _loss { false }; // if loss happened since last rate increase
|
bool _loss { false }; // if loss happened since last rate increase
|
||||||
|
|
|
@ -28,7 +28,6 @@ using namespace udt;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr<CongestionControl> congestionControl) :
|
Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr<CongestionControl> congestionControl) :
|
||||||
_connectionStart(p_high_resolution_clock::now()),
|
|
||||||
_parentSocket(parentSocket),
|
_parentSocket(parentSocket),
|
||||||
_destination(destination),
|
_destination(destination),
|
||||||
_congestionControl(move(congestionControl))
|
_congestionControl(move(congestionControl))
|
||||||
|
@ -278,10 +277,10 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) {
|
||||||
// pack in the receive speed and estimatedBandwidth
|
// pack in the receive speed and estimatedBandwidth
|
||||||
ackPacket->writePrimitive(packetReceiveSpeed);
|
ackPacket->writePrimitive(packetReceiveSpeed);
|
||||||
ackPacket->writePrimitive(estimatedBandwidth);
|
ackPacket->writePrimitive(estimatedBandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
// record this as the last ACK send time
|
// record this as the last ACK send time
|
||||||
lastACKSendTime = p_high_resolution_clock::now();
|
lastACKSendTime = p_high_resolution_clock::now();
|
||||||
}
|
|
||||||
|
|
||||||
// have the socket send off our packet
|
// have the socket send off our packet
|
||||||
_parentSocket->writeBasePacket(*ackPacket, _destination);
|
_parentSocket->writeBasePacket(*ackPacket, _destination);
|
||||||
|
@ -534,7 +533,8 @@ void Connection::processACK(std::unique_ptr<ControlPacket> controlPacket) {
|
||||||
// This will be the case if it has been longer than the sync interval OR
|
// This will be the case if it has been longer than the sync interval OR
|
||||||
// it looks like they haven't received our ACK2 for this ACK
|
// it looks like they haven't received our ACK2 for this ACK
|
||||||
auto currentTime = p_high_resolution_clock::now();
|
auto currentTime = p_high_resolution_clock::now();
|
||||||
static p_high_resolution_clock::time_point lastACK2SendTime;
|
static p_high_resolution_clock::time_point lastACK2SendTime =
|
||||||
|
p_high_resolution_clock::now() - std::chrono::microseconds(_synInterval);
|
||||||
|
|
||||||
microseconds sinceLastACK2 = duration_cast<microseconds>(currentTime - lastACK2SendTime);
|
microseconds sinceLastACK2 = duration_cast<microseconds>(currentTime - lastACK2SendTime);
|
||||||
|
|
||||||
|
@ -779,7 +779,7 @@ void Connection::resetReceiveState() {
|
||||||
|
|
||||||
// clear the loss list and _lastNAKTime
|
// clear the loss list and _lastNAKTime
|
||||||
_lossList.clear();
|
_lossList.clear();
|
||||||
_lastNAKTime = p_high_resolution_clock::time_point();
|
_lastNAKTime = p_high_resolution_clock::now();
|
||||||
|
|
||||||
// the _nakInterval need not be reset, that will happen on loss
|
// the _nakInterval need not be reset, that will happen on loss
|
||||||
|
|
||||||
|
|
|
@ -114,13 +114,14 @@ private:
|
||||||
|
|
||||||
int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss
|
int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss
|
||||||
int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms
|
int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms
|
||||||
p_high_resolution_clock::time_point _lastNAKTime;
|
p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::now();
|
||||||
|
|
||||||
bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server
|
bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server
|
||||||
bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client
|
bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client
|
||||||
|
|
||||||
p_high_resolution_clock::time_point _connectionStart; // holds the time_point for creation of this connection
|
p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection
|
||||||
p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender
|
p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender
|
||||||
|
|
||||||
bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection
|
bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection
|
||||||
|
|
||||||
LossList _lossList; // List of all missing packets
|
LossList _lossList; // List of all missing packets
|
||||||
|
|
|
@ -93,15 +93,16 @@ int32_t PacketTimeWindow::getEstimatedBandwidth() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PacketTimeWindow::onPacketArrival() {
|
void PacketTimeWindow::onPacketArrival() {
|
||||||
|
|
||||||
// take the current time
|
// take the current time
|
||||||
auto now = p_high_resolution_clock::now();
|
auto now = p_high_resolution_clock::now();
|
||||||
|
|
||||||
|
if (_packetIntervals.size() > 0) {
|
||||||
// record the interval between this packet and the last one
|
// record the interval between this packet and the last one
|
||||||
_packetIntervals[_currentPacketInterval++] = duration_cast<microseconds>(now - _lastPacketTime).count();
|
_packetIntervals[_currentPacketInterval++] = duration_cast<microseconds>(now - _lastPacketTime).count();
|
||||||
|
|
||||||
// reset the currentPacketInterval index when it wraps
|
// reset the currentPacketInterval index when it wraps
|
||||||
if (_currentPacketInterval == _numPacketIntervals) {
|
_currentPacketInterval %= _numPacketIntervals;
|
||||||
_currentPacketInterval = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember this as the last packet arrival time
|
// remember this as the last packet arrival time
|
||||||
|
@ -120,7 +121,5 @@ void PacketTimeWindow::onProbePair2Arrival() {
|
||||||
_probeIntervals[_currentProbeInterval++] = duration_cast<microseconds>(now - _firstProbeTime).count();
|
_probeIntervals[_currentProbeInterval++] = duration_cast<microseconds>(now - _firstProbeTime).count();
|
||||||
|
|
||||||
// reset the currentProbeInterval index when it wraps
|
// reset the currentProbeInterval index when it wraps
|
||||||
if (_currentProbeInterval == _numProbeIntervals) {
|
_currentProbeInterval %= _numProbeIntervals;
|
||||||
_currentProbeInterval = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@ private:
|
||||||
std::vector<int> _packetIntervals; // vector of microsecond intervals between packet arrivals
|
std::vector<int> _packetIntervals; // vector of microsecond intervals between packet arrivals
|
||||||
std::vector<int> _probeIntervals; // vector of microsecond intervals between probe pair arrivals
|
std::vector<int> _probeIntervals; // vector of microsecond intervals between probe pair arrivals
|
||||||
|
|
||||||
p_high_resolution_clock::time_point _lastPacketTime; // the time_point when last packet arrived
|
p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::now(); // the time_point when last packet arrived
|
||||||
p_high_resolution_clock::time_point _firstProbeTime; // the time_point when first probe in pair arrived
|
p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::now(); // the time_point when first probe in pair arrived
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,16 +280,12 @@ void SendQueue::run() {
|
||||||
// we haven't received a handshake ACK from the client
|
// we haven't received a handshake ACK from the client
|
||||||
// if it has been at least 100ms since we last sent a handshake, send another now
|
// if it has been at least 100ms since we last sent a handshake, send another now
|
||||||
|
|
||||||
// hold the time of last send in a static
|
|
||||||
static auto lastSendHandshake = p_high_resolution_clock::time_point();
|
|
||||||
|
|
||||||
static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100);
|
static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100);
|
||||||
|
|
||||||
// calculation the duration since the last handshake send
|
// hold the time of last send in a static
|
||||||
auto sinceLastHandshake = std::chrono::duration_cast<std::chrono::milliseconds>(p_high_resolution_clock::now()
|
static auto lastSendHandshake = p_high_resolution_clock::now() - HANDSHAKE_RESEND_INTERVAL_MS;
|
||||||
- lastSendHandshake);
|
|
||||||
|
|
||||||
if (sinceLastHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) {
|
if (p_high_resolution_clock::now() - lastSendHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) {
|
||||||
|
|
||||||
// it has been long enough since last handshake, send another
|
// it has been long enough since last handshake, send another
|
||||||
static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0);
|
static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0);
|
||||||
|
@ -299,9 +295,7 @@ void SendQueue::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we wait for the ACK or the re-send interval to expire
|
// we wait for the ACK or the re-send interval to expire
|
||||||
_handshakeACKCondition.wait_until(handshakeLock,
|
_handshakeACKCondition.wait_until(handshakeLock, p_high_resolution_clock::now() + HANDSHAKE_RESEND_INTERVAL_MS);
|
||||||
p_high_resolution_clock::now()
|
|
||||||
+ HANDSHAKE_RESEND_INTERVAL_MS);
|
|
||||||
|
|
||||||
// Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake.
|
// Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake.
|
||||||
// Either way let's continue processing - no packets will be sent if no handshake ACK has been received.
|
// Either way let's continue processing - no packets will be sent if no handshake ACK has been received.
|
||||||
|
|
|
@ -45,8 +45,6 @@ class SendQueue : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using time_point = p_high_resolution_clock::time_point;
|
|
||||||
|
|
||||||
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination);
|
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination);
|
||||||
|
|
||||||
void queuePacket(std::unique_ptr<Packet> packet);
|
void queuePacket(std::unique_ptr<Packet> packet);
|
||||||
|
|
|
@ -829,6 +829,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
||||||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
@ -838,6 +841,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
||||||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
@ -860,6 +866,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
renderPayload->addStatusGetters(statusGetters);
|
renderPayload->addStatusGetters(statusGetters);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
@ -870,6 +879,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
renderPayload->addStatusGetters(statusGetters);
|
renderPayload->addStatusGetters(statusGetters);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
somethingAdded = true;
|
somethingAdded = true;
|
||||||
}
|
}
|
||||||
|
@ -1283,6 +1295,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||||
|
|
||||||
//virtual
|
//virtual
|
||||||
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
_needsUpdateClusterMatrices = true;
|
||||||
_rig->updateAnimations(deltaTime, parentTransform);
|
_rig->updateAnimations(deltaTime, parentTransform);
|
||||||
}
|
}
|
||||||
void Model::simulateInternal(float deltaTime) {
|
void Model::simulateInternal(float deltaTime) {
|
||||||
|
@ -1293,6 +1306,10 @@ void Model::simulateInternal(float deltaTime) {
|
||||||
updateRig(deltaTime, parentTransform);
|
updateRig(deltaTime, parentTransform);
|
||||||
}
|
}
|
||||||
void Model::updateClusterMatrices() {
|
void Model::updateClusterMatrices() {
|
||||||
|
if (!_needsUpdateClusterMatrices) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_needsUpdateClusterMatrices = false;
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||||
|
@ -1803,13 +1820,15 @@ bool Model::initWhenReady(render::ScenePointer scene) {
|
||||||
segregateMeshGroups();
|
segregateMeshGroups();
|
||||||
|
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
|
|
||||||
foreach (auto renderItem, _transparentRenderItems) {
|
foreach (auto renderItem, _transparentRenderItems) {
|
||||||
auto item = scene->allocateID();
|
auto item = scene->allocateID();
|
||||||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (auto renderItem, _opaqueRenderItems) {
|
foreach (auto renderItem, _opaqueRenderItems) {
|
||||||
|
@ -1818,6 +1837,9 @@ bool Model::initWhenReady(render::ScenePointer scene) {
|
||||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||||
_renderItems.insert(item, renderPayload);
|
_renderItems.insert(item, renderPayload);
|
||||||
pendingChanges.resetItem(item, renderPayload);
|
pendingChanges.resetItem(item, renderPayload);
|
||||||
|
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||||
|
data.model->_needsUpdateClusterMatrices = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
scene->enqueuePendingChanges(pendingChanges);
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
|
|
||||||
|
|
|
@ -497,6 +497,7 @@ private:
|
||||||
QMap<render::ItemID, render::PayloadPointer> _renderItems;
|
QMap<render::ItemID, render::PayloadPointer> _renderItems;
|
||||||
bool _readyWhenAdded = false;
|
bool _readyWhenAdded = false;
|
||||||
bool _needsReload = true;
|
bool _needsReload = true;
|
||||||
|
bool _needsUpdateClusterMatrices = true;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RigPointer _rig;
|
RigPointer _rig;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QNetworkConfiguration>
|
#include <QNetworkConfiguration>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
@ -26,7 +27,8 @@ ScriptCache::ScriptCache(QObject* parent) {
|
||||||
// nothing to do here...
|
// nothing to do here...
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) {
|
QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool reload) {
|
||||||
|
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||||
QString scriptContents;
|
QString scriptContents;
|
||||||
if (_scriptCache.contains(url) && !reload) {
|
if (_scriptCache.contains(url) && !reload) {
|
||||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||||
|
@ -41,7 +43,7 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
||||||
if (alreadyWaiting) {
|
if (alreadyWaiting) {
|
||||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||||
} else {
|
} else {
|
||||||
auto request = ResourceManager::createResourceRequest(this, url);
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
request->setCacheEnabled(!reload);
|
request->setCacheEnabled(!reload);
|
||||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
||||||
request->send();
|
request->send();
|
||||||
|
@ -50,7 +52,8 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
||||||
return scriptContents;
|
return scriptContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptCache::deleteScript(const QUrl& url) {
|
void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
|
||||||
|
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||||
if (_scriptCache.contains(url)) {
|
if (_scriptCache.contains(url)) {
|
||||||
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
|
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
|
||||||
_scriptCache.remove(url);
|
_scriptCache.remove(url);
|
||||||
|
@ -79,4 +82,63 @@ void ScriptCache::scriptDownloaded() {
|
||||||
req->deleteLater();
|
req->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
QUrl unnormalizedURL(scriptOrURL);
|
||||||
|
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||||
|
|
||||||
|
// attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the entityScript use case)
|
||||||
|
if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) {
|
||||||
|
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_scriptCache.contains(url) && !forceDownload) {
|
||||||
|
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||||
|
#if 1 // def THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::getScriptContents() about to call contentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
contentAvailable(url.toString(), _scriptCache[url], true, true);
|
||||||
|
} else {
|
||||||
|
bool alreadyWaiting = _contentCallbacks.contains(url);
|
||||||
|
_contentCallbacks.insert(url, contentAvailable);
|
||||||
|
|
||||||
|
if (alreadyWaiting) {
|
||||||
|
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||||
|
} else {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
|
request->setCacheEnabled(!forceDownload);
|
||||||
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||||
|
request->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptCache::scriptContentAvailable() {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
|
||||||
|
QUrl url = req->getUrl();
|
||||||
|
QList<contentAvailableCallback> allCallbacks = _contentCallbacks.values(url);
|
||||||
|
_contentCallbacks.remove(url);
|
||||||
|
|
||||||
|
bool success = req->getResult() == ResourceRequest::Success;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
_scriptCache[url] = req->getData();
|
||||||
|
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||||
|
} else {
|
||||||
|
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||||
|
}
|
||||||
|
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
||||||
|
thisCallback(url.toString(), _scriptCache[url], true, success);
|
||||||
|
}
|
||||||
|
req->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,23 +20,33 @@ public:
|
||||||
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||||
|
|
||||||
/// Interface for loading scripts
|
/// Interface for loading scripts
|
||||||
class ScriptCache : public QObject, public Dependency {
|
class ScriptCache : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
|
||||||
void deleteScript(const QUrl& url);
|
|
||||||
|
|
||||||
|
QString getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
||||||
|
void deleteScript(const QUrl& unnormalizedURL);
|
||||||
|
|
||||||
|
// FIXME - how do we remove a script from the bad script list in the case of a redownload?
|
||||||
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
||||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void scriptDownloaded();
|
void scriptDownloaded(); // old version
|
||||||
|
void scriptContentAvailable(); // new version
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptCache(QObject* parent = NULL);
|
ScriptCache(QObject* parent = NULL);
|
||||||
|
|
||||||
|
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
||||||
|
|
||||||
QHash<QUrl, QString> _scriptCache;
|
QHash<QUrl, QString> _scriptCache;
|
||||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||||
QSet<QUrl> _badScripts;
|
QSet<QUrl> _badScripts;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
#include <QScriptEngine>
|
#include <QScriptEngine>
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
#include <AudioConstants.h>
|
#include <AudioConstants.h>
|
||||||
#include <AudioEffectOptions.h>
|
#include <AudioEffectOptions.h>
|
||||||
|
@ -44,6 +45,8 @@
|
||||||
|
|
||||||
#include "MIDIEvent.h"
|
#include "MIDIEvent.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||||
|
static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||||
|
|
||||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||||
QString message = "";
|
QString message = "";
|
||||||
|
@ -87,17 +90,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
||||||
_isFinished(false),
|
_isFinished(false),
|
||||||
_isRunning(false),
|
_isRunning(false),
|
||||||
_isInitialized(false),
|
_isInitialized(false),
|
||||||
_isAvatar(false),
|
|
||||||
_avatarIdentityTimer(NULL),
|
|
||||||
_avatarBillboardTimer(NULL),
|
|
||||||
_timerFunctionMap(),
|
_timerFunctionMap(),
|
||||||
_isListeningToAudioStream(false),
|
|
||||||
_avatarSound(NULL),
|
|
||||||
_numAvatarSoundSentBytes(0),
|
|
||||||
_wantSignals(wantSignals),
|
_wantSignals(wantSignals),
|
||||||
_controllerScriptingInterface(controllerScriptingInterface),
|
_controllerScriptingInterface(controllerScriptingInterface),
|
||||||
_avatarData(NULL),
|
|
||||||
_scriptName(),
|
|
||||||
_fileNameString(fileNameString),
|
_fileNameString(fileNameString),
|
||||||
_quatLibrary(),
|
_quatLibrary(),
|
||||||
_vec3Library(),
|
_vec3Library(),
|
||||||
|
@ -122,6 +117,29 @@ ScriptEngine::~ScriptEngine() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::runInThread() {
|
||||||
|
QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete
|
||||||
|
QString scriptEngineName = QString("Script Thread:") + getFilename();
|
||||||
|
workerThread->setObjectName(scriptEngineName);
|
||||||
|
|
||||||
|
// when the worker thread is started, call our engine's run..
|
||||||
|
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
|
||||||
|
|
||||||
|
// tell the thread to stop when the script engine is done
|
||||||
|
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
|
||||||
|
|
||||||
|
// when the thread is finished, add thread to the deleteLater queue
|
||||||
|
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
||||||
|
|
||||||
|
// when the thread is finished, add scriptEngine to the deleteLater queue
|
||||||
|
connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater);
|
||||||
|
|
||||||
|
moveToThread(workerThread);
|
||||||
|
|
||||||
|
// Starts an event loop, and emits workerThread->started()
|
||||||
|
workerThread->start();
|
||||||
|
}
|
||||||
|
|
||||||
QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
|
QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
|
||||||
QMutex ScriptEngine::_allScriptsMutex;
|
QMutex ScriptEngine::_allScriptsMutex;
|
||||||
bool ScriptEngine::_stoppingAllScripts = false;
|
bool ScriptEngine::_stoppingAllScripts = false;
|
||||||
|
@ -180,8 +198,6 @@ void ScriptEngine::stopAllScripts(QObject* application) {
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::waitTillDoneRunning() {
|
void ScriptEngine::waitTillDoneRunning() {
|
||||||
QString scriptName = getFilename();
|
|
||||||
|
|
||||||
// If the script never started running or finished running before we got here, we don't need to wait for it
|
// If the script never started running or finished running before we got here, we don't need to wait for it
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
|
|
||||||
|
@ -209,58 +225,7 @@ QString ScriptEngine::getFilename() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
_isAvatar = isAvatar;
|
|
||||||
|
|
||||||
if (_isAvatar && !_avatarIdentityTimer) {
|
|
||||||
// set up the avatar timers
|
|
||||||
_avatarIdentityTimer = new QTimer(this);
|
|
||||||
_avatarBillboardTimer = new QTimer(this);
|
|
||||||
|
|
||||||
// connect our slot
|
|
||||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
|
|
||||||
connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket);
|
|
||||||
|
|
||||||
// start the timers
|
|
||||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
|
||||||
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isAvatar) {
|
|
||||||
delete _avatarIdentityTimer;
|
|
||||||
_avatarIdentityTimer = NULL;
|
|
||||||
delete _avatarBillboardTimer;
|
|
||||||
_avatarBillboardTimer = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
|
||||||
_avatarData = avatarData;
|
|
||||||
|
|
||||||
// remove the old Avatar property, if it exists
|
|
||||||
globalObject().setProperty(objectName, QScriptValue());
|
|
||||||
|
|
||||||
// give the script engine the new Avatar script property
|
|
||||||
registerGlobalObject(objectName, _avatarData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) {
|
|
||||||
// remove the old Avatar property, if it exists
|
|
||||||
globalObject().setProperty(objectName, QScriptValue());
|
|
||||||
|
|
||||||
// give the script engine the new avatar hash map
|
|
||||||
registerGlobalObject(objectName, avatarHashMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
|
||||||
if (_isRunning) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_scriptContents = scriptContents;
|
|
||||||
_fileNameString = fileNameString;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
return;
|
return;
|
||||||
|
@ -271,38 +236,12 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||||
|
|
||||||
QUrl url(scriptURL);
|
QUrl url(scriptURL);
|
||||||
|
|
||||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
|
||||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
|
||||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
|
||||||
url = QUrl::fromLocalFile(_fileNameString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok, let's see if it's valid... and if so, load it
|
|
||||||
if (url.isValid()) {
|
|
||||||
if (url.scheme() == "file") {
|
|
||||||
_fileNameString = url.toLocalFile();
|
|
||||||
QFile scriptFile(_fileNameString);
|
|
||||||
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
|
||||||
qCDebug(scriptengine) << "ScriptEngine loading file:" << _fileNameString;
|
|
||||||
QTextStream in(&scriptFile);
|
|
||||||
_scriptContents = in.readAll();
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit scriptLoaded(_fileNameString);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCDebug(scriptengine) << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__;
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit errorLoadingScript(_fileNameString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bool isPending;
|
bool isPending;
|
||||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||||
scriptCache->getScript(url, this, isPending, reload);
|
scriptCache->getScript(url, this, isPending, reload);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||||
_scriptContents = scriptContents;
|
_scriptContents = scriptContents;
|
||||||
if (_wantSignals) {
|
if (_wantSignals) {
|
||||||
|
@ -310,6 +249,7 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||||
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
||||||
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
||||||
if (_wantSignals) {
|
if (_wantSignals) {
|
||||||
|
@ -383,57 +323,117 @@ void ScriptEngine::init() {
|
||||||
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
|
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerGlobalObject",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QObject*, object));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (object) {
|
if (object) {
|
||||||
QScriptValue value = newQObject(object);
|
QScriptValue value = newQObject(object);
|
||||||
globalObject().setProperty(name, value);
|
globalObject().setProperty(name, value);
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
return QScriptValue::NullValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
|
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
|
||||||
registerFunction(globalObject(), name, fun, numArguments);
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerFunction",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
|
||||||
|
Q_ARG(int, numArguments));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
|
||||||
|
globalObject().setProperty(name, scriptFun);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
|
void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
|
||||||
QScriptValue scriptFun = newFunction(fun, numArguments);
|
if (QThread::currentThread() != thread()) {
|
||||||
parent.setProperty(name, scriptFun);
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerFunction",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
|
||||||
|
Q_ARG(int, numArguments));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QScriptValue object = globalObject().property(parent);
|
||||||
|
if (object.isValid()) {
|
||||||
|
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
|
||||||
|
object.setProperty(name, scriptFun);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
||||||
QScriptEngine::FunctionSignature setter, QScriptValue object) {
|
QScriptEngine::FunctionSignature setter, const QString& parent) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
" name:" << name << "parent:" << parent;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "registerGetterSetter",
|
||||||
|
Q_ARG(const QString&, name),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, getter),
|
||||||
|
Q_ARG(QScriptEngine::FunctionSignature, setter),
|
||||||
|
Q_ARG(const QString&, parent));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;
|
||||||
|
#endif
|
||||||
|
|
||||||
QScriptValue setterFunction = newFunction(setter, 1);
|
QScriptValue setterFunction = newFunction(setter, 1);
|
||||||
QScriptValue getterFunction = newFunction(getter);
|
QScriptValue getterFunction = newFunction(getter);
|
||||||
|
|
||||||
if (!object.isNull()) {
|
if (!parent.isNull()) {
|
||||||
|
QScriptValue object = globalObject().property(parent);
|
||||||
|
if (object.isValid()) {
|
||||||
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||||
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||||
globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
|
||||||
void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) {
|
|
||||||
if (!_registeredHandlers.contains(entityID)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
|
|
||||||
if (!handlersOnEntity.contains(eventName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QScriptValueList handlersForEvent = handlersOnEntity[eventName];
|
|
||||||
if (!handlersForEvent.isEmpty()) {
|
|
||||||
QScriptValueList args = argGenerator();
|
|
||||||
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
|
||||||
handlersForEvent[i].call(QScriptValue(), args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Unregister the handlers for this eventName and entityID.
|
// Unregister the handlers for this eventName and entityID.
|
||||||
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << " eventName:" << eventName;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "removeEventHandler",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, eventName),
|
||||||
|
Q_ARG(QScriptValue, handler));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!_registeredHandlers.contains(entityID)) {
|
if (!_registeredHandlers.contains(entityID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -449,6 +449,22 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin
|
||||||
}
|
}
|
||||||
// Register the handler.
|
// Register the handler.
|
||||||
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << " eventName:" << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "addEventHandler",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, eventName),
|
||||||
|
Q_ARG(QScriptValue, handler));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script...
|
if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script...
|
||||||
// Connect up ALL the handlers to the global entities object's signals.
|
// Connect up ALL the handlers to the global entities object's signals.
|
||||||
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
|
// (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
|
||||||
|
@ -503,34 +519,25 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ScriptEngine::evaluate() {
|
|
||||||
if (_stoppingAllScripts) {
|
|
||||||
return; // bail early
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isInitialized) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue result = evaluate(_scriptContents);
|
|
||||||
|
|
||||||
// TODO: why do we check this twice? It seems like the call to clearExceptions() in the lower level evaluate call
|
|
||||||
// will cause this code to never actually run...
|
|
||||||
if (hasUncaughtException()) {
|
|
||||||
int line = uncaughtExceptionLineNumber();
|
|
||||||
qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
|
|
||||||
if (_wantSignals) {
|
|
||||||
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
|
|
||||||
}
|
|
||||||
clearExceptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
|
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
|
||||||
if (_stoppingAllScripts) {
|
if (_stoppingAllScripts) {
|
||||||
return QScriptValue(); // bail early
|
return QScriptValue(); // bail early
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QScriptValue result;
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber;
|
||||||
|
#endif
|
||||||
|
QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QScriptValue, result),
|
||||||
|
Q_ARG(const QString&, program),
|
||||||
|
Q_ARG(const QString&, fileName),
|
||||||
|
Q_ARG(int, lineNumber));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
_evaluatesPending++;
|
_evaluatesPending++;
|
||||||
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
||||||
if (hasUncaughtException()) {
|
if (hasUncaughtException()) {
|
||||||
|
@ -545,18 +552,6 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::sendAvatarIdentityPacket() {
|
|
||||||
if (_isAvatar && _avatarData) {
|
|
||||||
_avatarData->sendIdentityPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::sendAvatarBillboardPacket() {
|
|
||||||
if (_isAvatar && _avatarData) {
|
|
||||||
_avatarData->sendBillboardPacket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::run() {
|
void ScriptEngine::run() {
|
||||||
// TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
|
// TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
|
||||||
// we're in the process of stopping?
|
// we're in the process of stopping?
|
||||||
|
@ -608,107 +603,6 @@ void ScriptEngine::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isFinished && _isAvatar && _avatarData) {
|
|
||||||
|
|
||||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
|
|
||||||
/ (1000 * 1000)) + 0.5);
|
|
||||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
|
||||||
|
|
||||||
QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
|
||||||
_avatarData->doneEncoding(true);
|
|
||||||
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
|
|
||||||
|
|
||||||
avatarPacket->write(avatarByteArray);
|
|
||||||
|
|
||||||
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
|
||||||
|
|
||||||
if (_isListeningToAudioStream || _avatarSound) {
|
|
||||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
|
||||||
bool silentFrame = true;
|
|
||||||
|
|
||||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
|
||||||
const int16_t* nextSoundOutput = NULL;
|
|
||||||
|
|
||||||
if (_avatarSound) {
|
|
||||||
|
|
||||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
|
||||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
|
||||||
+ _numAvatarSoundSentBytes);
|
|
||||||
|
|
||||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
|
||||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
|
||||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
|
||||||
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
|
||||||
|
|
||||||
|
|
||||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
|
||||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
|
||||||
if (nextSoundOutput[i] != 0) {
|
|
||||||
silentFrame = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
|
||||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
|
||||||
// we're done with this sound object - so set our pointer back to NULL
|
|
||||||
// and our sent bytes back to zero
|
|
||||||
_avatarSound = NULL;
|
|
||||||
_numAvatarSoundSentBytes = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto audioPacket = NLPacket::create(silentFrame
|
|
||||||
? PacketType::SilentAudioFrame
|
|
||||||
: PacketType::MicrophoneAudioNoEcho);
|
|
||||||
|
|
||||||
// seek past the sequence number, will be packed when destination node is known
|
|
||||||
audioPacket->seek(sizeof(quint16));
|
|
||||||
|
|
||||||
if (silentFrame) {
|
|
||||||
if (!_isListeningToAudioStream) {
|
|
||||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write the number of silent samples so the audio-mixer can uphold timing
|
|
||||||
audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
|
||||||
audioPacket->writePrimitive(_avatarData->getPosition());
|
|
||||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
|
||||||
audioPacket->writePrimitive(headOrientation);
|
|
||||||
|
|
||||||
} else if (nextSoundOutput) {
|
|
||||||
// assume scripted avatar audio is mono and set channel flag to zero
|
|
||||||
audioPacket->writePrimitive((quint8) 0);
|
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
|
||||||
audioPacket->writePrimitive(_avatarData->getPosition());
|
|
||||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
|
||||||
audioPacket->writePrimitive(headOrientation);
|
|
||||||
|
|
||||||
// write the raw audio data
|
|
||||||
audioPacket->write(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// write audio packet to AudioMixer nodes
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){
|
|
||||||
// only send to nodes of type AudioMixer
|
|
||||||
if (node->getType() == NodeType::AudioMixer) {
|
|
||||||
// pack sequence number
|
|
||||||
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
|
||||||
audioPacket->seek(0);
|
|
||||||
audioPacket->writePrimitive(sequence);
|
|
||||||
|
|
||||||
// send audio packet
|
|
||||||
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 now = usecTimestampNow();
|
qint64 now = usecTimestampNow();
|
||||||
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||||
|
|
||||||
|
@ -735,9 +629,6 @@ void ScriptEngine::run() {
|
||||||
emit scriptEnding();
|
emit scriptEnding();
|
||||||
}
|
}
|
||||||
|
|
||||||
// kill the avatar identity timer
|
|
||||||
delete _avatarIdentityTimer;
|
|
||||||
|
|
||||||
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
||||||
// release the queue of edit entity messages.
|
// release the queue of edit entity messages.
|
||||||
entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();
|
entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();
|
||||||
|
@ -967,6 +858,256 @@ void ScriptEngine::load(const QString& loadFile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
|
||||||
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_registeredHandlers.contains(entityID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
|
||||||
|
if (!handlersOnEntity.contains(eventName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QScriptValueList handlersForEvent = handlersOnEntity[eventName];
|
||||||
|
if (!handlersForEvent.isEmpty()) {
|
||||||
|
QScriptValueList args = argGenerator();
|
||||||
|
for (int i = 0; i < handlersForEvent.count(); ++i) {
|
||||||
|
handlersForEvent[i].call(QScriptValue(), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||||
|
// for the download
|
||||||
|
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "loadEntityScript",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, entityScript),
|
||||||
|
Q_ARG(bool, forceRedownload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If we've been called our known entityScripts should not know about us..
|
||||||
|
assert(!_entityScripts.contains(entityID));
|
||||||
|
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
|
||||||
|
}, forceRedownload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||||
|
// for the download
|
||||||
|
void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "scriptOrURL:" << scriptOrURL << "contents:" << contents << "isURL:" << isURL << "success:" << success;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "entityScriptContentAvailable",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, scriptOrURL),
|
||||||
|
Q_ARG(const QString&, contents),
|
||||||
|
Q_ARG(bool, isURL),
|
||||||
|
Q_ARG(bool, success));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::entityScriptContentAvailable() thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||||
|
|
||||||
|
// first check the syntax of the script contents
|
||||||
|
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents);
|
||||||
|
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||||
|
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
|
||||||
|
qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":"
|
||||||
|
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
|
||||||
|
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
|
||||||
|
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||||
|
return; // done processing script
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isURL) {
|
||||||
|
setParentURL(scriptOrURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptEngine sandbox;
|
||||||
|
QScriptValue testConstructor = sandbox.evaluate(contents);
|
||||||
|
|
||||||
|
if (!testConstructor.isFunction()) {
|
||||||
|
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID;
|
||||||
|
qCDebug(scriptengine) << " NOT CONSTRUCTOR";
|
||||||
|
qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL;
|
||||||
|
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||||
|
return; // done processing script
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue entityScriptConstructor = evaluate(contents);
|
||||||
|
|
||||||
|
QScriptValue entityScriptObject = entityScriptConstructor.construct();
|
||||||
|
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject };
|
||||||
|
_entityScripts[entityID] = newDetails;
|
||||||
|
if (isURL) {
|
||||||
|
setParentURL("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got this far, then call the preload method
|
||||||
|
callEntityScriptMethod(entityID, "preload");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "unloadEntityScript",
|
||||||
|
Q_ARG(const EntityItemID&, entityID));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::unloadEntityScript() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_entityScripts.contains(entityID)) {
|
||||||
|
callEntityScriptMethod(entityID, "unload");
|
||||||
|
_entityScripts.remove(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::unloadAllEntityScripts() {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "unloadAllEntityScripts");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
|
||||||
|
#endif
|
||||||
|
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
|
||||||
|
callEntityScriptMethod(entityID, "unload");
|
||||||
|
}
|
||||||
|
_entityScripts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, methodName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_entityScripts.contains(entityID)) {
|
||||||
|
EntityScriptDetails details = _entityScripts[entityID];
|
||||||
|
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||||
|
if (entityScript.property(methodName).isFunction()) {
|
||||||
|
QScriptValueList args;
|
||||||
|
args << entityID.toScriptValue(this);
|
||||||
|
entityScript.property(methodName).call(entityScript, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, methodName),
|
||||||
|
Q_ARG(const MouseEvent&, event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_entityScripts.contains(entityID)) {
|
||||||
|
EntityScriptDetails details = _entityScripts[entityID];
|
||||||
|
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||||
|
if (entityScript.property(methodName).isFunction()) {
|
||||||
|
QScriptValueList args;
|
||||||
|
args << entityID.toScriptValue(this);
|
||||||
|
args << event.toScriptValue(this);
|
||||||
|
entityScript.property(methodName).call(entityScript, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
|
||||||
|
Q_ARG(const EntityItemID&, entityID),
|
||||||
|
Q_ARG(const QString&, methodName),
|
||||||
|
Q_ARG(const EntityItemID&, otherID),
|
||||||
|
Q_ARG(const Collision&, collision));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef THREAD_DEBUGGING
|
||||||
|
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
|
||||||
|
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_entityScripts.contains(entityID)) {
|
||||||
|
EntityScriptDetails details = _entityScripts[entityID];
|
||||||
|
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||||
|
if (entityScript.property(methodName).isFunction()) {
|
||||||
|
QScriptValueList args;
|
||||||
|
args << entityID.toScriptValue(this);
|
||||||
|
args << otherID.toScriptValue(this);
|
||||||
|
args << collisionToScriptValue(this, collision);
|
||||||
|
entityScript.property(methodName).call(entityScript, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,12 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1
|
||||||
|
|
||||||
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
|
typedef QHash<QString, QScriptValueList> RegisteredEventHandlers;
|
||||||
|
|
||||||
|
class EntityScriptDetails {
|
||||||
|
public:
|
||||||
|
QString scriptText;
|
||||||
|
QScriptValue scriptObject;
|
||||||
|
};
|
||||||
|
|
||||||
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -50,79 +56,86 @@ public:
|
||||||
|
|
||||||
~ScriptEngine();
|
~ScriptEngine();
|
||||||
|
|
||||||
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
/// run the script in a dedicated thread. This will have the side effect of evalulating
|
||||||
|
/// the current script contents and calling run(). Callers will likely want to register the script with external
|
||||||
|
/// services before calling this.
|
||||||
|
void runInThread();
|
||||||
|
|
||||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
/// run the script in the callers thread, exit when stop() is called.
|
||||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
void run();
|
||||||
|
|
||||||
const QString& getScriptName() const { return _scriptName; }
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
|
||||||
|
// properly ensure they are only called on the correct thread
|
||||||
|
|
||||||
QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
/// registers a global object by name
|
||||||
void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object);
|
||||||
QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue);
|
|
||||||
void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
|
/// registers a global getter/setter
|
||||||
void registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun,
|
Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
||||||
|
QScriptEngine::FunctionSignature setter, const QString& parent = QString(""));
|
||||||
|
|
||||||
|
/// register a global function
|
||||||
|
Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
|
||||||
|
|
||||||
|
/// register a function as a method on a previously registered global object
|
||||||
|
Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun,
|
||||||
int numArguments = -1);
|
int numArguments = -1);
|
||||||
|
|
||||||
Q_INVOKABLE void setIsAvatar(bool isAvatar);
|
/// evaluate some code in the context of the ScriptEngine and return the result
|
||||||
bool isAvatar() const { return _isAvatar; }
|
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); // this is also used by the script tool widget
|
||||||
|
|
||||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
/// if the script engine is not already running, this will download the URL and start the process of seting it up
|
||||||
void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName);
|
/// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed
|
||||||
|
/// to scripts. we may not need this to be invokable
|
||||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
void loadURL(const QUrl& scriptURL, bool reload);
|
||||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
|
||||||
|
|
||||||
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
|
||||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void run(); /// runs continuously until Agent.stop() is called
|
|
||||||
void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller
|
|
||||||
|
|
||||||
void timerFired();
|
|
||||||
|
|
||||||
bool hasScript() const { return !_scriptContents.isEmpty(); }
|
|
||||||
|
|
||||||
bool isFinished() const { return _isFinished; }
|
|
||||||
bool isRunning() const { return _isRunning; }
|
|
||||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
|
||||||
|
|
||||||
void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; }
|
|
||||||
bool isUserLoaded() const { return _isUserLoaded; }
|
|
||||||
|
|
||||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
|
||||||
|
|
||||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
|
||||||
|
|
||||||
QString getFilename() const;
|
|
||||||
|
|
||||||
static void stopAllScripts(QObject* application);
|
|
||||||
|
|
||||||
void waitTillDoneRunning();
|
|
||||||
|
|
||||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
|
||||||
virtual void errorInLoadingScript(const QUrl& url);
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - these are intended to be public interfaces available to scripts
|
||||||
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||||
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||||
|
|
||||||
public slots:
|
Q_INVOKABLE void load(const QString& loadfile);
|
||||||
void loadURL(const QUrl& scriptURL, bool reload);
|
Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
||||||
void stop();
|
Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
||||||
|
|
||||||
QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1);
|
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||||
QObject* setInterval(const QScriptValue& function, int intervalMS);
|
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
Q_INVOKABLE void print(const QString& message);
|
||||||
void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
||||||
void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
|
||||||
void load(const QString& loadfile);
|
|
||||||
void print(const QString& message);
|
|
||||||
QUrl resolvePath(const QString& path) const;
|
|
||||||
|
|
||||||
void nodeKilled(SharedNodePointer node);
|
// Entity Script Related methods
|
||||||
|
Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded
|
||||||
|
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method
|
||||||
|
Q_INVOKABLE void unloadAllEntityScripts();
|
||||||
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName);
|
||||||
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event);
|
||||||
|
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts
|
||||||
|
Q_INVOKABLE void stop();
|
||||||
|
|
||||||
|
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
|
||||||
|
bool isRunning() const { return _isRunning; } // used by ScriptWidget
|
||||||
|
|
||||||
|
static void stopAllScripts(QObject* application); // used by Application on shutdown
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE - These are the callback implementations for ScriptUser the get called by ScriptCache when the contents
|
||||||
|
// of a script are available.
|
||||||
|
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||||
|
virtual void errorInLoadingScript(const QUrl& url);
|
||||||
|
|
||||||
|
// These are currently used by Application to track if a script is user loaded or not. Consider finding a solution
|
||||||
|
// inside of Application so that the ScriptEngine class is not polluted by this notion
|
||||||
|
void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; }
|
||||||
|
bool isUserLoaded() const { return _isUserLoaded; }
|
||||||
|
|
||||||
|
// NOTE - this is used by the TypedArray implemetation. we need to review this for thread safety
|
||||||
|
ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void scriptLoaded(const QString& scriptFilename);
|
void scriptLoaded(const QString& scriptFilename);
|
||||||
|
@ -146,28 +159,24 @@ protected:
|
||||||
bool _isRunning;
|
bool _isRunning;
|
||||||
int _evaluatesPending = 0;
|
int _evaluatesPending = 0;
|
||||||
bool _isInitialized;
|
bool _isInitialized;
|
||||||
bool _isAvatar;
|
|
||||||
QTimer* _avatarIdentityTimer;
|
|
||||||
QTimer* _avatarBillboardTimer;
|
|
||||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||||
bool _isListeningToAudioStream;
|
|
||||||
Sound* _avatarSound;
|
|
||||||
int _numAvatarSoundSentBytes;
|
|
||||||
bool _isAgent = false;
|
|
||||||
QSet<QUrl> _includedURLs;
|
QSet<QUrl> _includedURLs;
|
||||||
bool _wantSignals = true;
|
bool _wantSignals = true;
|
||||||
|
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||||
private:
|
private:
|
||||||
|
void init();
|
||||||
|
QString getFilename() const;
|
||||||
|
void waitTillDoneRunning();
|
||||||
|
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||||
|
void timerFired();
|
||||||
void stopAllTimers();
|
void stopAllTimers();
|
||||||
void sendAvatarIdentityPacket();
|
|
||||||
void sendAvatarBillboardPacket();
|
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||||
|
|
||||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||||
void stopTimer(QTimer* timer);
|
void stopTimer(QTimer* timer);
|
||||||
|
|
||||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||||
AvatarData* _avatarData;
|
|
||||||
QString _scriptName;
|
|
||||||
QString _fileNameString;
|
QString _fileNameString;
|
||||||
Quat _quatLibrary;
|
Quat _quatLibrary;
|
||||||
Vec3 _vec3Library;
|
Vec3 _vec3Library;
|
||||||
|
@ -177,15 +186,15 @@ private:
|
||||||
|
|
||||||
ArrayBufferClass* _arrayBufferClass;
|
ArrayBufferClass* _arrayBufferClass;
|
||||||
|
|
||||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
|
||||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||||
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator);
|
void generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator);
|
||||||
|
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
||||||
|
|
||||||
private:
|
|
||||||
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||||
static QMutex _allScriptsMutex;
|
static QMutex _allScriptsMutex;
|
||||||
static bool _stoppingAllScripts;
|
static bool _stoppingAllScripts;
|
||||||
static bool _doneRunningThisScript;
|
static bool _doneRunningThisScript;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptEngine_h
|
#endif // hifi_ScriptEngine_h
|
||||||
|
|
|
@ -35,7 +35,6 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
|
||||||
joint.freeLineage.clear();
|
joint.freeLineage.clear();
|
||||||
joint.parentIndex = -1;
|
joint.parentIndex = -1;
|
||||||
joint.distanceToParent = 1.0f;
|
joint.distanceToParent = 1.0f;
|
||||||
joint.boneRadius = 1.0f;
|
|
||||||
|
|
||||||
joint.translation = origin; // T
|
joint.translation = origin; // T
|
||||||
joint.preTransform = glm::mat4(); // Roff * Rp
|
joint.preTransform = glm::mat4(); // Roff * Rp
|
||||||
|
@ -96,11 +95,11 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
|
||||||
|
|
||||||
void AnimInverseKinematicsTests::testSingleChain() {
|
void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
std::vector<FBXJoint> fbxJoints;
|
std::vector<FBXJoint> fbxJoints;
|
||||||
AnimPose offset;
|
makeTestFBXJoints(fbxJoints);
|
||||||
makeTestFBXJoints(fbxJoints, offset);
|
|
||||||
|
|
||||||
// create a skeleton and doll
|
// create a skeleton and doll
|
||||||
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints);
|
AnimPose offset;
|
||||||
|
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints, offset);
|
||||||
AnimSkeleton::Pointer skeletonPtr(skeleton);
|
AnimSkeleton::Pointer skeletonPtr(skeleton);
|
||||||
AnimInverseKinematics ikDoll("doll");
|
AnimInverseKinematics ikDoll("doll");
|
||||||
ikDoll.setSkeleton(skeletonPtr);
|
ikDoll.setSkeleton(skeletonPtr);
|
||||||
|
@ -130,10 +129,13 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
//
|
//
|
||||||
// A------>B------>C------>D
|
// A------>B------>C------>D
|
||||||
//
|
//
|
||||||
int indexD = 3;
|
|
||||||
glm::vec3 targetPosition(2.0f, 1.0f, 0.0f);
|
glm::vec3 targetPosition(2.0f, 1.0f, 0.0f);
|
||||||
glm::quat targetRotation = glm::angleAxis(PI / 2.0f, zAxis);
|
glm::quat targetRotation = glm::angleAxis(PI / 2.0f, zAxis);
|
||||||
ikDoll.updateTarget(indexD, targetPosition, targetRotation);
|
AnimVariantMap varMap;
|
||||||
|
varMap.set("positionD", targetPosition);
|
||||||
|
varMap.set("rotationD", targetRotation);
|
||||||
|
ikDoll.setTargetVars("D", "positionD", "rotationD");
|
||||||
|
AnimNode::Triggers triggers;
|
||||||
|
|
||||||
// the IK solution should be:
|
// the IK solution should be:
|
||||||
//
|
//
|
||||||
|
@ -143,7 +145,7 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
// A------>B------>C
|
// A------>B------>C
|
||||||
//
|
//
|
||||||
float dt = 1.0f;
|
float dt = 1.0f;
|
||||||
ikDoll.evaluate(dt);
|
ikDoll.evaluate(varMap, dt, triggers);
|
||||||
|
|
||||||
// verify absolute results
|
// verify absolute results
|
||||||
// NOTE: since we expect this solution to converge very quickly (one loop)
|
// NOTE: since we expect this solution to converge very quickly (one loop)
|
||||||
|
@ -204,17 +206,20 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
||||||
// |
|
// |
|
||||||
// A------>B t
|
// A------>B t
|
||||||
//
|
//
|
||||||
int indexD = 3;
|
|
||||||
glm::vec3 targetPosition(3.0f, 0.0f, 0.0f);
|
glm::vec3 targetPosition(3.0f, 0.0f, 0.0f);
|
||||||
glm::quat targetRotation = identity;
|
glm::quat targetRotation = identity;
|
||||||
ikDoll.updateTarget(indexD, targetPosition, targetRotation);
|
AnimVariantMap varMap;
|
||||||
|
varMap.set("positionD", targetPosition);
|
||||||
|
varMap.set("rotationD", targetRotation);
|
||||||
|
ikDoll.setTargetVars("D", "positionD", "rotationD");
|
||||||
|
AnimNode::Triggers triggers;
|
||||||
|
|
||||||
// the IK solution should be:
|
// the IK solution should be:
|
||||||
//
|
//
|
||||||
// A------>B------>C------>D
|
// A------>B------>C------>D
|
||||||
//
|
//
|
||||||
float dt = 1.0f;
|
float dt = 1.0f;
|
||||||
ikDoll.evaluate(dt);
|
ikDoll.evaluate(varMap, dt, triggers);
|
||||||
|
|
||||||
// verify absolute results
|
// verify absolute results
|
||||||
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
|
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
|
||||||
|
|
|
@ -193,6 +193,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
||||||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||||
glm::quat outputRotation = inputRotation;
|
glm::quat outputRotation = inputRotation;
|
||||||
|
|
||||||
|
shoulder.clearHistory();
|
||||||
bool updated = shoulder.apply(outputRotation);
|
bool updated = shoulder.apply(outputRotation);
|
||||||
QVERIFY(updated == false);
|
QVERIFY(updated == false);
|
||||||
QCOMPARE_WITH_ABS_ERROR(inputRotation, outputRotation, EPSILON);
|
QCOMPARE_WITH_ABS_ERROR(inputRotation, outputRotation, EPSILON);
|
||||||
|
@ -223,6 +224,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
||||||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||||
glm::quat outputRotation = inputRotation;
|
glm::quat outputRotation = inputRotation;
|
||||||
|
|
||||||
|
shoulder.clearHistory();
|
||||||
bool updated = shoulder.apply(outputRotation);
|
bool updated = shoulder.apply(outputRotation);
|
||||||
QVERIFY(updated == true);
|
QVERIFY(updated == true);
|
||||||
|
|
||||||
|
@ -257,6 +259,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
||||||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||||
glm::quat outputRotation = inputRotation;
|
glm::quat outputRotation = inputRotation;
|
||||||
|
|
||||||
|
shoulder.clearHistory();
|
||||||
bool updated = shoulder.apply(outputRotation);
|
bool updated = shoulder.apply(outputRotation);
|
||||||
QVERIFY(updated == true);
|
QVERIFY(updated == true);
|
||||||
|
|
||||||
|
@ -291,6 +294,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
||||||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||||
glm::quat outputRotation = inputRotation;
|
glm::quat outputRotation = inputRotation;
|
||||||
|
|
||||||
|
shoulder.clearHistory();
|
||||||
bool updated = shoulder.apply(outputRotation);
|
bool updated = shoulder.apply(outputRotation);
|
||||||
QVERIFY(updated == true);
|
QVERIFY(updated == true);
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,238 @@
|
||||||
},
|
},
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandOverlay",
|
||||||
|
"type": "overlay",
|
||||||
|
"data": {
|
||||||
|
"alpha": 1.0,
|
||||||
|
"boneSet": "rightHand",
|
||||||
|
"alphaVar": "rightHandOverlayAlpha"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "rightHandStateMachine",
|
||||||
|
"type": "stateMachine",
|
||||||
|
"data": {
|
||||||
|
"currentState": "rightHandIdle",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"id": "rightHandIdle",
|
||||||
|
"interpTarget": 3,
|
||||||
|
"interpDuration": 3,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isRightHandPoint", "state": "rightHandPointIntro" },
|
||||||
|
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointIntro",
|
||||||
|
"interpTarget": 3,
|
||||||
|
"interpDuration": 3,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isRightHandIdle", "state": "rightHandIdle" },
|
||||||
|
{ "var": "isRightHandPointIntroOnDone", "state": "rightHandPointHold" },
|
||||||
|
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointHold",
|
||||||
|
"interpTarget": 3,
|
||||||
|
"interpDuration": 3,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isRightHandIdle", "state": "rightHandPointOutro" },
|
||||||
|
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointOutro",
|
||||||
|
"interpTarget": 3,
|
||||||
|
"interpDuration": 3,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isRightHandPointOutroOnDone", "state": "rightHandIdle" },
|
||||||
|
{ "var": "isRightHandGrab", "state": "rightHandGrab" },
|
||||||
|
{ "var": "isRightHandPoint", "state": "rightHandPointHold" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandGrab",
|
||||||
|
"interpTarget": 3,
|
||||||
|
"interpDuration": 3,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isRightHandIdle", "state": "rightHandIdle" },
|
||||||
|
{ "var": "isRightHandPoint_DISABLED", "state": "rightHandPointHold" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "rightHandIdle",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 0.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointHold",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx",
|
||||||
|
"startFrame": 12.0,
|
||||||
|
"endFrame": 12.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointIntro",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 12.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": false
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandPointOutro",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 65.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": false
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandGrab",
|
||||||
|
"type": "blendLinear",
|
||||||
|
"data": {
|
||||||
|
"alpha": 0.0,
|
||||||
|
"alphaVar": "rightHandGrabBlend"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "rightHandOpen",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/right_hand_point.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 0.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rightHandClose",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx",
|
||||||
|
"startFrame": 15.0,
|
||||||
|
"endFrame": 15.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "leftHandOverlay",
|
||||||
|
"type": "overlay",
|
||||||
|
"data": {
|
||||||
|
"alpha": 1.0,
|
||||||
|
"boneSet": "leftHand",
|
||||||
|
"alphaVar" : "leftHandOverlay"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "leftHandStateMachine",
|
||||||
|
"type": "stateMachine",
|
||||||
|
"data": {
|
||||||
|
"currentState": "leftHandIdle",
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"id": "leftHandIdle",
|
||||||
|
"interpTarget": 6,
|
||||||
|
"interpDuration": 6,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isLeftHandPoint", "state": "leftHandPoint" },
|
||||||
|
{ "var": "isLeftHandGrab", "state": "leftHandGrab" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "leftHandPoint",
|
||||||
|
"interpTarget": 6,
|
||||||
|
"interpDuration": 6,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isLeftHandIdle", "state": "leftHandIdle" },
|
||||||
|
{ "var": "isLeftHandGrab", "state": "leftHandGrab" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "leftHandGrab",
|
||||||
|
"interpTarget": 6,
|
||||||
|
"interpDuration": 6,
|
||||||
|
"transitions": [
|
||||||
|
{ "var": "isLeftHandIdle", "state": "leftHandIdle" },
|
||||||
|
{ "var": "isLeftHandPoint", "state": "leftHandPoint" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "leftHandIdle",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx",
|
||||||
|
"startFrame": 30.0,
|
||||||
|
"endFrame": 30.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "leftHandPoint",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx",
|
||||||
|
"startFrame": 0.0,
|
||||||
|
"endFrame": 0.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "leftHandGrab",
|
||||||
|
"type": "clip",
|
||||||
|
"data": {
|
||||||
|
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx",
|
||||||
|
"startFrame": 15.0,
|
||||||
|
"endFrame": 15.0,
|
||||||
|
"timeScale": 1.0,
|
||||||
|
"loopFlag": true
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "mainStateMachine",
|
"id": "mainStateMachine",
|
||||||
"type": "stateMachine",
|
"type": "stateMachine",
|
||||||
|
@ -243,4 +475,8 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue