Merge pull request #2034 from birarda/master

fixes for scripted avatars
This commit is contained in:
Andrzej Kapolka 2014-02-21 10:14:41 -08:00
commit d8321bd419
20 changed files with 113 additions and 45 deletions

View file

@ -113,6 +113,10 @@ void Agent::run() {
// setup an Avatar for the script to use
AvatarData scriptedAvatar;
// call model URL setters with empty URLs so our avatar, if user, will have the default models
scriptedAvatar.setFaceModelURL(QUrl());
scriptedAvatar.setSkeletonModelURL(QUrl());
// give this AvatarData object to the script engine
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");

View file

@ -29,7 +29,7 @@ class Agent : public ThreadedAssignment {
public:
Agent(const QByteArray& packet);
void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); }
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
bool isAvatar() const { return _scriptEngine.isAvatar(); }
public slots:

View file

@ -93,7 +93,7 @@ void broadcastIdentityPacket() {
NodeList* nodeList = NodeList::getInstance();
QByteArray avatarIdentityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
QByteArray avatarIdentityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
int numPacketHeaderBytes = avatarIdentityPacket.size();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -128,7 +128,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
&& killedNode->getLinkedData()) {
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
killPacket += killedNode->getUUID().toRfc4122();
NodeList::getInstance()->broadcastToNodes(killPacket,
@ -159,7 +159,7 @@ void AvatarMixer::readPendingDatagrams() {
if (nodeData->hasIdentityChangedAfterParsing(receivedPacket)
&& !nodeData->hasSentIdentityBetweenKeyFrames()) {
// this avatar changed their identity in some way and we haven't sent a packet in this keyframe
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
QByteArray individualByteArray = nodeData->identityByteArray();
individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122());

View file

@ -121,7 +121,7 @@ void DataServer::readPendingDatagrams() {
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
// if redis stored the value successfully reply back with a confirm
// which is a reply packet with the sequence number
QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid);
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerConfirm, _uuid);
replyPacket.append(sequenceNumber);
@ -132,7 +132,7 @@ void DataServer::readPendingDatagrams() {
} else {
// setup a send packet with the returned data
// leverage the packetData sent by overwriting and appending
QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid);
QByteArray sendPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerSend, _uuid);
QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
sendPacketStream << sequenceNumber;

View file

@ -221,10 +221,10 @@ void DomainServer::readAvailableDatagrams() {
HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress;
static QByteArray broadcastPacket = byteArrayWithPopluatedHeader(PacketTypeDomainList);
static QByteArray broadcastPacket = byteArrayWithPopulatedHeader(PacketTypeDomainList);
static int numBroadcastPacketHeaderBytes = broadcastPacket.size();
static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment);
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
QByteArray receivedPacket;

34
examples/bot.js Normal file
View file

@ -0,0 +1,34 @@
//
// bot.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/20/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates an NPC avatar.
//
//
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// choose a random x and y in the range of 0 to 50
positionX = getRandomFloat(0, 50);
positionZ = getRandomFloat(0, 50);
// change the avatar's position to the random one
Avatar.position = {x: positionX, y: 0, z: positionZ};
// pick an integer between 1 and 20 for the face model for this bot
botNumber = getRandomInt(1, 20);
// set the face model fst using the bot number
// there is no need to change the body model - we're using the default
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/bot" + botNumber + ".fst";
Agent.isAvatar = true;

View file

@ -253,7 +253,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
// send the identity packet for our avatar each second to our avatar mixer
QTimer* identityPacketTimer = new QTimer();
connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);
identityPacketTimer->start(1000);
identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
@ -2412,7 +2412,7 @@ void Application::updateMyAvatar(float deltaTime) {
_myAvatar->update(deltaTime);
// send head/hand data to the avatar mixer and voxel server
QByteArray packet = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
packet.append(_myAvatar->toByteArray());
controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);

View file

@ -180,7 +180,7 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
}
static QByteArray createDatagramHeader(const QUuid& sessionID) {
QByteArray header = byteArrayWithPopluatedHeader(PacketTypeMetavoxelData);
QByteArray header = byteArrayWithPopulatedHeader(PacketTypeMetavoxelData);
header += sessionID.toRfc4122();
return header;
}

View file

@ -144,7 +144,7 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer<Node> &mixerWeakPointer) {
int bytesRead = numBytesForPacketHeader(datagram);
QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
QByteArray dummyAvatarByteArray = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
int numDummyHeaderBytes = dummyAvatarByteArray.size();
int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID;

View file

@ -644,17 +644,10 @@ void MyAvatar::loadData(QSettings* settings) {
}
void MyAvatar::sendKillAvatar() {
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::sendIdentityPacket() {
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
identityPacket.append(AvatarData::identityByteArray());
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
// first orbit horizontally
glm::quat orientation = getOrientation();

View file

@ -89,7 +89,6 @@ public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void sendIdentityPacket();
// Set/Get update the thrust that will move the avatar around
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };

View file

@ -47,7 +47,7 @@ void AudioInjector::injectAudio() {
NodeList* nodeList = NodeList::getInstance();
// setup the packet for injected audio
QByteArray injectAudioPacket = byteArrayWithPopluatedHeader(PacketTypeInjectAudio);
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
packetStream << QUuid::createUuid();

View file

@ -323,7 +323,6 @@ void AvatarData::setDisplayName(const QString& displayName) {
qDebug() << "Changing display name for avatar to" << displayName;
}
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
@ -338,3 +337,10 @@ void AvatarData::setOrientation(const glm::quat& orientation) {
_bodyYaw = eulerAngles.y;
_bodyRoll = eulerAngles.z;
}
void AvatarData::sendIdentityPacket() {
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
identityPacket.append(identityByteArray());
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}

View file

@ -53,6 +53,8 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
@ -81,8 +83,8 @@ class AvatarData : public NodeData {
Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness)
Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness)
Q_PROPERTY(QUrl faceModelURL READ getFaceModelURL WRITE setFaceModelURL)
Q_PROPERTY(QUrl skeletonModelURL READ getSkeletonModelURL WRITE setSkeletonModelURL)
Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
public:
AvatarData();
~AvatarData();
@ -150,14 +152,24 @@ public:
QByteArray identityByteArray();
const QUrl& getFaceModelURL() const { return _faceModelURL; }
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
const QString& getDisplayName() const { return _displayName; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setDisplayName(const QString& displayName);
QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); }
void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); }
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
virtual float getBoundingRadius() const { return 1.f; }
public slots:
void sendIdentityPacket();
protected:
glm::vec3 _position;
glm::vec3 _handPosition;

View file

@ -44,6 +44,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
AbstractMenuInterface* menu,
AbstractControllerScriptingInterface* controllerScriptingInterface) :
_isAvatar(false),
_avatarIdentityTimer(NULL),
_avatarData(NULL)
{
_scriptContents = scriptContents;
@ -74,6 +75,21 @@ ScriptEngine::~ScriptEngine() {
//printf("ScriptEngine::~ScriptEngine()...\n");
}
void ScriptEngine::setIsAvatar(bool isAvatar) {
_isAvatar = isAvatar;
if (_isAvatar && !_avatarIdentityTimer) {
// set up the avatar identity timer
_avatarIdentityTimer = new QTimer(this);
// connect our slot
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
// start the timer
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
}
}
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
_avatarData = avatarData;
@ -84,6 +100,7 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa
registerGlobalObject(objectName, _avatarData);
}
void ScriptEngine::setupMenuItems() {
if (_menu && _wantMenuItems) {
_menu->addActionToQMenuAndActionHash(_menu->getActiveScriptsMenu(), _scriptMenuName, 0, this, SLOT(stop()));
@ -173,6 +190,12 @@ void ScriptEngine::evaluate() {
}
}
void ScriptEngine::sendAvatarIdentityPacket() {
if (_isAvatar && _avatarData) {
_avatarData->sendIdentityPacket();
}
}
void ScriptEngine::run() {
if (!_isInitialized) {
init();
@ -229,16 +252,7 @@ void ScriptEngine::run() {
}
if (_isAvatar && _avatarData) {
static QByteArray avatarPacket;
int numAvatarHeaderBytes = 0;
if (avatarPacket.size() == 0) {
// pack the avatar header bytes the first time
// unlike the _avatar.getBroadcastData these won't change
numAvatarHeaderBytes = populatePacketHeader(avatarPacket, PacketTypeAvatarData);
}
avatarPacket.resize(numAvatarHeaderBytes);
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
avatarPacket.append(_avatarData->toByteArray());
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
@ -253,6 +267,9 @@ void ScriptEngine::run() {
}
emit scriptEnding();
// kill the avatar identity timer
delete _avatarIdentityTimer;
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
// release the queue of edit voxel messages.
_voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();

View file

@ -11,9 +11,9 @@
#include <vector>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtScript/QScriptEngine>
#include <AbstractMenuInterface.h>
#include <AudioScriptingInterface.h>
@ -52,7 +52,7 @@ public:
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
void setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; }
Q_INVOKABLE void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
void setAvatarData(AvatarData* avatarData, const QString& objectName);
@ -84,9 +84,12 @@ protected:
bool _isInitialized;
QScriptEngine _engine;
bool _isAvatar;
QTimer* _avatarIdentityTimer;
QHash<QTimer*, QScriptValue> _timerFunctionMap;
private:
void sendAvatarIdentityPacket();
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer);

View file

@ -31,7 +31,7 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() {
void DataServerClient::putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString) {
// setup the header for this packet and push packetStream to desired spot
QByteArray putPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerPut);
QByteArray putPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerPut);
QDataStream packetStream(&putPacket, QIODevice::Append);
// pack our data for the put packet
@ -66,7 +66,7 @@ void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QU
void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString,
DataServerCallbackObject* callbackObject) {
if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) {
QByteArray getPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerGet);
QByteArray getPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerGet);
QDataStream packetStream(&getPacket, QIODevice::Append);
// pack our data for the getPacket

View file

@ -531,7 +531,7 @@ void NodeList::sendDomainServerCheckIn() {
// construct the DS check in packet if we can
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
QByteArray domainServerPacket = byteArrayWithPopluatedHeader(PacketTypeDomainListRequest);
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server
@ -619,7 +619,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
? PacketTypeCreateAssignment
: PacketTypeRequestAssignment;
QByteArray packet = byteArrayWithPopluatedHeader(assignmentPacketType);
QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType);
QDataStream packetStream(&packet, QIODevice::Append);
packetStream << assignment;
@ -634,7 +634,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
}
QByteArray NodeList::constructPingPacket(PingType_t pingType) {
QByteArray pingPacket = byteArrayWithPopluatedHeader(PacketTypePing);
QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing);
QDataStream packetStream(&pingPacket, QIODevice::Append);
@ -654,7 +654,7 @@ QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypePingReply);
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();

View file

@ -65,7 +65,7 @@ PacketVersion versionForPacketType(PacketType type) {
}
}
QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID) {
QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID) {
QByteArray freshByteArray(MAX_PACKET_HEADER_BYTES, 0);
freshByteArray.resize(populatePacketHeader(freshByteArray, type, connectionUUID));
return freshByteArray;

View file

@ -66,7 +66,7 @@ PacketVersion versionForPacketType(PacketType type);
const QUuid nullUUID = QUuid();
QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID);
QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID);
int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID);
int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID);