mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-18 03:36:22 +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_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::set<ResourceCacheSharedItems>();
|
||||
|
@ -157,8 +153,11 @@ void Agent::run() {
|
|||
|
||||
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
|
||||
ScriptableAvatar scriptedAvatar(&_scriptEngine);
|
||||
ScriptableAvatar scriptedAvatar(_scriptEngine);
|
||||
scriptedAvatar.setForceFaceTrackerConnected(true);
|
||||
|
||||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||
|
@ -166,12 +165,11 @@ void Agent::run() {
|
|||
scriptedAvatar.setSkeletonModelURL(QUrl());
|
||||
|
||||
// give this AvatarData object to the script engine
|
||||
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
||||
setAvatarData(&scriptedAvatar, "Avatar");
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
||||
_scriptEngine.setAvatarHashMap(avatarHashMap.data(), "AvatarList");
|
||||
|
||||
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
||||
|
@ -179,33 +177,188 @@ void Agent::run() {
|
|||
packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
|
||||
|
||||
// register ourselves to the script engine
|
||||
_scriptEngine.registerGlobalObject("Agent", this);
|
||||
_scriptEngine->registerGlobalObject("Agent", this);
|
||||
|
||||
if (!_payload.isEmpty()) {
|
||||
_scriptEngine.setParentURL(_payload);
|
||||
}
|
||||
// FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why
|
||||
// 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>();
|
||||
|
||||
_scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
_scriptEngine->registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||
_entityViewer.init();
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
||||
_scriptEngine.setScriptContents(scriptContents);
|
||||
_scriptEngine.run();
|
||||
// wire up our additional agent related processing to the update signal
|
||||
QObject::connect(_scriptEngine, &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio);
|
||||
|
||||
_scriptEngine->run();
|
||||
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() {
|
||||
_scriptEngine.stop();
|
||||
_scriptEngine->stop();
|
||||
|
||||
_pingTimer->stop();
|
||||
delete _pingTimer;
|
||||
|
|
|
@ -37,37 +37,53 @@ class Agent : public ThreadedAssignment {
|
|||
public:
|
||||
Agent(NLPacket& packet);
|
||||
|
||||
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
|
||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
||||
|
||||
bool isPlayingAvatarSound() const { return _scriptEngine.isPlayingAvatarSound(); }
|
||||
|
||||
bool isListeningToAudioStream() const { return _scriptEngine.isListeningToAudioStream(); }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream)
|
||||
{ _scriptEngine.setIsListeningToAudioStream(isListeningToAudioStream); }
|
||||
|
||||
void setIsAvatar(bool isAvatar);
|
||||
bool isAvatar() const { return _isAvatar; }
|
||||
|
||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||
|
||||
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
||||
|
||||
virtual void aboutToFinish();
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void playAvatarSound(Sound* avatarSound) { _scriptEngine.setAvatarSound(avatarSound); }
|
||||
void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); }
|
||||
|
||||
private slots:
|
||||
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void sendPingRequests();
|
||||
void processAgentAvatarAndAudio(float deltaTime);
|
||||
|
||||
private:
|
||||
ScriptEngine _scriptEngine;
|
||||
ScriptEngine* _scriptEngine;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
QTimer* _pingTimer;
|
||||
|
||||
MixedAudioStream _receivedAudioStream;
|
||||
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
|
||||
|
|
|
@ -142,7 +142,7 @@ void EntityServer::pruneDeletedEntities() {
|
|||
}
|
||||
}
|
||||
|
||||
void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) {
|
||||
bool EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) {
|
||||
bool wantEditLogging = false;
|
||||
readOptionBool(QString("wantEditLogging"), settingsSectionObject, 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);
|
||||
tree->setWantEditLogging(wantEditLogging);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent);
|
||||
|
||||
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject);
|
||||
virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||
|
||||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
|
|
@ -242,7 +242,8 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip()) {
|
||||
0.0f != getCameraFarClip() &&
|
||||
getCameraNearClip() != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
|
|
|
@ -887,7 +887,7 @@ bool OctreeServer::readOptionString(const QString& optionName, const QJsonObject
|
|||
return optionAvailable;
|
||||
}
|
||||
|
||||
void OctreeServer::readConfiguration() {
|
||||
bool OctreeServer::readConfiguration() {
|
||||
// if the assignment had a payload, read and parse that
|
||||
if (getPayload().size() > 0) {
|
||||
parsePayload();
|
||||
|
@ -907,8 +907,11 @@ void OctreeServer::readConfiguration() {
|
|||
loop.exec();
|
||||
|
||||
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();
|
||||
QString settingsKey = getMyDomainSettingsKey();
|
||||
QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject();
|
||||
|
@ -1017,7 +1020,7 @@ void OctreeServer::readConfiguration() {
|
|||
packetsPerSecondTotalMax, _packetsTotalPerInterval);
|
||||
|
||||
|
||||
readAdditionalConfiguration(settingsSectionObject);
|
||||
return readAdditionalConfiguration(settingsSectionObject);
|
||||
}
|
||||
|
||||
void OctreeServer::run() {
|
||||
|
@ -1043,7 +1046,9 @@ void OctreeServer::run() {
|
|||
commonInit(getMyLoggingServerTargetName(), getMyNodeType());
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -1333,19 +1338,22 @@ void OctreeServer::sendStatsPacket() {
|
|||
QJsonObject statsObject2;
|
||||
statsObject2["data"] = dataObject1;
|
||||
statsObject2["timing"] = timingArray1;
|
||||
|
||||
// Stats Object 3
|
||||
|
||||
QJsonObject dataArray2;
|
||||
dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount();
|
||||
dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
||||
dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed();
|
||||
|
||||
QJsonObject timingArray2;
|
||||
timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
|
||||
timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
|
||||
timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
|
||||
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
||||
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
||||
|
||||
// Stats Object 3
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
dataArray2["1. packetQueue"] = (double)_octreeInboundPacketProcessor->packetsToProcessCount();
|
||||
dataArray2["2. totalPackets"] = (double)_octreeInboundPacketProcessor->getTotalPacketsProcessed();
|
||||
dataArray2["3. totalElements"] = (double)_octreeInboundPacketProcessor->getTotalElementsProcessed();
|
||||
|
||||
timingArray2["1. avgTransitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
|
||||
timingArray2["2. avgProcessTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
|
||||
timingArray2["3. avgLockWaitTimePerPacket"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
|
||||
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
||||
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
||||
}
|
||||
|
||||
QJsonObject statsObject3;
|
||||
statsObject3["data"] = dataArray2;
|
||||
|
|
|
@ -135,8 +135,8 @@ protected:
|
|||
bool readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result);
|
||||
bool readOptionInt(const QString& optionName, const QJsonObject& settingsSectionObject, int& result);
|
||||
bool readOptionString(const QString& optionName, const QJsonObject& settingsSectionObject, QString& result);
|
||||
void readConfiguration();
|
||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { };
|
||||
bool readConfiguration();
|
||||
virtual bool readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { return true; };
|
||||
void parsePayload();
|
||||
void initHTTPManager(int port);
|
||||
void resetSendingStats();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// Created by Eric Levin on 9/2/15
|
||||
// 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.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -13,6 +13,8 @@
|
|||
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var RADIUS_FACTOR = 4;
|
||||
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var rightTriggerAction = RIGHT_HAND_CLICK;
|
||||
|
||||
|
@ -45,7 +47,7 @@ var INTERSECT_COLOR = {
|
|||
blue: 10
|
||||
};
|
||||
|
||||
var GRAB_RADIUS = 1.5;
|
||||
var GRAB_RADIUS = 0.3;
|
||||
|
||||
var GRAB_COLOR = {
|
||||
red: 250,
|
||||
|
@ -58,12 +60,11 @@ var DISTANCE_HOLD_THRESHOLD = 0.8;
|
|||
var right4Action = 18;
|
||||
var left4Action = 17;
|
||||
|
||||
var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
|
||||
|
||||
var RIGHT = 1;
|
||||
var LEFT = 0;
|
||||
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right");
|
||||
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left");
|
||||
var startTime = Date.now();
|
||||
|
||||
|
||||
//Need to wait before calling these methods for some reason...
|
||||
|
@ -78,56 +79,67 @@ function controller(side, triggerAction, pullAction, hand) {
|
|||
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
||||
} else {
|
||||
|
||||
this.getHandPosition = MyAvatar.getLeftPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||
}
|
||||
this.triggerAction = triggerAction;
|
||||
this.pullAction = pullAction;
|
||||
this.actionID = null;
|
||||
this.tractorBeamActive = false;
|
||||
this.distanceHolding = false;
|
||||
this.closeGrabbing = false;
|
||||
this.triggerValue = 0;
|
||||
this.prevTriggerValue = 0;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
color: NO_INTERSECT_COLOR,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
visible: false,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
|
||||
this.pointer = null;
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.updateLine = function() {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
if (this.pointer != null) {
|
||||
if (Entities.getEntityProperties(this.pointer).id != this.poitner) {
|
||||
this.pointer == null;
|
||||
}
|
||||
}
|
||||
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, LINE_LENGTH)
|
||||
]
|
||||
});
|
||||
if (this.pointer == null) {
|
||||
this.lineCreationTime = Date.now();
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
color: NO_INTERSECT_COLOR,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
visible: true,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var handPosition = this.getHandPosition();
|
||||
var direction = Quat.getUp(this.getHandRotation());
|
||||
|
||||
//only check if we havent already grabbed an object
|
||||
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;
|
||||
}
|
||||
|
||||
//move origin a bit away from hand so nothing gets in way
|
||||
var origin = Vec3.sum(handPosition, direction);
|
||||
if (this.checkForIntersections(origin, direction)) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [ ZERO_VEC, Vec3.multiply(direction, LINE_LENGTH) ],
|
||||
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||
});
|
||||
|
||||
if (this.checkForIntersections(handPosition, direction)) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: INTERSECT_COLOR,
|
||||
});
|
||||
|
@ -144,7 +156,7 @@ controller.prototype.checkPointer = function() {
|
|||
Script.setTimeout(function() {
|
||||
var props = Entities.getEntityProperties(self.pointer);
|
||||
Entities.editEntity(self.pointer, {
|
||||
lifetime: props.age + EXTRA_TIME + LIFETIME
|
||||
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||
});
|
||||
self.checkPointer();
|
||||
}, POINTER_CHECK_TIME);
|
||||
|
@ -172,28 +184,54 @@ controller.prototype.checkForIntersections = function(origin, direction) {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.attemptMove = function() {
|
||||
if (this.tractorBeamActive) {
|
||||
return;
|
||||
}
|
||||
if (this.grabbedEntity || this.distanceHolding) {
|
||||
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;
|
||||
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, {
|
||||
targetPosition: newPosition,
|
||||
linearTimeScale: .1
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: .1,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: .1
|
||||
});
|
||||
} 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, {
|
||||
targetPosition: newPosition
|
||||
targetPosition: this.currentObjectPosition, linearTimeScale: .1,
|
||||
targetRotation: this.currentObjectRotation, angularTimeScale: .1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.showPointer = function() {
|
||||
|
@ -218,19 +256,10 @@ controller.prototype.letGo = function() {
|
|||
this.grabbedEntity = null;
|
||||
this.actionID = null;
|
||||
this.distanceHolding = false;
|
||||
this.tractorBeamActive = false;
|
||||
this.checkForEntityArrival = false;
|
||||
this.closeGrabbing = false;
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
|
@ -253,7 +282,6 @@ controller.prototype.update = function() {
|
|||
this.attemptMove();
|
||||
}
|
||||
|
||||
|
||||
this.prevTriggerValue = this.triggerValue;
|
||||
}
|
||||
|
||||
|
@ -331,27 +359,6 @@ controller.prototype.deactivateEntity = function(entity) {
|
|||
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() {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
if (this.grabbedEntity) {
|
||||
|
@ -364,13 +371,6 @@ function update() {
|
|||
leftController.update();
|
||||
}
|
||||
|
||||
function onActionEvent(action, state) {
|
||||
rightController.onActionEvent(action, state);
|
||||
leftController.onActionEvent(action, state);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function cleanup() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
|
@ -378,4 +378,3 @@ function cleanup() {
|
|||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
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 rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND);
|
||||
Hand* hand = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHand();
|
||||
setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX);
|
||||
setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX);
|
||||
setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK));
|
||||
setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::RIGHT_HAND_CLICK));
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) {
|
||||
emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK),
|
||||
userInputMapper->getActionState(UserInputMapper::SHIFT), LEFT_HAND_INDEX);
|
||||
|
@ -4026,8 +4026,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
AvatarManager::registerMetaTypes(scriptEngine);
|
||||
|
||||
// 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->setAvatarHashMap(DependencyManager::get<AvatarManager>().data(), "AvatarList");
|
||||
scriptEngine->registerGlobalObject("MyAvatar", _myAvatar);
|
||||
scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Camera", &_myCamera);
|
||||
|
||||
|
@ -4051,9 +4051,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
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,
|
||||
LocationScriptingInterface::locationSetter, windowValue);
|
||||
LocationScriptingInterface::locationSetter, "Window");
|
||||
// register `location` on the global object.
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter);
|
||||
|
@ -4083,9 +4083,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
||||
|
||||
QScriptValue hmdInterface = scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||
scriptEngine->registerFunction(hmdInterface, "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||
scriptEngine->registerGlobalObject("HMD", &HMDScriptingInterface::getInstance());
|
||||
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0);
|
||||
scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0);
|
||||
|
||||
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
|
||||
|
@ -4094,31 +4094,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
#ifdef HAVE_RTMIDI
|
||||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||
#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() {
|
||||
|
@ -4264,11 +4239,14 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
|
|||
scriptEngine->setUserLoaded(isUserLoaded);
|
||||
|
||||
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
|
||||
// doesn't just get an empty script engine)
|
||||
|
||||
// we can complete setup now since there isn't a script we have to load
|
||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||
scriptEngine->runInThread();
|
||||
} else {
|
||||
// connect to the appropriate signals of this script engine
|
||||
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);
|
||||
}
|
||||
|
||||
// FIXME - change to new version of ScriptCache loading notification
|
||||
void Application::handleScriptEngineLoaded(const QString& scriptFilename) {
|
||||
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
|
||||
registerScriptEngineWithApplicationServices(scriptEngine);
|
||||
scriptEngine->runInThread();
|
||||
}
|
||||
|
||||
// FIXME - change to new version of ScriptCache loading notification
|
||||
void Application::handleScriptLoadError(const QString& scriptFilename) {
|
||||
qCDebug(interfaceapp) << "Application::loadScript(), script failed to load...";
|
||||
QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load.");
|
||||
|
@ -4970,7 +4951,7 @@ mat4 Application::getHMDSensorPose() const {
|
|||
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;
|
||||
bool foundHand = false;
|
||||
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->setTipPosition(newTipPosition);
|
||||
palm->setTrigger(triggerValue);
|
||||
}
|
||||
|
||||
void Application::emulateMouse(Hand* hand, float click, float shift, int index) {
|
||||
|
|
|
@ -485,7 +485,7 @@ private:
|
|||
|
||||
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);
|
||||
|
||||
// Various helper functions called during update()
|
||||
|
|
|
@ -1296,10 +1296,13 @@ void MyAvatar::initAnimGraph() {
|
|||
// ik-avatar.json
|
||||
// https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060
|
||||
//
|
||||
// ik-avatar-hands.json
|
||||
// https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb
|
||||
//
|
||||
// or run a local web-server
|
||||
// python -m SimpleHTTPServer&
|
||||
// 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("http://localhost:8000/avatar.json");
|
||||
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb/raw/883c7ce8e75ad3f72a0d513c317fe4b74a41c3b8/ik-avatar-hands.json");
|
||||
_rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,17 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
|||
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;
|
||||
// Called within Model::simulate call, below.
|
||||
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);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
Rig::HeadParameters params;
|
||||
params.modelRotation = getRotation();
|
||||
params.modelTranslation = getTranslation();
|
||||
params.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
|
||||
params.leanSideways = head->getFinalLeanSideways();
|
||||
params.leanForward = head->getFinalLeanForward();
|
||||
params.torsoTwist = head->getTorsoTwist();
|
||||
params.localHeadOrientation = head->getFinalOrientationInLocalFrame();
|
||||
params.localHeadPitch = head->getFinalPitch();
|
||||
params.localHeadYaw = head->getFinalYaw();
|
||||
params.localHeadRoll = head->getFinalRoll();
|
||||
params.isInHMD = qApp->getAvatarUpdater()->isHMDMode();
|
||||
Rig::HeadParameters headParams;
|
||||
headParams.modelRotation = getRotation();
|
||||
headParams.modelTranslation = getTranslation();
|
||||
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
|
||||
headParams.leanSideways = head->getFinalLeanSideways();
|
||||
headParams.leanForward = head->getFinalLeanForward();
|
||||
headParams.torsoTwist = head->getTorsoTwist();
|
||||
headParams.localHeadOrientation = head->getFinalOrientationInLocalFrame();
|
||||
headParams.localHeadPitch = head->getFinalPitch();
|
||||
headParams.localHeadYaw = head->getFinalYaw();
|
||||
headParams.localHeadRoll = head->getFinalRoll();
|
||||
headParams.isInHMD = qApp->getAvatarUpdater()->isHMDMode();
|
||||
|
||||
// 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::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
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();
|
||||
params.eyeLookAt = head->getLookAtPosition();
|
||||
params.eyeSaccade = head->getSaccade();
|
||||
params.leanJointIndex = geometry.leanJointIndex;
|
||||
params.neckJointIndex = geometry.neckJointIndex;
|
||||
params.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||
params.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||
headParams.worldHeadOrientation = head->getFinalOrientationInWorldFrame();
|
||||
headParams.eyeLookAt = head->getLookAtPosition();
|
||||
headParams.eyeSaccade = head->getSaccade();
|
||||
headParams.leanJointIndex = geometry.leanJointIndex;
|
||||
headParams.neckJointIndex = geometry.neckJointIndex;
|
||||
headParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||
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 {
|
||||
// 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();
|
||||
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)) {
|
||||
|
@ -232,13 +237,15 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = {
|
|||
"fullBody",
|
||||
"upperBody",
|
||||
"lowerBody",
|
||||
"rightArm",
|
||||
"leftArm",
|
||||
"rightArm",
|
||||
"aboveTheHead",
|
||||
"belowTheHead",
|
||||
"headOnly",
|
||||
"spineOnly",
|
||||
"empty"
|
||||
"empty",
|
||||
"leftHand",
|
||||
"rightHand"
|
||||
};
|
||||
|
||||
static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
|
||||
|
|
|
@ -26,12 +26,14 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) {
|
|||
case FullBodyBoneSet: buildFullBodyBoneSet(); break;
|
||||
case UpperBodyBoneSet: buildUpperBodyBoneSet(); break;
|
||||
case LowerBodyBoneSet: buildLowerBodyBoneSet(); break;
|
||||
case RightArmBoneSet: buildRightArmBoneSet(); break;
|
||||
case LeftArmBoneSet: buildLeftArmBoneSet(); break;
|
||||
case RightArmBoneSet: buildRightArmBoneSet(); break;
|
||||
case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break;
|
||||
case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break;
|
||||
case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break;
|
||||
case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break;
|
||||
case LeftHandBoneSet: buildLeftHandBoneSet(); break;
|
||||
case RightHandBoneSet: buildRightHandBoneSet(); break;
|
||||
default:
|
||||
case EmptyBoneSet: buildEmptyBoneSet(); break;
|
||||
}
|
||||
|
@ -110,15 +112,6 @@ void AnimOverlay::buildLowerBodyBoneSet() {
|
|||
_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() {
|
||||
assert(_skeleton);
|
||||
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() {
|
||||
assert(_skeleton);
|
||||
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
|
||||
const AnimPoseVec& AnimOverlay::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
||||
|
||||
void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||
_skeleton = skeleton;
|
||||
AnimNode::setSkeletonInternal(skeleton);
|
||||
|
||||
// we have to re-build the bone set when the skeleton changes.
|
||||
buildBoneSet(_boneSet);
|
||||
|
|
|
@ -28,14 +28,16 @@ public:
|
|||
FullBodyBoneSet = 0,
|
||||
UpperBodyBoneSet,
|
||||
LowerBodyBoneSet,
|
||||
RightArmBoneSet,
|
||||
LeftArmBoneSet,
|
||||
RightArmBoneSet,
|
||||
AboveTheHeadBoneSet,
|
||||
BelowTheHeadBoneSet,
|
||||
HeadOnlyBoneSet,
|
||||
SpineOnlyBoneSet,
|
||||
EmptyBoneSet,
|
||||
NumBoneSets,
|
||||
LeftHandBoneSet,
|
||||
RightHandBoneSet,
|
||||
NumBoneSets
|
||||
};
|
||||
|
||||
AnimOverlay(const std::string& id, BoneSet boneSet, float alpha);
|
||||
|
@ -64,13 +66,15 @@ public:
|
|||
void buildFullBodyBoneSet();
|
||||
void buildUpperBodyBoneSet();
|
||||
void buildLowerBodyBoneSet();
|
||||
void buildRightArmBoneSet();
|
||||
void buildLeftArmBoneSet();
|
||||
void buildRightArmBoneSet();
|
||||
void buildAboveTheHeadBoneSet();
|
||||
void buildBelowTheHeadBoneSet();
|
||||
void buildHeadOnlyBoneSet();
|
||||
void buildSpineOnlyBoneSet();
|
||||
void buildEmptyBoneSet();
|
||||
void buildLeftHandBoneSet();
|
||||
void buildRightHandBoneSet();
|
||||
|
||||
// no copies
|
||||
AnimOverlay(const AnimOverlay&) = delete;
|
||||
|
|
|
@ -952,7 +952,7 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
|||
return _jointStates[jointIndex].getDefaultRotationInParentFrame();
|
||||
}
|
||||
|
||||
void Rig::updateFromHeadParameters(const HeadParameters& params) {
|
||||
void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
||||
if (params.enableLean) {
|
||||
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) {
|
||||
if (!_enableAnimGraph) {
|
||||
return;
|
||||
|
|
|
@ -76,6 +76,17 @@ public:
|
|||
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() {}
|
||||
|
||||
RigPointer getRigPointer() { return shared_from_this(); }
|
||||
|
@ -168,10 +179,12 @@ public:
|
|||
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
|
||||
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,
|
||||
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,
|
||||
float scale, float priority) = 0;
|
||||
|
||||
|
@ -215,6 +228,8 @@ public:
|
|||
Move
|
||||
};
|
||||
RigRole _state = RigRole::Idle;
|
||||
float _leftHandOverlayAlpha = 0.0f;
|
||||
float _rightHandOverlayAlpha = 0.0f;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Rig__) */
|
||||
|
|
|
@ -72,7 +72,7 @@ SwingTwistConstraint::SwingTwistConstraint() :
|
|||
_swingLimitFunction(),
|
||||
_minTwist(-PI),
|
||||
_maxTwist(PI),
|
||||
_lastTwistBoundary(0)
|
||||
_lastTwistBoundary(LAST_CLAMP_NO_BOUNDARY)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -249,3 +249,6 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void SwingTwistConstraint::clearHistory() {
|
||||
_lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY;
|
||||
}
|
||||
|
|
|
@ -73,6 +73,9 @@ public:
|
|||
/// \return reference to SwingLimitFunction instance for unit-testing
|
||||
const SwingLimitFunction& getSwingLimitFunction() const { return _swingLimitFunction; }
|
||||
|
||||
/// \brief exposed for unit testing
|
||||
void clearHistory();
|
||||
|
||||
protected:
|
||||
SwingLimitFunction _swingLimitFunction;
|
||||
float _minTwist;
|
||||
|
|
|
@ -125,12 +125,12 @@ public:
|
|||
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.)
|
||||
for (uint32_t i = 0; i < frameCount; ++i) {
|
||||
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(),
|
||||
_wantScripts(wantScripts),
|
||||
_entitiesScriptEngine(NULL),
|
||||
_sandboxScriptEngine(NULL),
|
||||
_lastMouseEventValid(false),
|
||||
_viewState(viewState),
|
||||
_scriptingServices(scriptingServices),
|
||||
|
@ -77,23 +76,11 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
EntityTreeRenderer::~EntityTreeRenderer() {
|
||||
// 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
|
||||
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() {
|
||||
leaveAllEntities();
|
||||
foreach (const EntityItemID& entityID, _entityScripts.keys()) {
|
||||
checkAndCallUnload(entityID);
|
||||
}
|
||||
_entityScripts.clear();
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
render::PendingChanges pendingChanges;
|
||||
|
@ -113,10 +100,9 @@ void EntityTreeRenderer::init() {
|
|||
|
||||
if (_wantScripts) {
|
||||
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
|
||||
_scriptingServices->getControllerScriptingInterface(), false);
|
||||
_scriptingServices->getControllerScriptingInterface());
|
||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
||||
|
||||
_sandboxScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities Sandbox", NULL, false);
|
||||
_entitiesScriptEngine->runInThread();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
OctreeRenderer::setTree(newTree);
|
||||
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
|
||||
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent);
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
// 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 all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||
emit leaveEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("leaveEntity").isValid()) {
|
||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,11 +182,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||
if (!_currentEntitiesInside.contains(entityID)) {
|
||||
emit enterEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("enterEntity").isValid()) {
|
||||
entityScript.property("enterEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||
}
|
||||
}
|
||||
_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
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
emit leaveEntity(entityID);
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("leaveEntity").isValid()) {
|
||||
entityScript.property("leaveEntity").call(entityScript, entityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
_currentEntitiesInside.clear();
|
||||
|
||||
|
@ -811,27 +610,6 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
|
|||
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) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
|
@ -854,18 +632,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
|||
}
|
||||
|
||||
emit mousePressOnEntity(rayPickResult, 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);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
_currentClickingOnEntityID = rayPickResult.entityID;
|
||||
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("clickDownOnEntity").isValid()) {
|
||||
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event, deviceID));
|
||||
} else {
|
||||
emit mousePressOffEntity(rayPickResult, event, deviceID);
|
||||
}
|
||||
|
@ -886,24 +657,14 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
|||
if (rayPickResult.intersects) {
|
||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
emit mouseReleaseOnEntity(rayPickResult, 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);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
|
||||
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// 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
|
||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||
if (rayPickResult.intersects) {
|
||||
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
|
||||
// load the entity script if needed...
|
||||
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);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event, deviceID));
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
// handle the hover logic...
|
||||
|
||||
|
@ -943,30 +696,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
// then we need to send the hover leave.
|
||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("hoverEnterEntity").isValid()) {
|
||||
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
|
||||
// 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
|
||||
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
|
||||
if (entityScript.property("hoverOverEntity").isValid()) {
|
||||
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event, deviceID));
|
||||
|
||||
// remember what we're hovering over
|
||||
_currentHoverOverEntityID = rayPickResult.entityID;
|
||||
|
@ -977,14 +719,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
// send the hover leave for our previous entity
|
||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
|
||||
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
|
||||
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event, deviceID));
|
||||
_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
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
|
||||
|
||||
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
|
||||
|
||||
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
|
||||
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
|
||||
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event, deviceID));
|
||||
}
|
||||
_lastMouseEvent = MouseEvent(*event, deviceID);
|
||||
_lastMouseEventValid = true;
|
||||
|
@ -1007,9 +736,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
|
||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
checkAndCallUnload(entityID);
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
|
||||
// here's where we remove the entity payload from the scene
|
||||
if (_entitiesInScene.contains(entityID)) {
|
||||
|
@ -1042,28 +770,16 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
|||
|
||||
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
checkAndCallUnload(entityID);
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(entityID, true, reload); // is preload!
|
||||
if (entityScript.property("preload").isValid()) {
|
||||
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);
|
||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||
if (entity && !entity->getScript().isEmpty()) {
|
||||
_entitiesScriptEngine->loadEntityScript(entityID, entity->getScript(), reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1143,24 +859,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
|||
|
||||
// And now the entity scripts
|
||||
emit collisionWithEntity(idA, idB, collision);
|
||||
QScriptValue entityScriptA = loadEntityScript(idA);
|
||||
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);
|
||||
}
|
||||
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||
emit collisionWithEntity(idB, idA, collision);
|
||||
QScriptValue entityScriptB = loadEntityScript(idB);
|
||||
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);
|
||||
}
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
||||
|
|
|
@ -28,14 +28,9 @@ class Model;
|
|||
class ScriptEngine;
|
||||
class ZoneEntityItem;
|
||||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
QString scriptText;
|
||||
QScriptValue scriptObject;
|
||||
};
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
|
||||
|
@ -87,9 +82,6 @@ public:
|
|||
/// hovering over, and entering entities
|
||||
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
|
||||
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
|
||||
virtual void errorInLoadingScript(const QUrl& url);
|
||||
|
||||
// For Scene.shouldRenderEntities
|
||||
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
||||
|
||||
|
@ -137,7 +129,6 @@ private:
|
|||
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
||||
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||
void checkAndCallUnload(const EntityItemID& entityID);
|
||||
|
||||
QList<Model*> _releasedModels;
|
||||
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
||||
|
@ -155,16 +146,6 @@ private:
|
|||
|
||||
bool _wantScripts;
|
||||
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,
|
||||
const EntityItemID& id, const Collision& collision);
|
||||
|
|
|
@ -17,21 +17,31 @@
|
|||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
||||
QUrl ResourceManager::normalizeURL(const QUrl& url) {
|
||||
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) {
|
||||
return new FileResourceRequest(parent, url);
|
||||
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
||||
return new HTTPResourceRequest(parent, url);
|
||||
} else if (scheme == URL_SCHEME_ATP) {
|
||||
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();
|
||||
|
|
|
@ -24,6 +24,7 @@ const QString URL_SCHEME_ATP = "atp";
|
|||
|
||||
class ResourceManager {
|
||||
public:
|
||||
static QUrl normalizeURL(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>();
|
||||
nodeList->setOwnerType(nodeType);
|
||||
|
||||
_domainServerTimer = new QTimer();
|
||||
_domainServerTimer = new QTimer(this);
|
||||
connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
||||
_domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) {
|
|||
}
|
||||
|
||||
DefaultCC::DefaultCC() :
|
||||
_lastRCTime(p_high_resolution_clock::now()),
|
||||
_slowStartLastAck(_sendCurrSeqNum),
|
||||
_lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX })
|
||||
{
|
||||
|
|
|
@ -108,7 +108,8 @@ public:
|
|||
private:
|
||||
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
|
||||
SequenceNumber _slowStartLastAck; // last ACKed seq num
|
||||
bool _loss { false }; // if loss happened since last rate increase
|
||||
|
|
|
@ -28,7 +28,6 @@ using namespace udt;
|
|||
using namespace std::chrono;
|
||||
|
||||
Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr<CongestionControl> congestionControl) :
|
||||
_connectionStart(p_high_resolution_clock::now()),
|
||||
_parentSocket(parentSocket),
|
||||
_destination(destination),
|
||||
_congestionControl(move(congestionControl))
|
||||
|
@ -278,11 +277,11 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) {
|
|||
// pack in the receive speed and estimatedBandwidth
|
||||
ackPacket->writePrimitive(packetReceiveSpeed);
|
||||
ackPacket->writePrimitive(estimatedBandwidth);
|
||||
|
||||
// record this as the last ACK send time
|
||||
lastACKSendTime = p_high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// record this as the last ACK send time
|
||||
lastACKSendTime = p_high_resolution_clock::now();
|
||||
|
||||
// have the socket send off our packet
|
||||
_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
|
||||
// it looks like they haven't received our ACK2 for this ACK
|
||||
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);
|
||||
|
||||
|
@ -779,7 +779,7 @@ void Connection::resetReceiveState() {
|
|||
|
||||
// clear the loss list and _lastNAKTime
|
||||
_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
|
||||
|
||||
|
|
|
@ -114,13 +114,14 @@ private:
|
|||
|
||||
int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss
|
||||
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 _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
|
||||
|
||||
bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection
|
||||
|
||||
LossList _lossList; // List of all missing packets
|
||||
|
|
|
@ -93,15 +93,16 @@ int32_t PacketTimeWindow::getEstimatedBandwidth() const {
|
|||
}
|
||||
|
||||
void PacketTimeWindow::onPacketArrival() {
|
||||
|
||||
// take the current time
|
||||
auto now = p_high_resolution_clock::now();
|
||||
|
||||
// record the interval between this packet and the last one
|
||||
_packetIntervals[_currentPacketInterval++] = duration_cast<microseconds>(now - _lastPacketTime).count();
|
||||
|
||||
// reset the currentPacketInterval index when it wraps
|
||||
if (_currentPacketInterval == _numPacketIntervals) {
|
||||
_currentPacketInterval = 0;
|
||||
if (_packetIntervals.size() > 0) {
|
||||
// record the interval between this packet and the last one
|
||||
_packetIntervals[_currentPacketInterval++] = duration_cast<microseconds>(now - _lastPacketTime).count();
|
||||
|
||||
// reset the currentPacketInterval index when it wraps
|
||||
_currentPacketInterval %= _numPacketIntervals;
|
||||
}
|
||||
|
||||
// remember this as the last packet arrival time
|
||||
|
@ -120,7 +121,5 @@ void PacketTimeWindow::onProbePair2Arrival() {
|
|||
_probeIntervals[_currentProbeInterval++] = duration_cast<microseconds>(now - _firstProbeTime).count();
|
||||
|
||||
// reset the currentProbeInterval index when it wraps
|
||||
if (_currentProbeInterval == _numProbeIntervals) {
|
||||
_currentProbeInterval = 0;
|
||||
}
|
||||
_currentProbeInterval %= _numProbeIntervals;
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ private:
|
|||
std::vector<int> _packetIntervals; // vector of microsecond intervals between packet 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 _firstProbeTime; // the time_point when first probe in pair 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 = p_high_resolution_clock::now(); // the time_point when first probe in pair arrived
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -279,17 +279,13 @@ void SendQueue::run() {
|
|||
if (!_hasReceivedHandshakeACK) {
|
||||
// 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
|
||||
|
||||
// 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);
|
||||
|
||||
// calculation the duration since the last handshake send
|
||||
auto sinceLastHandshake = std::chrono::duration_cast<std::chrono::milliseconds>(p_high_resolution_clock::now()
|
||||
- lastSendHandshake);
|
||||
// hold the time of last send in a static
|
||||
static auto lastSendHandshake = p_high_resolution_clock::now() - HANDSHAKE_RESEND_INTERVAL_MS;
|
||||
|
||||
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
|
||||
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
|
||||
_handshakeACKCondition.wait_until(handshakeLock,
|
||||
p_high_resolution_clock::now()
|
||||
+ HANDSHAKE_RESEND_INTERVAL_MS);
|
||||
_handshakeACKCondition.wait_until(handshakeLock, 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.
|
||||
// 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
|
||||
|
||||
public:
|
||||
using time_point = p_high_resolution_clock::time_point;
|
||||
|
||||
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination);
|
||||
|
||||
void queuePacket(std::unique_ptr<Packet> packet);
|
||||
|
|
|
@ -798,7 +798,7 @@ namespace render {
|
|||
return payload->model->renderPart(args, payload->meshIndex, payload->partIndex, payload->transparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* template <> const model::MaterialKey& shapeGetMaterialKey(const MeshPartPayload::Pointer& payload) {
|
||||
return payload->model->getPartMaterial(payload->meshIndex, payload->partIndex);
|
||||
}*/
|
||||
|
@ -829,6 +829,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
|||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
_renderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
@ -838,6 +841,9 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
|
|||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
_renderItems.insert(item, renderPayload);
|
||||
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);
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
_renderItems.insert(item, renderPayload);
|
||||
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);
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
_renderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
}
|
||||
|
@ -1283,6 +1295,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
|
||||
//virtual
|
||||
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
_needsUpdateClusterMatrices = true;
|
||||
_rig->updateAnimations(deltaTime, parentTransform);
|
||||
}
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
|
@ -1293,11 +1306,15 @@ void Model::simulateInternal(float deltaTime) {
|
|||
updateRig(deltaTime, parentTransform);
|
||||
}
|
||||
void Model::updateClusterMatrices() {
|
||||
if (!_needsUpdateClusterMatrices) {
|
||||
return;
|
||||
}
|
||||
_needsUpdateClusterMatrices = false;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
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),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
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, 1.0f));
|
||||
auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
|
||||
|
||||
glm::mat4 modelToWorld = glm::mat4_cast(_rotation);
|
||||
|
@ -1803,13 +1820,15 @@ bool Model::initWhenReady(render::ScenePointer scene) {
|
|||
segregateMeshGroups();
|
||||
|
||||
render::PendingChanges pendingChanges;
|
||||
|
||||
foreach (auto renderItem, _transparentRenderItems) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderData = MeshPartPayload::Pointer(renderItem);
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||
_renderItems.insert(item, renderPayload);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
}
|
||||
|
||||
foreach (auto renderItem, _opaqueRenderItems) {
|
||||
|
@ -1818,6 +1837,9 @@ bool Model::initWhenReady(render::ScenePointer scene) {
|
|||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderData);
|
||||
_renderItems.insert(item, renderPayload);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
pendingChanges.updateItem<MeshPartPayload>(item, [&](MeshPartPayload& data) {
|
||||
data.model->_needsUpdateClusterMatrices = true;
|
||||
});
|
||||
}
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
|
||||
|
|
|
@ -497,6 +497,7 @@ private:
|
|||
QMap<render::ItemID, render::PayloadPointer> _renderItems;
|
||||
bool _readyWhenAdded = false;
|
||||
bool _needsReload = true;
|
||||
bool _needsUpdateClusterMatrices = true;
|
||||
|
||||
protected:
|
||||
RigPointer _rig;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QNetworkConfiguration>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
|
||||
#include <assert.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -26,7 +27,8 @@ ScriptCache::ScriptCache(QObject* parent) {
|
|||
// 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;
|
||||
if (_scriptCache.contains(url) && !reload) {
|
||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
|
@ -41,7 +43,7 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
|||
if (alreadyWaiting) {
|
||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||
} else {
|
||||
auto request = ResourceManager::createResourceRequest(this, url);
|
||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||
request->setCacheEnabled(!reload);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
||||
request->send();
|
||||
|
@ -50,7 +52,8 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
|
|||
return scriptContents;
|
||||
}
|
||||
|
||||
void ScriptCache::deleteScript(const QUrl& url) {
|
||||
void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
|
||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||
if (_scriptCache.contains(url)) {
|
||||
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
|
||||
_scriptCache.remove(url);
|
||||
|
@ -79,4 +82,63 @@ void ScriptCache::scriptDownloaded() {
|
|||
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;
|
||||
};
|
||||
|
||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||
|
||||
/// Interface for loading scripts
|
||||
class ScriptCache : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
||||
void deleteScript(const QUrl& url);
|
||||
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
|
||||
|
||||
|
||||
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); }
|
||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||
|
||||
private slots:
|
||||
void scriptDownloaded();
|
||||
|
||||
void scriptDownloaded(); // old version
|
||||
void scriptContentAvailable(); // new version
|
||||
|
||||
private:
|
||||
ScriptCache(QObject* parent = NULL);
|
||||
|
||||
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
||||
|
||||
QHash<QUrl, QString> _scriptCache;
|
||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||
QSet<QUrl> _badScripts;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValue>
|
||||
|
||||
#include <AudioConstants.h>
|
||||
#include <AudioEffectOptions.h>
|
||||
|
@ -44,6 +45,8 @@
|
|||
|
||||
#include "MIDIEvent.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||
static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||
|
||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||
QString message = "";
|
||||
|
@ -87,17 +90,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
|||
_isFinished(false),
|
||||
_isRunning(false),
|
||||
_isInitialized(false),
|
||||
_isAvatar(false),
|
||||
_avatarIdentityTimer(NULL),
|
||||
_avatarBillboardTimer(NULL),
|
||||
_timerFunctionMap(),
|
||||
_isListeningToAudioStream(false),
|
||||
_avatarSound(NULL),
|
||||
_numAvatarSoundSentBytes(0),
|
||||
_wantSignals(wantSignals),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_scriptName(),
|
||||
_fileNameString(fileNameString),
|
||||
_quatLibrary(),
|
||||
_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;
|
||||
QMutex ScriptEngine::_allScriptsMutex;
|
||||
bool ScriptEngine::_stoppingAllScripts = false;
|
||||
|
@ -180,8 +198,6 @@ void ScriptEngine::stopAllScripts(QObject* application) {
|
|||
|
||||
|
||||
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 (_isRunning) {
|
||||
|
||||
|
@ -209,58 +225,7 @@ QString ScriptEngine::getFilename() const {
|
|||
}
|
||||
|
||||
|
||||
void ScriptEngine::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, &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;
|
||||
}
|
||||
|
||||
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||
if (_isRunning) {
|
||||
return;
|
||||
|
@ -271,38 +236,12 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
|||
|
||||
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;
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
scriptCache->getScript(url, this, isPending, reload);
|
||||
}
|
||||
}
|
||||
bool isPending;
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
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) {
|
||||
_scriptContents = scriptContents;
|
||||
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) {
|
||||
qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
|
||||
if (_wantSignals) {
|
||||
|
@ -383,57 +323,117 @@ void ScriptEngine::init() {
|
|||
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) {
|
||||
QScriptValue value = newQObject(object);
|
||||
globalObject().setProperty(name, value);
|
||||
return value;
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) {
|
||||
registerFunction(globalObject(), name, fun, numArguments);
|
||||
void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int 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) {
|
||||
QScriptValue scriptFun = newFunction(fun, numArguments);
|
||||
parent.setProperty(name, scriptFun);
|
||||
void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
#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,
|
||||
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 getterFunction = newFunction(getter);
|
||||
|
||||
if (!object.isNull()) {
|
||||
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||
if (!parent.isNull()) {
|
||||
QScriptValue object = globalObject().property(parent);
|
||||
if (object.isValid()) {
|
||||
object.setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||
object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);
|
||||
}
|
||||
} else {
|
||||
globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);
|
||||
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.
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
@ -449,6 +449,22 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin
|
|||
}
|
||||
// Register the 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...
|
||||
// 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.)
|
||||
|
@ -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) {
|
||||
if (_stoppingAllScripts) {
|
||||
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++;
|
||||
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
||||
if (hasUncaughtException()) {
|
||||
|
@ -545,18 +552,6 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
|
|||
return result;
|
||||
}
|
||||
|
||||
void ScriptEngine::sendAvatarIdentityPacket() {
|
||||
if (_isAvatar && _avatarData) {
|
||||
_avatarData->sendIdentityPacket();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::sendAvatarBillboardPacket() {
|
||||
if (_isAvatar && _avatarData) {
|
||||
_avatarData->sendBillboardPacket();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::run() {
|
||||
// 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?
|
||||
|
@ -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();
|
||||
float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
|
||||
|
||||
|
@ -735,9 +629,6 @@ void ScriptEngine::run() {
|
|||
emit scriptEnding();
|
||||
}
|
||||
|
||||
// kill the avatar identity timer
|
||||
delete _avatarIdentityTimer;
|
||||
|
||||
if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {
|
||||
// release the queue of edit entity messages.
|
||||
entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();
|
||||
|
@ -967,6 +858,256 @@ void ScriptEngine::load(const QString& loadFile) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
||||
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
||||
// 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 (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;
|
||||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
QString scriptText;
|
||||
QScriptValue scriptObject;
|
||||
};
|
||||
|
||||
class ScriptEngine : public QScriptEngine, public ScriptUser {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -50,79 +56,86 @@ public:
|
|||
|
||||
~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();
|
||||
|
||||
/// run the script in the callers thread, exit when stop() is called.
|
||||
void run();
|
||||
|
||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
|
||||
const QString& getScriptName() const { return _scriptName; }
|
||||
/// registers a global object by name
|
||||
Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object);
|
||||
|
||||
QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
||||
void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,
|
||||
QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue);
|
||||
void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1);
|
||||
void registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun,
|
||||
/// registers a global getter/setter
|
||||
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);
|
||||
|
||||
Q_INVOKABLE void setIsAvatar(bool isAvatar);
|
||||
bool isAvatar() const { return _isAvatar; }
|
||||
/// evaluate some code in the context of the ScriptEngine and return the result
|
||||
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);
|
||||
void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName);
|
||||
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
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);
|
||||
/// if the script engine is not already running, this will download the URL and start the process of seting it up
|
||||
/// 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
|
||||
void loadURL(const QUrl& scriptURL, bool reload);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler);
|
||||
|
||||
public slots:
|
||||
void loadURL(const QUrl& scriptURL, bool reload);
|
||||
void stop();
|
||||
Q_INVOKABLE void load(const QString& loadfile);
|
||||
Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
||||
Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
||||
|
||||
QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1);
|
||||
QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
||||
void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
||||
void load(const QString& loadfile);
|
||||
void print(const QString& message);
|
||||
QUrl resolvePath(const QString& path) const;
|
||||
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||
Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
Q_INVOKABLE void print(const QString& message);
|
||||
Q_INVOKABLE 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:
|
||||
void scriptLoaded(const QString& scriptFilename);
|
||||
|
@ -146,28 +159,24 @@ protected:
|
|||
bool _isRunning;
|
||||
int _evaluatesPending = 0;
|
||||
bool _isInitialized;
|
||||
bool _isAvatar;
|
||||
QTimer* _avatarIdentityTimer;
|
||||
QTimer* _avatarBillboardTimer;
|
||||
QHash<QTimer*, QScriptValue> _timerFunctionMap;
|
||||
bool _isListeningToAudioStream;
|
||||
Sound* _avatarSound;
|
||||
int _numAvatarSoundSentBytes;
|
||||
bool _isAgent = false;
|
||||
QSet<QUrl> _includedURLs;
|
||||
bool _wantSignals = true;
|
||||
|
||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
private:
|
||||
void init();
|
||||
QString getFilename() const;
|
||||
void waitTillDoneRunning();
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void sendAvatarIdentityPacket();
|
||||
void sendAvatarBillboardPacket();
|
||||
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||
AvatarData* _avatarData;
|
||||
QString _scriptName;
|
||||
QString _fileNameString;
|
||||
Quat _quatLibrary;
|
||||
Vec3 _vec3Library;
|
||||
|
@ -177,15 +186,15 @@ private:
|
|||
|
||||
ArrayBufferClass* _arrayBufferClass;
|
||||
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||
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 QMutex _allScriptsMutex;
|
||||
static bool _stoppingAllScripts;
|
||||
static bool _doneRunningThisScript;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEngine_h
|
||||
|
|
|
@ -35,7 +35,6 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
|
|||
joint.freeLineage.clear();
|
||||
joint.parentIndex = -1;
|
||||
joint.distanceToParent = 1.0f;
|
||||
joint.boneRadius = 1.0f;
|
||||
|
||||
joint.translation = origin; // T
|
||||
joint.preTransform = glm::mat4(); // Roff * Rp
|
||||
|
@ -96,11 +95,11 @@ void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
|
|||
|
||||
void AnimInverseKinematicsTests::testSingleChain() {
|
||||
std::vector<FBXJoint> fbxJoints;
|
||||
AnimPose offset;
|
||||
makeTestFBXJoints(fbxJoints, offset);
|
||||
makeTestFBXJoints(fbxJoints);
|
||||
|
||||
// create a skeleton and doll
|
||||
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints);
|
||||
AnimPose offset;
|
||||
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints, offset);
|
||||
AnimSkeleton::Pointer skeletonPtr(skeleton);
|
||||
AnimInverseKinematics ikDoll("doll");
|
||||
ikDoll.setSkeleton(skeletonPtr);
|
||||
|
@ -130,10 +129,13 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
|||
//
|
||||
// A------>B------>C------>D
|
||||
//
|
||||
int indexD = 3;
|
||||
glm::vec3 targetPosition(2.0f, 1.0f, 0.0f);
|
||||
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:
|
||||
//
|
||||
|
@ -143,7 +145,7 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
|||
// A------>B------>C
|
||||
//
|
||||
float dt = 1.0f;
|
||||
ikDoll.evaluate(dt);
|
||||
ikDoll.evaluate(varMap, dt, triggers);
|
||||
|
||||
// verify absolute results
|
||||
// NOTE: since we expect this solution to converge very quickly (one loop)
|
||||
|
@ -204,17 +206,20 @@ void AnimInverseKinematicsTests::testSingleChain() {
|
|||
// |
|
||||
// A------>B t
|
||||
//
|
||||
int indexD = 3;
|
||||
glm::vec3 targetPosition(3.0f, 0.0f, 0.0f);
|
||||
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:
|
||||
//
|
||||
// A------>B------>C------>D
|
||||
//
|
||||
float dt = 1.0f;
|
||||
ikDoll.evaluate(dt);
|
||||
ikDoll.evaluate(varMap, dt, triggers);
|
||||
|
||||
// verify absolute results
|
||||
// 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 outputRotation = inputRotation;
|
||||
|
||||
shoulder.clearHistory();
|
||||
bool updated = shoulder.apply(outputRotation);
|
||||
QVERIFY(updated == false);
|
||||
QCOMPARE_WITH_ABS_ERROR(inputRotation, outputRotation, EPSILON);
|
||||
|
@ -223,6 +224,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
|||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||
glm::quat outputRotation = inputRotation;
|
||||
|
||||
shoulder.clearHistory();
|
||||
bool updated = shoulder.apply(outputRotation);
|
||||
QVERIFY(updated == true);
|
||||
|
||||
|
@ -257,6 +259,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
|||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||
glm::quat outputRotation = inputRotation;
|
||||
|
||||
shoulder.clearHistory();
|
||||
bool updated = shoulder.apply(outputRotation);
|
||||
QVERIFY(updated == true);
|
||||
|
||||
|
@ -291,6 +294,7 @@ void RotationConstraintTests::testSwingTwistConstraint() {
|
|||
glm::quat inputRotation = swingRotation * twistRotation * referenceRotation;
|
||||
glm::quat outputRotation = inputRotation;
|
||||
|
||||
shoulder.clearHistory();
|
||||
bool updated = shoulder.apply(outputRotation);
|
||||
QVERIFY(updated == true);
|
||||
|
||||
|
|
|
@ -52,190 +52,426 @@
|
|||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "mainStateMachine",
|
||||
"type": "stateMachine",
|
||||
"id": "rightHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"currentState": "idle",
|
||||
"states": [
|
||||
{
|
||||
"id": "idle",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkFwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" }
|
||||
]
|
||||
}
|
||||
]
|
||||
"alpha": 1.0,
|
||||
"boneSet": "rightHand",
|
||||
"alphaVar": "rightHandOverlayAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idle",
|
||||
"type": "clip",
|
||||
"id": "rightHandStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
"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": []
|
||||
"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": "walkFwd",
|
||||
"type": "clip",
|
||||
"id": "leftHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 35.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
"alpha": 1.0,
|
||||
"boneSet": "leftHand",
|
||||
"alphaVar" : "leftHandOverlay"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 37.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 28.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 30.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
"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",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "idle",
|
||||
"states": [
|
||||
{
|
||||
"id": "idle",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkFwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idle",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "walkFwd",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 35.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 37.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 28.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 30.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue