mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-05 20:36:28 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into poseFilters
This commit is contained in:
commit
829fa2b323
556 changed files with 103438 additions and 6038 deletions
|
@ -34,6 +34,7 @@ module.exports = {
|
|||
"Quat": false,
|
||||
"Rates": false,
|
||||
"Recording": false,
|
||||
"Resource": false,
|
||||
"Reticle": false,
|
||||
"Scene": false,
|
||||
"Script": false,
|
||||
|
|
|
@ -5,7 +5,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
|||
You will need the following tools to build our Android targets.
|
||||
|
||||
* [cmake](http://www.cmake.org/download/) ~> 3.5.1
|
||||
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.5.1
|
||||
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.6.2
|
||||
* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4
|
||||
* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d
|
||||
* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1
|
||||
|
|
|
@ -210,6 +210,7 @@ endforeach()
|
|||
|
||||
file(GLOB_RECURSE JS_SRC scripts/*.js)
|
||||
add_custom_target(js SOURCES ${JS_SRC})
|
||||
GroupSources("scripts")
|
||||
|
||||
if (UNIX)
|
||||
install(
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <ScriptCache.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <SoundCache.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include <recording/ClipCache.h>
|
||||
|
@ -75,6 +76,8 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
|
||||
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<UsersScriptingInterface>();
|
||||
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
|
@ -351,6 +354,10 @@ void Agent::executeScript() {
|
|||
// give this AvatarData object to the script engine
|
||||
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());
|
||||
|
||||
// give scripts access to the Users object
|
||||
_scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
||||
|
||||
|
||||
auto player = DependencyManager::get<recording::Deck>();
|
||||
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
|
||||
if (player->isPlaying()) {
|
||||
|
@ -537,7 +544,7 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
|
||||
|
||||
// start the timers
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
|
||||
|
||||
// tell the avatarAudioTimer to start ticking
|
||||
emit startAvatarAudioTimer();
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
//
|
||||
// AssignmentAction.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntitySimulation.h"
|
||||
|
||||
#include "AssignmentAction.h"
|
||||
|
||||
AssignmentAction::AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
EntityActionInterface(type, id),
|
||||
_data(QByteArray()),
|
||||
_active(false),
|
||||
_ownerEntity(ownerEntity) {
|
||||
}
|
||||
|
||||
AssignmentAction::~AssignmentAction() {
|
||||
}
|
||||
|
||||
void AssignmentAction::removeFromSimulation(EntitySimulationPointer simulation) const {
|
||||
withReadLock([&]{
|
||||
simulation->removeAction(_id);
|
||||
simulation->applyActionChanges();
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray AssignmentAction::serialize() const {
|
||||
QByteArray result;
|
||||
withReadLock([&]{
|
||||
result = _data;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void AssignmentAction::deserialize(QByteArray serializedArguments) {
|
||||
withWriteLock([&]{
|
||||
_data = serializedArguments;
|
||||
});
|
||||
}
|
||||
|
||||
bool AssignmentAction::updateArguments(QVariantMap arguments) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::updateArguments called in assignment-client.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap AssignmentAction::getArguments() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getArguments called in assignment-client.";
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getPosition() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getPosition called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setPosition(glm::vec3 position) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setPosition called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::quat AssignmentAction::getRotation() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getRotation called in assignment-client.";
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
void AssignmentAction::setRotation(glm::quat rotation) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setRotation called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getLinearVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getLinearVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setLinearVelocity(glm::vec3 linearVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setLinearVelocity called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentAction::getAngularVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::getAngularVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentAction::setAngularVelocity(glm::vec3 angularVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentAction::setAngularVelocity called in assignment-client.";
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
//
|
||||
// AssignmentActionFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
||||
|
||||
EntityActionPointer assignmentActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
|
||||
return EntityActionPointer(new AssignmentAction(type, id, ownerEntity));
|
||||
}
|
||||
|
||||
EntityActionPointer AssignmentActionFactory::factory(EntityActionType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
|
||||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer AssignmentActionFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
|
||||
QDataStream serializedActionDataStream(data);
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
|
||||
serializedActionDataStream >> type;
|
||||
serializedActionDataStream >> id;
|
||||
|
||||
EntityActionPointer action = assignmentActionFactory(type, id, ownerEntity);
|
||||
|
||||
if (action) {
|
||||
action->deserialize(data);
|
||||
}
|
||||
return action;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// AssignmentActionFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentActionFactory_h
|
||||
#define hifi_AssignmentActionFactory_h
|
||||
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "AssignmentAction.h"
|
||||
|
||||
class AssignmentActionFactory : public EntityActionFactoryInterface {
|
||||
public:
|
||||
AssignmentActionFactory() : EntityActionFactoryInterface() { }
|
||||
virtual ~AssignmentActionFactory() { }
|
||||
virtual EntityActionPointer factory(EntityActionType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) override;
|
||||
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentActionFactory_h
|
|
@ -30,9 +30,10 @@
|
|||
#include <ShutdownEventListener.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentActionFactory.h"
|
||||
#include "AssignmentDynamicFactory.h"
|
||||
|
||||
#include "AssignmentClient.h"
|
||||
#include "AssignmentClientLogging.h"
|
||||
|
@ -63,9 +64,10 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
auto animationCache = DependencyManager::set<AnimationCache>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>(false);
|
||||
|
||||
DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>();
|
||||
auto actionFactory = DependencyManager::set<AssignmentActionFactory>();
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
auto dynamicFactory = DependencyManager::set<AssignmentDynamicFactory>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||
|
||||
// setup a thread for the NodeList and its PacketReceiver
|
||||
QThread* nodeThread = new QThread(this);
|
||||
|
|
83
assignment-client/src/AssignmentDynamic.cpp
Normal file
83
assignment-client/src/AssignmentDynamic.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// AssignmentDynamic.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntitySimulation.h"
|
||||
|
||||
#include "AssignmentDynamic.h"
|
||||
|
||||
AssignmentDynamic::AssignmentDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
EntityDynamicInterface(type, id),
|
||||
_data(QByteArray()),
|
||||
_active(false),
|
||||
_ownerEntity(ownerEntity) {
|
||||
}
|
||||
|
||||
AssignmentDynamic::~AssignmentDynamic() {
|
||||
}
|
||||
|
||||
void AssignmentDynamic::removeFromSimulation(EntitySimulationPointer simulation) const {
|
||||
withReadLock([&]{
|
||||
simulation->removeDynamic(_id);
|
||||
simulation->applyDynamicChanges();
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray AssignmentDynamic::serialize() const {
|
||||
QByteArray result;
|
||||
withReadLock([&]{
|
||||
result = _data;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void AssignmentDynamic::deserialize(QByteArray serializedArguments) {
|
||||
withWriteLock([&]{
|
||||
_data = serializedArguments;
|
||||
});
|
||||
}
|
||||
|
||||
bool AssignmentDynamic::updateArguments(QVariantMap arguments) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::updateArguments called in assignment-client.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap AssignmentDynamic::getArguments() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getArguments called in assignment-client.";
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getPosition() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getPosition called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::quat AssignmentDynamic::getRotation() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getRotation called in assignment-client.";
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getLinearVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getLinearVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentDynamic::setLinearVelocity(glm::vec3 linearVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::setLinearVelocity called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getAngularVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getAngularVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentDynamic::setAngularVelocity(glm::vec3 angularVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::setAngularVelocity called in assignment-client.";
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// AssignmentAction.h
|
||||
// AssignmentDynamic.h
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves 2015-6-19
|
||||
|
@ -8,21 +8,21 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtActionInterface.html
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtDynamicInterface.html
|
||||
|
||||
#ifndef hifi_AssignmentAction_h
|
||||
#define hifi_AssignmentAction_h
|
||||
#ifndef hifi_AssignmentDynamic_h
|
||||
#define hifi_AssignmentDynamic_h
|
||||
|
||||
#include <QUuid>
|
||||
#include <EntityItem.h>
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
|
||||
class AssignmentAction : public EntityActionInterface, public ReadWriteLockable {
|
||||
class AssignmentDynamic : public EntityDynamicInterface, public ReadWriteLockable {
|
||||
public:
|
||||
AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~AssignmentAction();
|
||||
AssignmentDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~AssignmentDynamic();
|
||||
|
||||
virtual void removeFromSimulation(EntitySimulationPointer simulation) const override;
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
|
||||
|
@ -38,9 +38,7 @@ private:
|
|||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() override;
|
||||
virtual void setPosition(glm::vec3 position) override;
|
||||
virtual glm::quat getRotation() override;
|
||||
virtual void setRotation(glm::quat rotation) override;
|
||||
virtual glm::vec3 getLinearVelocity() override;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
|
||||
virtual glm::vec3 getAngularVelocity() override;
|
||||
|
@ -50,4 +48,4 @@ protected:
|
|||
EntityItemWeakPointer _ownerEntity;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentAction_h
|
||||
#endif // hifi_AssignmentDynamic_h
|
48
assignment-client/src/AssignmentDynamicFactory.cpp
Normal file
48
assignment-client/src/AssignmentDynamicFactory.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// AssignmentDynamcFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssignmentDynamicFactory.h"
|
||||
|
||||
|
||||
EntityDynamicPointer assignmentDynamicFactory(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) {
|
||||
return EntityDynamicPointer(new AssignmentDynamic(type, id, ownerEntity));
|
||||
}
|
||||
|
||||
EntityDynamicPointer AssignmentDynamicFactory::factory(EntityDynamicType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityDynamicPointer dynamic = assignmentDynamicFactory(type, id, ownerEntity);
|
||||
if (dynamic) {
|
||||
bool ok = dynamic->updateArguments(arguments);
|
||||
if (ok) {
|
||||
return dynamic;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityDynamicPointer AssignmentDynamicFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
|
||||
QDataStream serializedDynamicDataStream(data);
|
||||
EntityDynamicType type;
|
||||
QUuid id;
|
||||
|
||||
serializedDynamicDataStream >> type;
|
||||
serializedDynamicDataStream >> id;
|
||||
|
||||
EntityDynamicPointer dynamic = assignmentDynamicFactory(type, id, ownerEntity);
|
||||
|
||||
if (dynamic) {
|
||||
dynamic->deserialize(data);
|
||||
}
|
||||
return dynamic;
|
||||
}
|
29
assignment-client/src/AssignmentDynamicFactory.h
Normal file
29
assignment-client/src/AssignmentDynamicFactory.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// AssignmentDynamicFactory.cpp
|
||||
// assignment-client/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-19
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentDynamicFactory_h
|
||||
#define hifi_AssignmentDynamicFactory_h
|
||||
|
||||
#include "EntityDynamicFactoryInterface.h"
|
||||
#include "AssignmentDynamic.h"
|
||||
|
||||
class AssignmentDynamicFactory : public EntityDynamicFactoryInterface {
|
||||
public:
|
||||
AssignmentDynamicFactory() : EntityDynamicFactoryInterface() { }
|
||||
virtual ~AssignmentDynamicFactory() { }
|
||||
virtual EntityDynamicPointer factory(EntityDynamicType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) override;
|
||||
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentDynamicFactory_h
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "SendAssetTask.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
@ -21,6 +23,7 @@
|
|||
#include <udt/Packet.h>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ByteRange.h"
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
||||
|
@ -34,20 +37,21 @@ SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const Shar
|
|||
|
||||
void SendAssetTask::run() {
|
||||
MessageID messageID;
|
||||
DataOffset start, end;
|
||||
|
||||
ByteRange byteRange;
|
||||
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
// starting at index 1.
|
||||
_message->readPrimitive(&start);
|
||||
_message->readPrimitive(&end);
|
||||
_message->readPrimitive(&byteRange.fromInclusive);
|
||||
_message->readPrimitive(&byteRange.toExclusive);
|
||||
|
||||
QString hexHash = assetHash.toHex();
|
||||
|
||||
qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from " << start << " to " << end;
|
||||
qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from "
|
||||
<< byteRange.fromInclusive << " to " << byteRange.toExclusive;
|
||||
|
||||
qDebug() << "Starting task to send asset: " << hexHash << " for messageID " << messageID;
|
||||
auto replyPacketList = NLPacketList::create(PacketType::AssetGetReply, QByteArray(), true, true);
|
||||
|
@ -56,7 +60,7 @@ void SendAssetTask::run() {
|
|||
|
||||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
if (end <= start) {
|
||||
if (!byteRange.isValid()) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
} else {
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||
|
@ -64,15 +68,40 @@ void SendAssetTask::run() {
|
|||
QFile file { filePath };
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
if (file.size() < end) {
|
||||
|
||||
// first fixup the range based on the now known file size
|
||||
byteRange.fixupRange(file.size());
|
||||
|
||||
// check if we're being asked to read data that we just don't have
|
||||
// because of the file size
|
||||
if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " " << start << ":" << end;
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " "
|
||||
<< byteRange.fromInclusive << ":" << byteRange.toExclusive;
|
||||
} else {
|
||||
auto size = end - start;
|
||||
file.seek(start);
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
replyPacketList->write(file.read(size));
|
||||
// we have a valid byte range, handle it and send the asset
|
||||
auto size = byteRange.size();
|
||||
|
||||
if (byteRange.fromInclusive >= 0) {
|
||||
|
||||
// this range is positive, meaning we just need to seek into the file and then read from there
|
||||
file.seek(byteRange.fromInclusive);
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
replyPacketList->write(file.read(size));
|
||||
} else {
|
||||
// this range is negative, at least the first part of the read will be back into the end of the file
|
||||
|
||||
// seek to the part of the file where the negative range begins
|
||||
file.seek(file.size() + byteRange.fromInclusive);
|
||||
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
|
||||
// first write everything from the negative range to the end of the file
|
||||
replyPacketList->write(file.read(size));
|
||||
}
|
||||
|
||||
qCDebug(networking) << "Sending asset: " << hexHash;
|
||||
}
|
||||
file.close();
|
||||
|
|
|
@ -71,15 +71,10 @@ AvatarMixer::~AvatarMixer() {
|
|||
|
||||
void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
|
||||
QByteArray individualData = nodeData->getAvatar().identityByteArray();
|
||||
|
||||
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
|
||||
|
||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122());
|
||||
|
||||
identityPacket->write(individualData);
|
||||
|
||||
DependencyManager::get<NodeList>()->sendPacket(std::move(identityPacket), *destinationNode);
|
||||
|
||||
auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||
identityPackets->write(individualData);
|
||||
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPackets), *destinationNode);
|
||||
++_sumIdentityPackets;
|
||||
}
|
||||
|
||||
|
|
|
@ -263,16 +263,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
// make sure we haven't already sent this data from this sender to this receiver
|
||||
// or that somehow we haven't sent
|
||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||
// don't ignore this avatar if we haven't sent any update for a long while
|
||||
// in an effort to prevent other interfaces from deleting a stale avatar instance
|
||||
uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID());
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND;
|
||||
if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() &&
|
||||
lastBroadcastTime + AVATAR_UPDATE_STALE > startIgnoreCalculation) {
|
||||
++numAvatarsHeldBack;
|
||||
shouldIgnore = true;
|
||||
}
|
||||
++numAvatarsHeldBack;
|
||||
shouldIgnore = true;
|
||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||
++numAvatarsWithSkippedFrames;
|
||||
|
@ -285,7 +277,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
int avatarRank = 0;
|
||||
|
||||
// this is overly conservative, because it includes some avatars we might not consider
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
|
||||
while (!sortedAvatars.empty()) {
|
||||
AvatarPriority sortData = sortedAvatars.top();
|
||||
|
|
|
@ -481,14 +481,14 @@ void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
|
|||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool reload) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
||||
|
||||
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
|
||||
|
|
|
@ -67,8 +67,8 @@ private:
|
|||
|
||||
void addingEntity(const EntityItemID& entityID);
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void entityServerScriptChanging(const EntityItemID& entityID, const bool reload);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||
void entityServerScriptChanging(const EntityItemID& entityID, bool reload);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, bool reload = false);
|
||||
|
||||
void cleanupOldKilledListeners();
|
||||
|
||||
|
|
87
cmake/externals/nvtt/CMakeLists.txt
vendored
Normal file
87
cmake/externals/nvtt/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
set(EXTERNAL_NAME nvtt)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/nvtt-win-2.1.0.zip
|
||||
URL_MD5 3ea6eeadbcc69071acf9c49ba565760e
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "Location of NVTT include directory")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Release/x64/nvtt.lib CACHE FILEPATH "Path to NVTT release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/Release>/x64" CACHE PATH "Location of NVTT release DLL")
|
||||
else ()
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.zip
|
||||
URL_MD5 81b8fa6a9ee3f986088eb6e2215d6a57
|
||||
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "Location of NVTT include directory")
|
||||
|
||||
if (APPLE)
|
||||
set(_LIB_EXT "dylib")
|
||||
else ()
|
||||
set(_LIB_EXT "so")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libnvtt.${_LIB_EXT} CACHE FILEPATH "Path to NVTT library")
|
||||
|
||||
if (APPLE)
|
||||
# on OS X we have to use install_name_tool to fix the paths found in the NVTT shared libraries
|
||||
# so that they can be found and linked during the linking phase
|
||||
set(_NVTT_LIB_DIR "${INSTALL_DIR}/lib")
|
||||
|
||||
# first fix the install names of all present libraries
|
||||
ExternalProject_Add_Step(
|
||||
${EXTERNAL_NAME}
|
||||
change-install-name
|
||||
COMMENT "Calling install_name_tool on NVTT libraries to fix install name for dylib linking"
|
||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_NVTT_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
||||
DEPENDEES install
|
||||
WORKING_DIRECTORY <INSTALL_DIR>
|
||||
LOG 1
|
||||
)
|
||||
|
||||
# then, for the main library (libnvtt) fix the paths to the dependency libraries (core, image, math)
|
||||
ExternalProject_Add_Step(
|
||||
${EXTERNAL_NAME}
|
||||
change-dependency-paths
|
||||
COMMENT "Calling install_name_tool on NVTT libraries to fix paths for dependency libraries"
|
||||
COMMAND install_name_tool -change libnvimage.dylib ${INSTALL_DIR}/lib/libnvimage.dylib libnvtt.dylib
|
||||
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvtt.dylib
|
||||
COMMAND install_name_tool -change libnvmath.dylib ${INSTALL_DIR}/lib/libnvmath.dylib libnvtt.dylib
|
||||
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvimage.dylib
|
||||
COMMAND install_name_tool -change libnvmath.dylib ${INSTALL_DIR}/lib/libnvmath.dylib libnvimage.dylib
|
||||
COMMAND install_name_tool -change libnvcore.dylib ${INSTALL_DIR}/lib/libnvcore.dylib libnvmath.dylib
|
||||
DEPENDEES install
|
||||
WORKING_DIRECTORY <INSTALL_DIR>/lib
|
||||
LOG 1
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for IDE users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
37
cmake/modules/FindNVTT.cmake
Normal file
37
cmake/modules/FindNVTT.cmake
Normal file
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# FindNVTT.cmake
|
||||
#
|
||||
# Try to find NVIDIA texture tools library and include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# NVTT_FOUND
|
||||
# NVTT_INCLUDE_DIRS
|
||||
# NVTT_LIBRARIES
|
||||
# NVTT_DLL_PATH
|
||||
#
|
||||
# Created on 4/14/2017 by Stephen Birarda
|
||||
# Copyright 2017 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("nvtt")
|
||||
|
||||
find_path(NVTT_INCLUDE_DIRS nvtt/nvtt.h PATH_SUFFIXES include HINTS ${NVTT_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_library(NVTT_LIBRARY_RELEASE nvtt PATH_SUFFIXES "lib" "Release.x64/lib" HINTS ${NVTT_SEARCH_DIRS})
|
||||
find_library(NVTT_LIBRARY_DEBUG nvtt PATH_SUFFIXES "lib" "Debug.x64/lib" HINTS ${NVTT_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(NVTT)
|
||||
|
||||
if (WIN32)
|
||||
find_path(NVTT_DLL_PATH nvtt.dll PATH_SUFFIXES "Release.x64/bin" HINTS ${NVTT_SEARCH_DIRS})
|
||||
find_package_handle_standard_args(NVTT DEFAULT_MSG NVTT_INCLUDE_DIRS NVTT_LIBRARIES NVTT_DLL_PATH)
|
||||
else ()
|
||||
find_package_handle_standard_args(NVTT DEFAULT_MSG NVTT_INCLUDE_DIRS NVTT_LIBRARIES)
|
||||
endif ()
|
|
@ -435,23 +435,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
QUuid hintNodeID;
|
||||
|
||||
// in case this is a node that's failing to connect
|
||||
// double check we don't have a node whose sockets match exactly already in the list
|
||||
limitedNodeList->eachNodeBreakable([&nodeConnection, &hintNodeID](const SharedNodePointer& node){
|
||||
if (node->getPublicSocket() == nodeConnection.publicSockAddr
|
||||
&& node->getLocalSocket() == nodeConnection.localSockAddr) {
|
||||
// we have a node that already has these exact sockets - this occurs if a node
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
|
||||
// set the edit rights for this user
|
||||
newNode->setPermissions(userPerms);
|
||||
|
@ -479,11 +464,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
return newNode;
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID) {
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
|
||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||
|
||||
QUuid nodeID;
|
||||
|
||||
if (connectedPeer) {
|
||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||
nodeID = nodeConnection.connectUUID;
|
||||
|
@ -493,10 +479,8 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
|||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
} else {
|
||||
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
|
||||
if (nodeID.isNull()) {
|
||||
nodeID = QUuid::createUuid();
|
||||
}
|
||||
// we got a connectUUID we didn't recognize, randomly generate a new one
|
||||
nodeID = QUuid::createUuid();
|
||||
}
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
|
|
@ -76,8 +76,7 @@ private:
|
|||
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID = QUuid());
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
|
||||
|
||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
|
|
@ -65,6 +65,7 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
|||
|
||||
file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js)
|
||||
add_custom_target(qml SOURCES ${QML_SRC})
|
||||
GroupSources("resources/qml")
|
||||
|
||||
if (UNIX)
|
||||
install(
|
||||
|
@ -193,7 +194,7 @@ link_hifi_libraries(
|
|||
recording fbx networking model-networking entities avatars
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer avatars-renderer ui auto-updater
|
||||
controllers plugins
|
||||
controllers plugins image
|
||||
ui-plugins display-plugins input-plugins
|
||||
${NON_ANDROID_LIBRARIES}
|
||||
)
|
||||
|
|
Binary file not shown.
BIN
interface/resources/icons/loader-red-countdown-ring.gif
Normal file
BIN
interface/resources/icons/loader-red-countdown-ring.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
109
interface/resources/icons/tablet-icons/avatar-record-a.svg
Normal file
109
interface/resources/icons/tablet-icons/avatar-record-a.svg
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="avatar-record-a.svg"
|
||||
inkscape:version="0.92.1 r15371"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1829"
|
||||
inkscape:window-height="1057"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="-9.4279661"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="83"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="g879"><path
|
||||
class="st0"
|
||||
d="m 23.2,20.5 c -1,0.8 -1.8,1.4 -2.7,2.1 -0.2,0.1 -0.2,0.4 -0.2,0.7 -0.3,1.7 -0.6,3.4 -0.9,5.1 -0.1,0.8 -0.6,1.2 -1.3,1.1 -0.7,-0.1 -1.2,-0.7 -1.1,-1.4 0.3,-2.2 0.6,-4.4 1,-6.6 0.1,-0.3 0.3,-0.7 0.6,-0.9 1.4,-1.3 2.8,-2.5 4.2,-3.7 0.7,-0.6 1.5,-1 2.4,-0.9 0.3,0 0.7,0 1,0 1,-0.1 1.7,0.4 2.1,1.3 0.7,1.4 1.4,2.8 1.9,4.3 0.5,1.3 1.2,2.1 2.4,2.6 1,0.4 2,1 3,1.5 0.2,0.1 0.5,0.3 0.7,0.5 0.4,0.4 0.5,1 0.3,1.4 C 36.4,28 36,28.1 35.5,28 35.1,27.9 34.7,27.8 34.3,27.6 33,27 31.8,26.4 30.6,25.8 29.8,25.5 29.2,25 28.8,24.2 c -0.2,-0.3 -0.4,-0.6 -0.7,-1 -0.1,0.3 -0.1,0.5 -0.2,0.7 -0.3,1.2 -0.5,2.4 -0.8,3.6 -0.1,0.4 0,0.7 0.2,1 2.2,3.7 4.4,7.4 6.6,11.1 0.3,0.4 0.4,1 0.5,1.5 0.1,0.7 -0.1,1.3 -0.7,1.6 C 33,43.1 32.3,43.1 31.8,42.6 31.4,42.2 31,41.8 30.7,41.3 28.2,37.4 25.7,33.4 23.2,29.5 22.8,28.8 22.4,28 22.1,27.3 22,26.9 22,26.4 22.1,26 c 0.4,-1.8 0.7,-3.6 1.1,-5.5 z"
|
||||
id="path5"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M 23.2,33.9 C 23.1,33.8 23,33.7 23,33.6 c 0,0 0,0 0,0 -0.2,-0.2 -0.3,-0.5 -0.5,-0.7 -0.3,-0.4 -0.6,-0.8 -0.9,-1.1 -0.3,1 -0.5,2 -0.8,3 -0.1,0.3 -0.3,0.7 -0.4,1 -1,1.5 -2,3.1 -3,4.6 -0.2,0.4 -0.4,0.8 -0.6,1.3 -0.2,0.9 0.7,1.9 1.6,1.5 0.5,-0.2 1,-0.7 1.3,-1.1 0.9,-1.1 1.6,-2.3 2.5,-3.3 0.8,-1 1.4,-2.2 1.8,-3.4 -0.2,-0.7 -0.5,-1.1 -0.8,-1.5 z"
|
||||
id="path7"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M 29,11.6 C 29,12.9 27.9,14 26.6,14 H 26.4 C 25.1,14 24,12.9 24,11.6 V 10.4 C 24,9.1 25.1,8 26.4,8 h 0.2 c 1.3,0 2.4,1.1 2.4,2.4 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="m 43.4,24.1 c -0.5,0.3 -0.9,0.5 -1.4,0.8 v 6.3 h 2.3 v -7.6 c -0.3,0.2 -0.6,0.3 -0.9,0.5 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M 42,38.6 V 39 c 0,1.2 -1,2.1 -2.1,2.1 h -0.8 v 2.3 h 0.8 c 2.5,0 4.5,-2 4.5,-4.5 V 38.5 H 42 Z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="m 9.7,12.2 v -0.4 c 0,-1.2 1,-2.1 2.1,-2.1 h 2 V 7.3 h -2 c -2.5,0 -4.5,2 -4.5,4.5 v 0.4 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><rect
|
||||
x="7.4000001"
|
||||
y="18.299999"
|
||||
class="st0"
|
||||
width="2.3"
|
||||
height="12.9"
|
||||
id="rect17"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M 9.7,38.9 V 38.5 H 7.4 v 0.4 c 0,2.5 2,4.5 4.5,4.5 h 2 v -2.3 h -2 c -1.2,0 -2.2,-1 -2.2,-2.2 z"
|
||||
id="path19"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /><g
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
id="g25"><circle
|
||||
class="st0"
|
||||
cx="38.599998"
|
||||
cy="13.3"
|
||||
r="2.2"
|
||||
id="circle21"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="m 38.6,15.5 c -1.2,0 -2.2,-1 -2.2,-2.2 0,-1.2 1,-2.2 2.2,-2.2 1.2,0 2.2,1 2.2,2.2 0,1.2 -1,2.2 -2.2,2.2 z m 0,-4.3 c -1.1,0 -2.1,0.9 -2.1,2.1 0,1.2 0.9,2.1 2.1,2.1 1.1,0 2.1,-0.9 2.1,-2.1 0,-1.2 -1,-2.1 -2.1,-2.1 z"
|
||||
id="path23"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /></g><path
|
||||
class="st0"
|
||||
d="m 38.6,19.7 c -3.6,0 -6.4,-2.9 -6.4,-6.4 0,-3.5 2.9,-6.4 6.4,-6.4 3.6,0 6.4,2.9 6.4,6.4 0,3.5 -2.9,6.4 -6.4,6.4 z m 0,-10.6 c -2.3,0 -4.2,1.9 -4.2,4.2 0,2.3 1.9,4.2 4.2,4.2 2.3,0 4.2,-1.9 4.2,-4.2 0,-2.3 -1.9,-4.2 -4.2,-4.2 z"
|
||||
id="path27"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 5.4 KiB |
36
interface/resources/icons/tablet-icons/avatar-record-i.svg
Normal file
36
interface/resources/icons/tablet-icons/avatar-record-i.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M23.2,20.5c-1,0.8-1.8,1.4-2.7,2.1c-0.2,0.1-0.2,0.4-0.2,0.7c-0.3,1.7-0.6,3.4-0.9,5.1
|
||||
c-0.1,0.8-0.6,1.2-1.3,1.1c-0.7-0.1-1.2-0.7-1.1-1.4c0.3-2.2,0.6-4.4,1-6.6c0.1-0.3,0.3-0.7,0.6-0.9c1.4-1.3,2.8-2.5,4.2-3.7
|
||||
c0.7-0.6,1.5-1,2.4-0.9c0.3,0,0.7,0,1,0c1-0.1,1.7,0.4,2.1,1.3c0.7,1.4,1.4,2.8,1.9,4.3c0.5,1.3,1.2,2.1,2.4,2.6c1,0.4,2,1,3,1.5
|
||||
c0.2,0.1,0.5,0.3,0.7,0.5c0.4,0.4,0.5,1,0.3,1.4C36.4,28,36,28.1,35.5,28c-0.4-0.1-0.8-0.2-1.2-0.4c-1.3-0.6-2.5-1.2-3.7-1.8
|
||||
c-0.8-0.3-1.4-0.8-1.8-1.6c-0.2-0.3-0.4-0.6-0.7-1c-0.1,0.3-0.1,0.5-0.2,0.7c-0.3,1.2-0.5,2.4-0.8,3.6c-0.1,0.4,0,0.7,0.2,1
|
||||
c2.2,3.7,4.4,7.4,6.6,11.1c0.3,0.4,0.4,1,0.5,1.5c0.1,0.7-0.1,1.3-0.7,1.6c-0.7,0.4-1.4,0.4-1.9-0.1c-0.4-0.4-0.8-0.8-1.1-1.3
|
||||
c-2.5-3.9-5-7.9-7.5-11.8c-0.4-0.7-0.8-1.5-1.1-2.2c-0.1-0.4-0.1-0.9,0-1.3C22.5,24.2,22.8,22.4,23.2,20.5z"/>
|
||||
<path class="st0" d="M23.2,33.9c-0.1-0.1-0.2-0.2-0.2-0.3c0,0,0,0,0,0c-0.2-0.2-0.3-0.5-0.5-0.7c-0.3-0.4-0.6-0.8-0.9-1.1
|
||||
c-0.3,1-0.5,2-0.8,3c-0.1,0.3-0.3,0.7-0.4,1c-1,1.5-2,3.1-3,4.6c-0.2,0.4-0.4,0.8-0.6,1.3c-0.2,0.9,0.7,1.9,1.6,1.5
|
||||
c0.5-0.2,1-0.7,1.3-1.1c0.9-1.1,1.6-2.3,2.5-3.3c0.8-1,1.4-2.2,1.8-3.4C23.8,34.7,23.5,34.3,23.2,33.9z"/>
|
||||
<path class="st0" d="M29,11.6c0,1.3-1.1,2.4-2.4,2.4h-0.2c-1.3,0-2.4-1.1-2.4-2.4v-1.2C24,9.1,25.1,8,26.4,8h0.2
|
||||
c1.3,0,2.4,1.1,2.4,2.4V11.6z"/>
|
||||
<path class="st0" d="M43.4,24.1c-0.5,0.3-0.9,0.5-1.4,0.8v6.3h2.3v-7.6C44,23.8,43.7,23.9,43.4,24.1z"/>
|
||||
<path class="st0" d="M42,38.6v0.4c0,1.2-1,2.1-2.1,2.1h-0.8v2.3h0.8c2.5,0,4.5-2,4.5-4.5v-0.4H42z"/>
|
||||
<path class="st0" d="M9.7,12.2v-0.4c0-1.2,1-2.1,2.1-2.1h2V7.3h-2c-2.5,0-4.5,2-4.5,4.5v0.4H9.7z"/>
|
||||
<rect x="7.4" y="18.3" class="st0" width="2.3" height="12.9"/>
|
||||
<path class="st0" d="M9.7,38.9v-0.4H7.4v0.4c0,2.5,2,4.5,4.5,4.5h2v-2.3h-2C10.7,41.1,9.7,40.1,9.7,38.9z"/>
|
||||
<g>
|
||||
<circle class="st0" cx="38.6" cy="13.3" r="2.2"/>
|
||||
<path class="st0" d="M38.6,15.5c-1.2,0-2.2-1-2.2-2.2s1-2.2,2.2-2.2c1.2,0,2.2,1,2.2,2.2S39.8,15.5,38.6,15.5z M38.6,11.2
|
||||
c-1.1,0-2.1,0.9-2.1,2.1s0.9,2.1,2.1,2.1c1.1,0,2.1-0.9,2.1-2.1S39.7,11.2,38.6,11.2z"/>
|
||||
</g>
|
||||
<path class="st0" d="M38.6,19.7c-3.6,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4S42.1,19.7,38.6,19.7z M38.6,9.1
|
||||
c-2.3,0-4.2,1.9-4.2,4.2s1.9,4.2,4.2,4.2c2.3,0,4.2-1.9,4.2-4.2S40.9,9.1,38.6,9.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
94
interface/resources/icons/tablet-icons/doppleganger-a.svg
Normal file
94
interface/resources/icons/tablet-icons/doppleganger-a.svg
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="doppleganger-a.svg"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34"><linearGradient
|
||||
id="linearGradient8353"
|
||||
osb:paint="solid"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8355" /></linearGradient></defs><sodipodi:namedview
|
||||
pagecolor="#ff4900"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1004"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.44"
|
||||
inkscape:cx="-3.2806499"
|
||||
inkscape:cy="20.640561"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g4308" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8375"
|
||||
transform="matrix(1.0667546,0,0,1.0667546,-2.1894733,-1.7818707)"><g
|
||||
id="g4308"
|
||||
transform="translate(1.0333645e-6,0)"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
transform="matrix(1.1059001,0,0,1.1059001,-17.342989,-7.9561147)"><path
|
||||
class="st0"
|
||||
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st0"
|
||||
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g8-3"
|
||||
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||
transform="matrix(-1.1059001,0,0,1.1059001,67.821392,-7.9561147)"><path
|
||||
class="st0"
|
||||
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||
id="path10-6"
|
||||
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st0"
|
||||
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||
id="path12-7"
|
||||
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" /></g></g><rect
|
||||
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.15729524;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.62918094, 1.25836187;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4306"
|
||||
width="0.12393159"
|
||||
height="46.498554"
|
||||
x="25.227457"
|
||||
y="1.8070068"
|
||||
rx="0"
|
||||
ry="0.9407174" /></g></g></svg>
|
After Width: | Height: | Size: 5.8 KiB |
94
interface/resources/icons/tablet-icons/doppleganger-i.svg
Normal file
94
interface/resources/icons/tablet-icons/doppleganger-i.svg
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="doppleganger-i.svg"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34"><linearGradient
|
||||
id="linearGradient8353"
|
||||
osb:paint="solid"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8355" /></linearGradient></defs><sodipodi:namedview
|
||||
pagecolor="#ff4900"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1004"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.44"
|
||||
inkscape:cx="-3.2806499"
|
||||
inkscape:cy="20.640561"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="g4308" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8375"
|
||||
transform="matrix(1.0667546,0,0,1.0667546,-2.1894733,-1.7818707)"><g
|
||||
id="g4308"
|
||||
transform="translate(1.0333645e-6,0)"><g
|
||||
id="g8"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
transform="matrix(1.1059001,0,0,1.1059001,-17.342989,-7.9561147)"><path
|
||||
class="st0"
|
||||
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||
id="path10"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st0"
|
||||
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||
id="path12"
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
id="g8-3"
|
||||
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||
transform="matrix(-1.1059001,0,0,1.1059001,67.821392,-7.9561147)"><path
|
||||
class="st0"
|
||||
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||
id="path10-6"
|
||||
style="fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st0"
|
||||
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||
id="path12-7"
|
||||
style="fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" /></g></g><rect
|
||||
style="opacity:0.5;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.15729524;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.62918094, 1.25836187000000010;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4306"
|
||||
width="0.12393159"
|
||||
height="46.498554"
|
||||
x="25.227457"
|
||||
y="1.8070068"
|
||||
rx="0"
|
||||
ry="0.9407174" /></g></g></svg>
|
After Width: | Height: | Size: 5.9 KiB |
|
@ -211,6 +211,11 @@ Item {
|
|||
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
|
||||
", Pending: " + root.downloadsPending;
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Processing: " + root.processing +
|
||||
", Pending: " + root.processingPending;
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded && root.downloadUrls.length > 0;
|
||||
text: "Download URLs:"
|
||||
|
@ -244,16 +249,16 @@ Item {
|
|||
id: octreeCol
|
||||
spacing: 4; x: 4; y: 4;
|
||||
StatText {
|
||||
text: " Frame timing:"
|
||||
text: "Engine: " + root.engineFrameTime.toFixed(1) + " ms"
|
||||
}
|
||||
StatText {
|
||||
text: " Batch: " + root.batchFrameTime.toFixed(1) + " ms"
|
||||
text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms"
|
||||
}
|
||||
StatText {
|
||||
text: " GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
|
||||
text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
|
||||
}
|
||||
StatText {
|
||||
text: " Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms"
|
||||
text: "Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms"
|
||||
}
|
||||
StatText {
|
||||
text: "Triangles: " + root.triangles +
|
||||
|
|
|
@ -23,6 +23,8 @@ Item {
|
|||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
property bool isDesktop: false
|
||||
property bool removingPage: false
|
||||
property bool loadingPage: false
|
||||
|
||||
|
||||
property int currentPage: -1 // used as a model for repeater
|
||||
|
@ -94,10 +96,14 @@ Item {
|
|||
}
|
||||
|
||||
function goBack() {
|
||||
if (webview.canGoBack) {
|
||||
pagesModel.remove(currentPage);
|
||||
if (webview.canGoBack && !isUrlLoaded(webview.url)) {
|
||||
if (currentPage > 0) {
|
||||
removingPage = true;
|
||||
pagesModel.remove(currentPage);
|
||||
}
|
||||
webview.goBack();
|
||||
} else if (currentPage > 0) {
|
||||
removingPage = true;
|
||||
pagesModel.remove(currentPage);
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +127,10 @@ Item {
|
|||
urlAppend(url)
|
||||
}
|
||||
|
||||
function isUrlLoaded(url) {
|
||||
return (pagesModel.get(currentPage).webUrl === url);
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
view.reloadAndBypassCache()
|
||||
view.setActiveFocusOnPress(true);
|
||||
|
@ -128,6 +138,10 @@ Item {
|
|||
}
|
||||
|
||||
function urlAppend(url) {
|
||||
if (removingPage) {
|
||||
removingPage = false;
|
||||
return;
|
||||
}
|
||||
var lurl = decodeURIComponent(url)
|
||||
if (lurl[lurl.length - 1] !== "/") {
|
||||
lurl = lurl + "/"
|
||||
|
@ -140,6 +154,7 @@ Item {
|
|||
|
||||
onCurrentPageChanged: {
|
||||
if (currentPage >= 0 && currentPage < pagesModel.count) {
|
||||
timer.start();
|
||||
webview.url = pagesModel.get(currentPage).webUrl;
|
||||
web.url = webview.url;
|
||||
web.address = webview.url;
|
||||
|
@ -158,7 +173,7 @@ Item {
|
|||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 100
|
||||
interval: 200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: timer.stop();
|
||||
|
@ -230,10 +245,10 @@ Item {
|
|||
keyboardRaised = false;
|
||||
punctuationMode = false;
|
||||
keyboard.resetShiftMode(false);
|
||||
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
urlAppend(loadRequest.url.toString())
|
||||
urlAppend(loadRequest.url.toString());
|
||||
loadingPage = true;
|
||||
var url = loadRequest.url.toString();
|
||||
if (urlHandler.canHandleUrl(url)) {
|
||||
if (urlHandler.handleUrl(url)) {
|
||||
|
@ -241,12 +256,27 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WebEngineView.LoadFailedStatus == loadRequest.status) {
|
||||
console.log(" Tablet WebEngineView failed to laod url: " + loadRequest.url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
request.openIn(webview);
|
||||
}
|
||||
}
|
||||
|
||||
HiFiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
web.isDesktop = (typeof desktop !== "undefined");
|
||||
|
|
|
@ -466,6 +466,11 @@ FocusScope {
|
|||
return fileDialogBuilder.createObject(desktop, properties);
|
||||
}
|
||||
|
||||
Component { id: assetDialogBuilder; AssetDialog { } }
|
||||
function assetDialog(properties) {
|
||||
return assetDialogBuilder.createObject(desktop, properties);
|
||||
}
|
||||
|
||||
function unfocusWindows() {
|
||||
// First find the active focus item, and unfocus it, all the way
|
||||
// up the parent chain to the window
|
||||
|
|
58
interface/resources/qml/dialogs/AssetDialog.qml
Normal file
58
interface/resources/qml/dialogs/AssetDialog.qml
Normal file
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// AssetDialog.qml
|
||||
//
|
||||
// Created by David Rowe on 18 Apr 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
import "../windows"
|
||||
|
||||
import "assetDialog"
|
||||
|
||||
ModalWindow {
|
||||
id: root
|
||||
resizable: true
|
||||
implicitWidth: 480
|
||||
implicitHeight: 360
|
||||
|
||||
minSize: Qt.vector2d(360, 240)
|
||||
draggable: true
|
||||
|
||||
Settings {
|
||||
category: "AssetDialog"
|
||||
property alias width: root.width
|
||||
property alias height: root.height
|
||||
property alias x: root.x
|
||||
property alias y: root.y
|
||||
}
|
||||
|
||||
// Set from OffscreenUi::assetDialog().
|
||||
property alias caption: root.title
|
||||
property alias dir: assetDialogContent.dir
|
||||
property alias filter: assetDialogContent.filter
|
||||
property alias options: assetDialogContent.options
|
||||
|
||||
// Dialog results.
|
||||
signal selectedAsset(var asset);
|
||||
signal canceled();
|
||||
|
||||
property int titleWidth: 0 // For ModalFrame.
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
AssetDialogContent {
|
||||
id: assetDialogContent
|
||||
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
anchors.margins: 0
|
||||
}
|
||||
}
|
53
interface/resources/qml/dialogs/TabletAssetDialog.qml
Normal file
53
interface/resources/qml/dialogs/TabletAssetDialog.qml
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// TabletAssetDialog.qml
|
||||
//
|
||||
// Created by David Rowe on 18 Apr 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../windows"
|
||||
|
||||
import "assetDialog"
|
||||
|
||||
TabletModalWindow {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
// Set from OffscreenUi::assetDialog().
|
||||
property alias caption: root.title
|
||||
property alias dir: assetDialogContent.dir
|
||||
property alias filter: assetDialogContent.filter
|
||||
property alias options: assetDialogContent.options
|
||||
|
||||
// Dialog results.
|
||||
signal selectedAsset(var asset);
|
||||
signal canceled();
|
||||
|
||||
property int titleWidth: 0 // For TabletModalFrame.
|
||||
|
||||
TabletModalFrame {
|
||||
id: frame
|
||||
anchors.fill: parent
|
||||
|
||||
AssetDialogContent {
|
||||
id: assetDialogContent
|
||||
singleClickNavigate: true
|
||||
width: parent.width - 12
|
||||
height: parent.height - frame.frameMarginTop - 12
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: parent.height - height - 6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,536 @@
|
|||
//
|
||||
// AssetDialogContent.qml
|
||||
//
|
||||
// Created by David Rowe on 19 Apr 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../controls-uit"
|
||||
import "../../styles-uit"
|
||||
|
||||
import "../fileDialog"
|
||||
|
||||
Item {
|
||||
// Set from OffscreenUi::assetDialog()
|
||||
property alias dir: assetTableModel.folder
|
||||
property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx".
|
||||
property int options // Not used.
|
||||
|
||||
property bool selectDirectory: false
|
||||
|
||||
// Not implemented.
|
||||
//property bool saveDialog: false;
|
||||
//property bool multiSelect: false;
|
||||
|
||||
property bool singleClickNavigate: false
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Component.onCompleted: {
|
||||
homeButton.destination = dir;
|
||||
|
||||
if (selectDirectory) {
|
||||
d.currentSelectionIsFolder = true;
|
||||
d.currentSelectionPath = assetTableModel.folder;
|
||||
}
|
||||
|
||||
assetTableView.forceActiveFocus();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: assetDialogItem
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
MouseArea {
|
||||
// Clear selection when click on internal unused area.
|
||||
anchors.fill: parent
|
||||
drag.target: root
|
||||
onClicked: {
|
||||
d.clearSelection();
|
||||
frame.forceActiveFocus();
|
||||
assetTableView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: navControls
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: parent.left
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
GlyphButton {
|
||||
id: upButton
|
||||
glyph: hifi.glyphs.levelUp
|
||||
width: height
|
||||
size: 30
|
||||
enabled: assetTableModel.parentFolder !== ""
|
||||
onClicked: d.navigateUp();
|
||||
}
|
||||
|
||||
GlyphButton {
|
||||
id: homeButton
|
||||
property string destination: ""
|
||||
glyph: hifi.glyphs.home
|
||||
size: 28
|
||||
width: height
|
||||
enabled: destination !== ""
|
||||
//onClicked: d.navigateHome();
|
||||
onClicked: assetTableModel.folder = destination;
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: pathSelector
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: navControls.right
|
||||
leftMargin: hifi.dimensions.contentSpacing.x
|
||||
right: parent.right
|
||||
}
|
||||
z: 10
|
||||
|
||||
property string lastValidFolder: assetTableModel.folder
|
||||
|
||||
function calculatePathChoices(folder) {
|
||||
var folders = folder.split("/"),
|
||||
choices = [],
|
||||
i, length;
|
||||
|
||||
if (folders[folders.length - 1] === "") {
|
||||
folders.pop();
|
||||
}
|
||||
|
||||
choices.push(folders[0]);
|
||||
|
||||
for (i = 1, length = folders.length; i < length; i++) {
|
||||
choices.push(choices[i - 1] + "/" + folders[i]);
|
||||
}
|
||||
|
||||
if (folders[0] === "") {
|
||||
choices[0] = "/";
|
||||
}
|
||||
|
||||
choices.reverse();
|
||||
|
||||
if (choices.length > 0) {
|
||||
pathSelector.model = choices;
|
||||
}
|
||||
}
|
||||
|
||||
onLastValidFolderChanged: {
|
||||
var folder = lastValidFolder;
|
||||
calculatePathChoices(folder);
|
||||
}
|
||||
|
||||
onCurrentTextChanged: {
|
||||
var folder = currentText;
|
||||
|
||||
if (folder !== "/") {
|
||||
folder += "/";
|
||||
}
|
||||
|
||||
if (folder !== assetTableModel.folder) {
|
||||
if (root.selectDirectory) {
|
||||
currentSelection.text = currentText;
|
||||
d.currentSelectionPath = currentText;
|
||||
}
|
||||
assetTableModel.folder = folder;
|
||||
assetTableView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property string currentSelectionPath
|
||||
property bool currentSelectionIsFolder
|
||||
property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); }
|
||||
|
||||
function update() {
|
||||
var row = assetTableView.currentRow;
|
||||
|
||||
if (row === -1) {
|
||||
if (!root.selectDirectory) {
|
||||
currentSelection.text = "";
|
||||
currentSelectionIsFolder = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var rowInfo = assetTableModel.get(row);
|
||||
currentSelectionPath = rowInfo.filePath;
|
||||
currentSelectionIsFolder = rowInfo.fileIsDir;
|
||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
||||
currentSelection.text = currentSelectionPath;
|
||||
} else {
|
||||
currentSelection.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
function navigateUp() {
|
||||
if (assetTableModel.parentFolder !== "") {
|
||||
assetTableModel.folder = assetTableModel.parentFolder;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function navigateHome() {
|
||||
assetTableModel.folder = homeButton.destination;
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
assetTableView.selection.clear();
|
||||
assetTableView.currentRow = -1;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: assetTableModel
|
||||
|
||||
property string folder
|
||||
property string parentFolder: ""
|
||||
readonly property string rootFolder: "/"
|
||||
|
||||
onFolderChanged: {
|
||||
parentFolder = calculateParentFolder();
|
||||
update();
|
||||
}
|
||||
|
||||
function calculateParentFolder() {
|
||||
if (folder !== "/") {
|
||||
return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function isFolder(row) {
|
||||
if (row === -1) {
|
||||
return false;
|
||||
}
|
||||
return get(row).fileIsDir;
|
||||
}
|
||||
|
||||
function onGetAllMappings(error, map) {
|
||||
var mappings,
|
||||
fileTypeFilter,
|
||||
index,
|
||||
path,
|
||||
fileName,
|
||||
fileType,
|
||||
fileIsDir,
|
||||
isValid,
|
||||
subDirectory,
|
||||
subDirectories = [],
|
||||
fileNameSort,
|
||||
rows = 0,
|
||||
lower,
|
||||
middle,
|
||||
upper,
|
||||
i,
|
||||
length;
|
||||
|
||||
clear();
|
||||
|
||||
if (error === "") {
|
||||
mappings = Object.keys(map);
|
||||
fileTypeFilter = filter.replace("*", "").toLowerCase();
|
||||
|
||||
for (i = 0, length = mappings.length; i < length; i++) {
|
||||
index = mappings[i].lastIndexOf("/");
|
||||
|
||||
path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1);
|
||||
fileName = mappings[i].slice(path.length);
|
||||
fileType = fileName.slice(fileName.lastIndexOf("."));
|
||||
fileIsDir = false;
|
||||
isValid = false;
|
||||
|
||||
if (fileType.toLowerCase() === fileTypeFilter) {
|
||||
if (path === folder) {
|
||||
isValid = !selectDirectory;
|
||||
} else if (path.length > folder.length) {
|
||||
subDirectory = path.slice(folder.length);
|
||||
index = subDirectory.indexOf("/");
|
||||
if (index === subDirectory.lastIndexOf("/")) {
|
||||
fileName = subDirectory.slice(0, index);
|
||||
if (subDirectories.indexOf(fileName) === -1) {
|
||||
fileIsDir = true;
|
||||
isValid = true;
|
||||
subDirectories.push(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase();
|
||||
|
||||
lower = 0;
|
||||
upper = rows;
|
||||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (fileNameSort < get(middle)["fileNameSort"]) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
lessThan = false;
|
||||
lower = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
insert(lower, {
|
||||
fileName: fileName,
|
||||
filePath: path + (fileIsDir ? "" : fileName),
|
||||
fileIsDir: fileIsDir,
|
||||
fileNameSort: fileNameSort
|
||||
});
|
||||
|
||||
rows++;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log("Error getting mappings from Asset Server");
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
d.clearSelection();
|
||||
clear();
|
||||
Assets.getAllMappings(onGetAllMappings);
|
||||
}
|
||||
}
|
||||
|
||||
Table {
|
||||
id: assetTableView
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
anchors {
|
||||
top: navControls.bottom
|
||||
topMargin: hifi.dimensions.contentSpacing.y
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: currentSelection.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
|
||||
}
|
||||
|
||||
model: assetTableModel
|
||||
|
||||
focus: true
|
||||
|
||||
onClicked: {
|
||||
if (singleClickNavigate) {
|
||||
navigateToRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleClicked: navigateToRow(row);
|
||||
Keys.onReturnPressed: navigateToCurrentRow();
|
||||
Keys.onEnterPressed: navigateToCurrentRow();
|
||||
|
||||
itemDelegate: Item {
|
||||
clip: true
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
FontLoader { id: firaSansRegular; source: "../../../fonts/FiraSans-Regular.ttf"; }
|
||||
|
||||
FiraSansSemiBold {
|
||||
text: styleData.value
|
||||
elide: styleData.elideMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
size: hifi.fontSizes.tableText
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir)
|
||||
? firaSansSemiBold.name : firaSansRegular.name
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
id: fileNameColumn
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
width: assetTableView.width
|
||||
movable: false
|
||||
resizable: false
|
||||
}
|
||||
|
||||
function navigateToRow(row) {
|
||||
currentRow = row;
|
||||
navigateToCurrentRow();
|
||||
}
|
||||
|
||||
function navigateToCurrentRow() {
|
||||
if (model.isFolder(currentRow)) {
|
||||
model.folder = model.get(currentRow).filePath;
|
||||
} else {
|
||||
okAction.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: prefixClearTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: assetTableView.prefix = "";
|
||||
}
|
||||
|
||||
property string prefix: ""
|
||||
|
||||
function addToPrefix(event) {
|
||||
if (!event.text || event.text === "") {
|
||||
return false;
|
||||
}
|
||||
var newPrefix = prefix + event.text.toLowerCase();
|
||||
var matchedIndex = -1;
|
||||
for (var i = 0; i < model.count; ++i) {
|
||||
var name = model.get(i).fileName.toLowerCase();
|
||||
if (0 === name.indexOf(newPrefix)) {
|
||||
matchedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedIndex !== -1) {
|
||||
assetTableView.selection.clear();
|
||||
assetTableView.selection.select(matchedIndex);
|
||||
assetTableView.currentRow = matchedIndex;
|
||||
assetTableView.prefix = newPrefix;
|
||||
}
|
||||
prefixClearTimer.restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Backspace:
|
||||
case Qt.Key_Tab:
|
||||
case Qt.Key_Backtab:
|
||||
event.accepted = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (addToPrefix(event)) {
|
||||
event.accepted = true
|
||||
} else {
|
||||
event.accepted = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: currentSelection
|
||||
label: selectDirectory ? "Directory:" : "File name:"
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: selectionType.visible ? selectionType.left: parent.right
|
||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
||||
bottom: buttonRow.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
readOnly: true
|
||||
activeFocusOnTab: !readOnly
|
||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
||||
onAccepted: okAction.trigger();
|
||||
}
|
||||
|
||||
FileTypeSelection {
|
||||
id: selectionType
|
||||
anchors {
|
||||
top: currentSelection.top
|
||||
left: buttonRow.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: !selectDirectory && filtersCount > 1
|
||||
KeyNavigation.left: assetTableView
|
||||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Action {
|
||||
id: okAction
|
||||
text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open"
|
||||
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
||||
onTriggered: {
|
||||
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
||||
|| root.selectDirectory && assetTableView.currentRow === -1) {
|
||||
selectedAsset(d.currentSelectionPath);
|
||||
root.destroy();
|
||||
} else {
|
||||
assetTableView.navigateToCurrentRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: cancelAction
|
||||
text: "Cancel"
|
||||
onTriggered: {
|
||||
canceled();
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors {
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
|
||||
Button {
|
||||
id: openButton
|
||||
color: hifi.buttons.blue
|
||||
action: okAction
|
||||
Keys.onReturnPressed: okAction.trigger()
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: selectionType
|
||||
KeyNavigation.right: cancelButton
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
action: cancelAction
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: openButton
|
||||
KeyNavigation.right: assetTableView.contentItem
|
||||
Keys.onReturnPressed: { canceled(); root.enabled = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Backspace:
|
||||
event.accepted = d.navigateUp();
|
||||
break;
|
||||
|
||||
case Qt.Key_Home:
|
||||
event.accepted = d.navigateHome();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,11 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import "../../controls"
|
||||
import "../toolbars"
|
||||
import HFWebEngineProfile 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../styles-uit"
|
||||
|
||||
StackView {
|
||||
id: editRoot
|
||||
objectName: "stack"
|
||||
initialItem: editBasePage
|
||||
initialItem: Qt.resolvedUrl('EditTabView.qml')
|
||||
|
||||
property var eventBridge;
|
||||
signal sendToScript(var message);
|
||||
|
@ -30,270 +22,10 @@ StackView {
|
|||
editRoot.pop();
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: editBasePage
|
||||
TabView {
|
||||
id: editTabView
|
||||
// anchors.fill: parent
|
||||
height: 60
|
||||
|
||||
Tab {
|
||||
title: "CREATE"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
Rectangle {
|
||||
color: "#404040"
|
||||
|
||||
Text {
|
||||
color: "#ffffff"
|
||||
text: "Choose an Entity Type to Create:"
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 28
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 28
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: createEntitiesFlow
|
||||
spacing: 35
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 70
|
||||
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/94-model-01.svg"
|
||||
text: "MODEL"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/21-cube-01.svg"
|
||||
text: "CUBE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/22-sphere-01.svg"
|
||||
text: "SPHERE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/24-light-01.svg"
|
||||
text: "LIGHT"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/20-text-01.svg"
|
||||
text: "TEXT"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/25-web-1-01.svg"
|
||||
text: "WEB"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/23-zone-01.svg"
|
||||
text: "ZONE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/90-particles-01.svg"
|
||||
text: "PARTICLE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: assetServerButton
|
||||
text: "Open This Domain's Asset Server"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: createEntitiesFlow.bottom
|
||||
anchors.topMargin: 35
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Import Entities (.json)"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: assetServerButton.bottom
|
||||
anchors.topMargin: 20
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "LIST"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: entityListToolWebView
|
||||
url: "../../../../../scripts/system/html/entityList.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "PROPERTIES"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: entityPropertiesWebView
|
||||
url: "../../../../../scripts/system/html/entityProperties.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "GRID"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: gridControlsWebView
|
||||
url: "../../../../../scripts/system/html/gridControls.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "P"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: particleExplorerWebView
|
||||
url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
style: TabViewStyle {
|
||||
frameOverlap: 1
|
||||
tab: Rectangle {
|
||||
color: styleData.selected ? "#404040" :"black"
|
||||
implicitWidth: text.width + 42
|
||||
implicitHeight: 40
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: styleData.title
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: styleData.selected ? "white" : "white"
|
||||
property string glyphtext: ""
|
||||
HiFiGlyphs {
|
||||
anchors.centerIn: parent
|
||||
size: 30
|
||||
color: "#ffffff"
|
||||
text: text.glyphtext
|
||||
}
|
||||
Component.onCompleted: if (styleData.title == "P") {
|
||||
text.text = " ";
|
||||
text.glyphtext = "\ue004";
|
||||
}
|
||||
}
|
||||
}
|
||||
tabBar: Rectangle {
|
||||
color: "black"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
// Passes script messages to the item on the top of the stack
|
||||
function fromScript(message) {
|
||||
var currentItem = editRoot.currentItem;
|
||||
if (currentItem && currentItem.fromScript)
|
||||
currentItem.fromScript(message);
|
||||
}
|
||||
}
|
||||
|
|
318
interface/resources/qml/hifi/tablet/EditTabView.qml
Normal file
318
interface/resources/qml/hifi/tablet/EditTabView.qml
Normal file
|
@ -0,0 +1,318 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import "../../controls"
|
||||
import "../toolbars"
|
||||
import HFWebEngineProfile 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../styles-uit"
|
||||
|
||||
|
||||
TabView {
|
||||
id: editTabView
|
||||
// anchors.fill: parent
|
||||
height: 60
|
||||
|
||||
Tab {
|
||||
title: "CREATE"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
Rectangle {
|
||||
color: "#404040"
|
||||
|
||||
Text {
|
||||
color: "#ffffff"
|
||||
text: "Choose an Entity Type to Create:"
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 28
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 28
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: createEntitiesFlow
|
||||
spacing: 35
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 70
|
||||
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/94-model-01.svg"
|
||||
text: "MODEL"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/21-cube-01.svg"
|
||||
text: "CUBE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/22-sphere-01.svg"
|
||||
text: "SPHERE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/24-light-01.svg"
|
||||
text: "LIGHT"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/20-text-01.svg"
|
||||
text: "TEXT"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/25-web-1-01.svg"
|
||||
text: "WEB"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/23-zone-01.svg"
|
||||
text: "ZONE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/90-particles-01.svg"
|
||||
text: "PARTICLE"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
|
||||
});
|
||||
editTabView.currentIndex = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: assetServerButton
|
||||
text: "Open This Domain's Asset Server"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: createEntitiesFlow.bottom
|
||||
anchors.topMargin: 35
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Import Entities (.json)"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 55
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.top: assetServerButton.bottom
|
||||
anchors.topMargin: 20
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "LIST"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: entityListToolWebView
|
||||
url: "../../../../../scripts/system/html/entityList.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "PROPERTIES"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: entityPropertiesWebView
|
||||
url: "../../../../../scripts/system/html/entityProperties.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "GRID"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: gridControlsWebView
|
||||
url: "../../../../../scripts/system/html/gridControls.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "P"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: particleExplorerWebView
|
||||
url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
|
||||
eventBridge: editRoot.eventBridge
|
||||
anchors.fill: parent
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
style: TabViewStyle {
|
||||
frameOverlap: 1
|
||||
tab: Rectangle {
|
||||
color: styleData.selected ? "#404040" :"black"
|
||||
implicitWidth: text.width + 42
|
||||
implicitHeight: 40
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: styleData.title
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: styleData.selected ? "white" : "white"
|
||||
property string glyphtext: ""
|
||||
HiFiGlyphs {
|
||||
anchors.centerIn: parent
|
||||
size: 30
|
||||
color: "#ffffff"
|
||||
text: text.glyphtext
|
||||
}
|
||||
Component.onCompleted: if (styleData.title == "P") {
|
||||
text.text = " ";
|
||||
text.glyphtext = "\ue004";
|
||||
}
|
||||
}
|
||||
}
|
||||
tabBar: Rectangle {
|
||||
color: "black"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
}
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'selectTab':
|
||||
selectTab(message.params.id);
|
||||
break;
|
||||
default:
|
||||
console.warn('Unrecognized message:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
// Changes the current tab based on tab index or title as input
|
||||
function selectTab(id) {
|
||||
if (typeof id === 'number') {
|
||||
if (id >= 0 && id <= 4) {
|
||||
editTabView.currentIndex = id;
|
||||
} else {
|
||||
console.warn('Attempt to switch to invalid tab:', id);
|
||||
}
|
||||
} else if (typeof id === 'string'){
|
||||
switch (id.toLowerCase()) {
|
||||
case 'create':
|
||||
editTabView.currentIndex = 0;
|
||||
break;
|
||||
case 'list':
|
||||
editTabView.currentIndex = 1;
|
||||
break;
|
||||
case 'properties':
|
||||
editTabView.currentIndex = 2;
|
||||
break;
|
||||
case 'grid':
|
||||
editTabView.currentIndex = 3;
|
||||
break;
|
||||
case 'particle':
|
||||
editTabView.currentIndex = 4;
|
||||
break;
|
||||
default:
|
||||
console.warn('Attempt to switch to invalid tab:', id);
|
||||
}
|
||||
} else {
|
||||
console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id));
|
||||
}
|
||||
}
|
||||
}
|
170
interface/resources/qml/hifi/tablet/InputRecorder.qml
Normal file
170
interface/resources/qml/hifi/tablet/InputRecorder.qml
Normal file
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// Created by Dante Ruiz 2017/04/17
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import Hifi 1.0
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../windows"
|
||||
import "../../dialogs"
|
||||
|
||||
Rectangle {
|
||||
id: inputRecorder
|
||||
property var eventBridge;
|
||||
HifiConstants { id: hifi }
|
||||
signal sendToScript(var message);
|
||||
color: hifi.colors.baseGray;
|
||||
property string path: ""
|
||||
property string dir: ""
|
||||
property var dialog: null;
|
||||
property bool recording: false;
|
||||
|
||||
Component { id: fileDialog; TabletFileDialog { } }
|
||||
Row {
|
||||
id: topButtons
|
||||
width: parent.width
|
||||
height: 40
|
||||
spacing: 40
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: 10
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: start
|
||||
text: "Start Recoring"
|
||||
color: hifi.buttons.black
|
||||
enabled: true
|
||||
onClicked: {
|
||||
if (inputRecorder.recording) {
|
||||
sendToScript({method: "Stop"});
|
||||
inputRecorder.recording = false;
|
||||
start.text = "Start Recording";
|
||||
selectedFile.text = "Current recording is not saved";
|
||||
} else {
|
||||
sendToScript({method: "Start"});
|
||||
inputRecorder.recording = true;
|
||||
start.text = "Stop Recording";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: save
|
||||
text: "Save Recording"
|
||||
color: hifi.buttons.black
|
||||
enabled: true
|
||||
onClicked: {
|
||||
sendToScript({method: "Save"});
|
||||
selectedFile.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: playBack
|
||||
anchors.right: browse.left
|
||||
anchors.top: selectedFile.bottom
|
||||
anchors.topMargin: 10
|
||||
|
||||
text: "Play Recording"
|
||||
color: hifi.buttons.black
|
||||
enabled: true
|
||||
onClicked: {
|
||||
sendToScript({method: "playback"});
|
||||
HMD.closeTablet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
HifiControls.TextField {
|
||||
id: selectedFile
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: topButtons.top
|
||||
anchors.topMargin: 40
|
||||
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
readOnly: true
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
HifiControls.Button {
|
||||
id: browse
|
||||
anchors.right: parent.right
|
||||
anchors.top: selectedFile.bottom
|
||||
anchors.topMargin: 10
|
||||
|
||||
text: "Load..."
|
||||
color: hifi.buttons.black
|
||||
enabled: true
|
||||
onClicked: {
|
||||
dialog = fileDialog.createObject(inputRecorder);
|
||||
dialog.caption = "InputRecorder";
|
||||
console.log(dialog.dir);
|
||||
dialog.dir = "file:///" + inputRecorder.dir;
|
||||
dialog.selectedFile.connect(getFileSelected);
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: notes
|
||||
anchors.centerIn: parent;
|
||||
spacing: 20
|
||||
|
||||
Text {
|
||||
text: "All files are saved under the folder 'hifi-input-recording' in AppData directory";
|
||||
color: "white"
|
||||
font.pointSize: 10
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "To cancel a recording playback press Alt-B"
|
||||
color: "white"
|
||||
font.pointSize: 10
|
||||
}
|
||||
}
|
||||
|
||||
function getFileSelected(file) {
|
||||
selectedFile.text = file;
|
||||
inputRecorder.path = file;
|
||||
sendToScript({method: "Load", params: {file: path }});
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case "update":
|
||||
updateButtonStatus(message.params);
|
||||
break;
|
||||
case "path":
|
||||
console.log(message.params);
|
||||
inputRecorder.dir = message.params;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function updateButtonStatus(status) {
|
||||
inputRecorder.recording = status;
|
||||
|
||||
if (inputRecorder.recording) {
|
||||
start.text = "Stop Recording";
|
||||
} else {
|
||||
start.text = "Start Recording";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ StackView {
|
|||
var card = tabletWebView.createObject();
|
||||
card.url = addressBarDialog.metaverseServerUrl + targetString;
|
||||
card.parentStackItem = root;
|
||||
card.eventBridge = root.eventBridge;
|
||||
root.push(card);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ StackView {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
profileRoot.push(Qt.reslovedUrl(path));
|
||||
profileRoot.push(Qt.resolvedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
|
|
@ -23,7 +23,7 @@ StackView {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
profileRoot.push(Qt.reslovedUrl(path));
|
||||
profileRoot.push(Qt.resolvedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
|
|
@ -23,7 +23,7 @@ StackView {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
profileRoot.push(Qt.reslovedUrl(path));
|
||||
profileRoot.push(Qt.resolvedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
|
|
@ -23,7 +23,7 @@ StackView {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
profileRoot.push(Qt.reslovedUrl(path));
|
||||
profileRoot.push(Qt.resolvedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
|
|
@ -23,7 +23,7 @@ StackView {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
profileRoot.push(Qt.reslovedUrl(path));
|
||||
profileRoot.push(Qt.resolvedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
|
|
@ -44,6 +44,12 @@ Item {
|
|||
return openModal;
|
||||
}
|
||||
|
||||
Component { id: assetDialogBuilder; TabletAssetDialog { } }
|
||||
function assetDialog(properties) {
|
||||
openModal = assetDialogBuilder.createObject(tabletRoot, properties);
|
||||
return openModal;
|
||||
}
|
||||
|
||||
function setMenuProperties(rootMenu, subMenu) {
|
||||
tabletRoot.rootMenu = rootMenu;
|
||||
tabletRoot.subMenu = subMenu;
|
||||
|
|
Binary file not shown.
|
@ -78,6 +78,7 @@
|
|||
#include <InfoView.h>
|
||||
#include <input-plugins/InputPlugin.h>
|
||||
#include <controllers/UserInputMapper.h>
|
||||
#include <controllers/InputRecorder.h>
|
||||
#include <controllers/ScriptingInterface.h>
|
||||
#include <controllers/StateController.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
|
@ -140,7 +141,7 @@
|
|||
#include "devices/Leapmotion.h"
|
||||
#include "DiscoverabilityManager.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "InterfaceActionFactory.h"
|
||||
#include "InterfaceDynamicFactory.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "LODManager.h"
|
||||
#include "ModelPackager.h"
|
||||
|
@ -462,7 +463,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
||||
DependencyManager::registerInheritance<EntityActionFactoryInterface, InterfaceActionFactory>();
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, InterfaceParentFinder>();
|
||||
|
||||
// Set dependencies
|
||||
|
@ -516,7 +517,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<OffscreenUi>();
|
||||
DependencyManager::set<AutoUpdater>();
|
||||
DependencyManager::set<PathUtils>();
|
||||
DependencyManager::set<InterfaceActionFactory>();
|
||||
DependencyManager::set<InterfaceDynamicFactory>();
|
||||
DependencyManager::set<AudioInjectorManager>();
|
||||
DependencyManager::set<MessagesClient>();
|
||||
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
||||
|
@ -594,7 +595,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_aboutToQuit(false),
|
||||
_notifiedPacketVersionMismatchThisDomain(false),
|
||||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||
_lastFaceTrackerUpdate(0)
|
||||
_lastFaceTrackerUpdate(0),
|
||||
_snapshotSound(nullptr)
|
||||
{
|
||||
auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
|
||||
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
|
||||
|
@ -1436,6 +1438,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
entityPacketSender->setMyAvatar(myAvatar.get());
|
||||
|
||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool)));
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
|
@ -1443,8 +1446,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
QString skyboxUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.jpg" };
|
||||
QString skyboxAmbientUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-ambient.jpg" };
|
||||
|
||||
_defaultSkyboxTexture = textureCache->getImageTexture(skyboxUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", false } });
|
||||
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", true } });
|
||||
_defaultSkyboxTexture = textureCache->getImageTexture(skyboxUrl, image::TextureUsage::CUBE_TEXTURE, { { "generateIrradiance", false } });
|
||||
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, image::TextureUsage::CUBE_TEXTURE, { { "generateIrradiance", true } });
|
||||
|
||||
_defaultSkybox->setCubemap(_defaultSkyboxTexture);
|
||||
|
||||
|
@ -1453,6 +1456,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
return entityServerNode && !isPhysicsEnabled();
|
||||
});
|
||||
|
||||
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
|
||||
_snapshotSound = DependencyManager::get<SoundCache>()->getSound(QUrl::fromLocalFile(inf.absoluteFilePath()));
|
||||
|
||||
QVariant testProperty = property(hifi::properties::TEST);
|
||||
qDebug() << testProperty;
|
||||
if (testProperty.isValid()) {
|
||||
|
@ -1460,46 +1466,53 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||
scriptEngines->loadScript(testScript, false);
|
||||
} else {
|
||||
// Get sandbox content set version, if available
|
||||
enum HandControllerType {
|
||||
Vive,
|
||||
Oculus
|
||||
};
|
||||
static const std::map<HandControllerType, int> MIN_CONTENT_VERSION = {
|
||||
{ Vive, 1 },
|
||||
{ Oculus, 27 }
|
||||
};
|
||||
|
||||
// Get sandbox content set version
|
||||
auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||
auto contentVersion = 0;
|
||||
int contentVersion = 0;
|
||||
QFile contentVersionFile(contentVersionPath);
|
||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString line = contentVersionFile.readAll();
|
||||
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
|
||||
contentVersion = line.toInt();
|
||||
contentVersion = line.toInt(); // returns 0 if conversion fails
|
||||
}
|
||||
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
|
||||
|
||||
static const int MIN_VIVE_CONTENT_VERSION = 1;
|
||||
static const int MIN_OCULUS_TOUCH_CONTENT_VERSION = 27;
|
||||
|
||||
bool hasSufficientTutorialContent = false;
|
||||
// Get controller availability
|
||||
bool hasHandControllers = false;
|
||||
|
||||
// Only specific hand controllers are currently supported, so only send users to the tutorial
|
||||
// if they have one of those hand controllers.
|
||||
HandControllerType handControllerType = Vive;
|
||||
if (PluginUtils::isViveControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
hasSufficientTutorialContent = contentVersion >= MIN_VIVE_CONTENT_VERSION;
|
||||
handControllerType = Vive;
|
||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
hasSufficientTutorialContent = contentVersion >= MIN_OCULUS_TOUCH_CONTENT_VERSION;
|
||||
handControllerType = Oculus;
|
||||
}
|
||||
|
||||
// Check tutorial content versioning
|
||||
bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType);
|
||||
|
||||
// Check HMD use (may be technically available without being in use)
|
||||
bool hasHMD = PluginUtils::isHMDAvailable();
|
||||
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
|
||||
|
||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
|
||||
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable() && hasHandControllers;
|
||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
||||
bool isTutorialComplete = tutorialComplete.get();
|
||||
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
|
||||
|
||||
bool shouldGoToTutorial = hasHMDAndHandControllers && hasSufficientTutorialContent && !tutorialComplete.get();
|
||||
|
||||
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
|
||||
qCDebug(interfaceapp) << "Has sufficient tutorial content (" << contentVersion << ") : " << hasSufficientTutorialContent;
|
||||
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
|
||||
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
|
||||
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
|
||||
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
|
||||
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
||||
|
||||
// when --url in command line, teleport to location
|
||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||
|
@ -1536,7 +1549,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (hasHMDAndHandControllers) {
|
||||
if (isUsingHMD) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
|
@ -1775,12 +1788,17 @@ void Application::cleanupBeforeQuit() {
|
|||
// stop QML
|
||||
DependencyManager::destroy<OffscreenUi>();
|
||||
|
||||
if (_snapshotSoundInjector != nullptr) {
|
||||
_snapshotSoundInjector->stop();
|
||||
}
|
||||
|
||||
// stop audio after QML, as there are unexplained audio crashes originating in qtwebengine
|
||||
|
||||
// stop the AudioClient, synchronously
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"stop", Qt::BlockingQueuedConnection);
|
||||
|
||||
|
||||
// destroy Audio so it and its threads have a chance to go down safely
|
||||
DependencyManager::destroy<AudioClient>();
|
||||
DependencyManager::destroy<AudioInjectorManager>();
|
||||
|
@ -2084,7 +2102,7 @@ void Application::initializeUi() {
|
|||
|
||||
void Application::paintGL() {
|
||||
// Some plugins process message events, allowing paintGL to be called reentrantly.
|
||||
if (_inPaint || _aboutToQuit) {
|
||||
if (_inPaint || _aboutToQuit || _window->isMinimized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2736,6 +2754,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
if (isMeta) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->load("Browser.qml");
|
||||
} else if (isOption) {
|
||||
controller::InputRecorder* inputRecorder = controller::InputRecorder::getInstance();
|
||||
inputRecorder->stopPlayback();
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -4430,7 +4451,7 @@ void Application::update(float deltaTime) {
|
|||
_entitySimulation->setObjectsToChange(stillNeedChange);
|
||||
});
|
||||
|
||||
_entitySimulation->applyActionChanges();
|
||||
_entitySimulation->applyDynamicChanges();
|
||||
|
||||
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
|
||||
_physicsEngine->removeObjects(motionStates);
|
||||
|
@ -4440,8 +4461,8 @@ void Application::update(float deltaTime) {
|
|||
_physicsEngine->changeObjects(motionStates);
|
||||
|
||||
myAvatar->prepareForPhysicsSimulation();
|
||||
_physicsEngine->forEachAction([&](EntityActionPointer action) {
|
||||
action->prepareForPhysicsSimulation();
|
||||
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
|
||||
dynamic->prepareForPhysicsSimulation();
|
||||
});
|
||||
}
|
||||
{
|
||||
|
@ -5195,11 +5216,7 @@ void Application::resettingDomain() {
|
|||
}
|
||||
|
||||
void Application::nodeAdded(SharedNodePointer node) const {
|
||||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
// new avatar mixer, send off our identity packet right away
|
||||
getMyAvatar()->sendIdentityPacket();
|
||||
getMyAvatar()->resetLastSent();
|
||||
}
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
void Application::nodeActivated(SharedNodePointer node) {
|
||||
|
@ -5235,6 +5252,13 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
if (node->getType() == NodeType::AudioMixer) {
|
||||
DependencyManager::get<AudioClient>()->negotiateAudioFormat();
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
// new avatar mixer, send off our identity packet right away
|
||||
getMyAvatar()->markIdentityDataChanged();
|
||||
getMyAvatar()->sendIdentityPacket();
|
||||
getMyAvatar()->resetLastSent();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::nodeKilled(SharedNodePointer node) {
|
||||
|
@ -6405,15 +6429,24 @@ void Application::loadAddAvatarBookmarkDialog() const {
|
|||
}
|
||||
|
||||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
|
||||
postLambdaEvent([notify, includeAnimated, aspectRatio, this] {
|
||||
QMediaPlayer* player = new QMediaPlayer();
|
||||
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
|
||||
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
|
||||
player->play();
|
||||
|
||||
//keep sound thread out of event loop scope
|
||||
|
||||
AudioInjectorOptions options;
|
||||
options.localOnly = true;
|
||||
options.stereo = true;
|
||||
|
||||
if (_snapshotSoundInjector) {
|
||||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
QByteArray samples = _snapshotSound->getByteArray();
|
||||
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
|
||||
}
|
||||
|
||||
postLambdaEvent([notify, includeAnimated, aspectRatio, this] {
|
||||
// Get a screenshot and save it
|
||||
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
|
||||
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
|
@ -6424,6 +6457,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::shareSnapshot(const QString& path, const QUrl& href) {
|
||||
postLambdaEvent([path, href] {
|
||||
// not much to do here, everything is done in snapshot code...
|
||||
|
@ -6486,6 +6520,14 @@ void Application::activeChanged(Qt::ApplicationState state) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::windowMinimizedChanged(bool minimized) {
|
||||
if (!minimized && !getActiveDisplayPlugin()->isActive()) {
|
||||
getActiveDisplayPlugin()->activate();
|
||||
} else if (minimized && getActiveDisplayPlugin()->isActive()) {
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::postLambdaEvent(std::function<void()> f) {
|
||||
if (this->thread() == QThread::currentThread()) {
|
||||
f();
|
||||
|
@ -6731,11 +6773,6 @@ void Application::updateDisplayMode() {
|
|||
return;
|
||||
}
|
||||
|
||||
UserActivityLogger::getInstance().logAction("changed_display_mode", {
|
||||
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
|
||||
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" }
|
||||
});
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
// Make the switch atomic from the perspective of other threads
|
||||
|
@ -6773,6 +6810,13 @@ void Application::updateDisplayMode() {
|
|||
if (!active) {
|
||||
qFatal("Failed to activate fallback plugin");
|
||||
}
|
||||
|
||||
// We've changed the selection - it should be reflected in the menu
|
||||
QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
|
||||
if (!action) {
|
||||
qFatal("Failed to find activated plugin");
|
||||
}
|
||||
action->setChecked(true);
|
||||
}
|
||||
|
||||
_offscreenContext->makeCurrent();
|
||||
|
@ -6783,13 +6827,16 @@ void Application::updateDisplayMode() {
|
|||
offscreenUi->getDesktop()->setProperty("repositionLocked", wasRepositionLocked);
|
||||
}
|
||||
|
||||
bool isHmd = _displayPlugin->isHmd();
|
||||
qCDebug(interfaceapp) << "Entering into" << (isHmd ? "HMD" : "Desktop") << "Mode";
|
||||
|
||||
// Only log/emit after a successful change
|
||||
UserActivityLogger::getInstance().logAction("changed_display_mode", {
|
||||
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
|
||||
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" },
|
||||
{ "hmd", isHmd }
|
||||
});
|
||||
emit activeDisplayPluginChanged();
|
||||
|
||||
if (_displayPlugin->isHmd()) {
|
||||
qCDebug(interfaceapp) << "Entering into HMD Mode";
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Entering into Desktop Mode";
|
||||
}
|
||||
|
||||
// reset the avatar, to set head and hand palms back to a reasonable default pose.
|
||||
getMyAvatar()->reset(false);
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
#include <model/Skybox.h>
|
||||
#include <ModelScriptingInterface.h>
|
||||
|
||||
#include "Sound.h"
|
||||
|
||||
class OffscreenGLCanvas;
|
||||
class GLCanvas;
|
||||
|
@ -80,6 +81,7 @@ class FaceTracker;
|
|||
class MainWindow;
|
||||
class AssetUpload;
|
||||
class CompositorHelper;
|
||||
class AudioInjector;
|
||||
|
||||
namespace controller {
|
||||
class StateController;
|
||||
|
@ -415,6 +417,7 @@ private slots:
|
|||
void faceTrackerMuteToggled();
|
||||
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
void windowMinimizedChanged(bool minimized);
|
||||
|
||||
void notifyPacketVersionMismatch();
|
||||
|
||||
|
@ -682,6 +685,8 @@ private:
|
|||
QTimer _addAssetToWorldErrorTimer;
|
||||
|
||||
FileScriptingInterface* _fileDownload;
|
||||
AudioInjector* _snapshotSoundInjector { nullptr };
|
||||
SharedSoundPointer _snapshotSound;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
//
|
||||
// InterfaceActionFactory.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
|
||||
#include <avatar/AvatarActionHold.h>
|
||||
#include <ObjectActionOffset.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
#include <ObjectActionTravelOriented.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "InterfaceActionFactory.h"
|
||||
|
||||
|
||||
EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
|
||||
switch (type) {
|
||||
case ACTION_TYPE_NONE:
|
||||
return EntityActionPointer();
|
||||
case ACTION_TYPE_OFFSET:
|
||||
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
|
||||
case ACTION_TYPE_SPRING:
|
||||
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
|
||||
case ACTION_TYPE_HOLD:
|
||||
return std::make_shared<AvatarActionHold>(id, ownerEntity);
|
||||
case ACTION_TYPE_TRAVEL_ORIENTED:
|
||||
return std::make_shared<ObjectActionTravelOriented>(id, ownerEntity);
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type");
|
||||
return EntityActionPointer();
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer InterfaceActionFactory::factory(EntityActionType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
|
||||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
if (action->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
|
||||
QDataStream serializedArgumentStream(data);
|
||||
EntityActionType type;
|
||||
QUuid id;
|
||||
|
||||
serializedArgumentStream >> type;
|
||||
serializedArgumentStream >> id;
|
||||
|
||||
EntityActionPointer action = interfaceActionFactory(type, id, ownerEntity);
|
||||
|
||||
if (action) {
|
||||
action->deserialize(data);
|
||||
if (action->lifetimeIsOver()) {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during action creation.*");
|
||||
qDebug() << "InterfaceActionFactory::factoryBA lifetimeIsOver during action creation --"
|
||||
<< action->getExpires() << "<" << usecTimestampNow();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
88
interface/src/InterfaceDynamicFactory.cpp
Normal file
88
interface/src/InterfaceDynamicFactory.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// InterfaceDynamicFactory.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
|
||||
#include <avatar/AvatarActionHold.h>
|
||||
#include <avatar/AvatarActionFarGrab.h>
|
||||
#include <ObjectActionOffset.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
#include <ObjectActionTravelOriented.h>
|
||||
#include <ObjectConstraintHinge.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "InterfaceDynamicFactory.h"
|
||||
|
||||
|
||||
EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) {
|
||||
switch (type) {
|
||||
case DYNAMIC_TYPE_NONE:
|
||||
return EntityDynamicPointer();
|
||||
case DYNAMIC_TYPE_OFFSET:
|
||||
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_SPRING:
|
||||
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_HOLD:
|
||||
return std::make_shared<AvatarActionHold>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_TRAVEL_ORIENTED:
|
||||
return std::make_shared<ObjectActionTravelOriented>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_HINGE:
|
||||
return std::make_shared<ObjectConstraintHinge>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_FAR_GRAB:
|
||||
return std::make_shared<AvatarActionFarGrab>(id, ownerEntity);
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type");
|
||||
return EntityDynamicPointer();
|
||||
}
|
||||
|
||||
|
||||
EntityDynamicPointer InterfaceDynamicFactory::factory(EntityDynamicType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) {
|
||||
EntityDynamicPointer dynamic = interfaceDynamicFactory(type, id, ownerEntity);
|
||||
if (dynamic) {
|
||||
bool ok = dynamic->updateArguments(arguments);
|
||||
if (ok) {
|
||||
if (dynamic->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
return dynamic;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
EntityDynamicPointer InterfaceDynamicFactory::factoryBA(EntityItemPointer ownerEntity, QByteArray data) {
|
||||
QDataStream serializedArgumentStream(data);
|
||||
EntityDynamicType type;
|
||||
QUuid id;
|
||||
|
||||
serializedArgumentStream >> type;
|
||||
serializedArgumentStream >> id;
|
||||
|
||||
EntityDynamicPointer dynamic = interfaceDynamicFactory(type, id, ownerEntity);
|
||||
|
||||
if (dynamic) {
|
||||
dynamic->deserialize(data);
|
||||
if (dynamic->lifetimeIsOver()) {
|
||||
static QString repeatedMessage =
|
||||
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during dynamic creation.*");
|
||||
qDebug() << "InterfaceDynamicFactory::factoryBA lifetimeIsOver during dynamic creation --"
|
||||
<< dynamic->getExpires() << "<" << usecTimestampNow();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return dynamic;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// InterfaceActionFactory.cpp
|
||||
// InterfaceDynamicFactory.cpp
|
||||
// interface/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-10
|
||||
|
@ -9,21 +9,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_InterfaceActionFactory_h
|
||||
#define hifi_InterfaceActionFactory_h
|
||||
#ifndef hifi_InterfaceDynamicFactory_h
|
||||
#define hifi_InterfaceDynamicFactory_h
|
||||
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "EntityDynamicFactoryInterface.h"
|
||||
|
||||
class InterfaceActionFactory : public EntityActionFactoryInterface {
|
||||
class InterfaceDynamicFactory : public EntityDynamicFactoryInterface {
|
||||
public:
|
||||
InterfaceActionFactory() : EntityActionFactoryInterface() { }
|
||||
virtual ~InterfaceActionFactory() { }
|
||||
virtual EntityActionPointer factory(EntityActionType type,
|
||||
InterfaceDynamicFactory() : EntityDynamicFactoryInterface() { }
|
||||
virtual ~InterfaceDynamicFactory() { }
|
||||
virtual EntityDynamicPointer factory(EntityDynamicType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) override;
|
||||
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity,
|
||||
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity,
|
||||
QByteArray data) override;
|
||||
};
|
||||
|
||||
#endif // hifi_InterfaceActionFactory_h
|
||||
#endif // hifi_InterfaceDynamicFactory_h
|
|
@ -115,8 +115,6 @@ Avatar::Avatar(QThread* thread, RigPointer rig) :
|
|||
}
|
||||
|
||||
Avatar::~Avatar() {
|
||||
assert(isDead()); // mark dead before calling the dtor
|
||||
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
|
@ -510,12 +508,13 @@ static TextRenderer3D* textRenderer(TextRendererType type) {
|
|||
void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||
auto avatarPayload = new render::Payload<AvatarData>(self);
|
||||
auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload);
|
||||
_renderItemID = scene->allocateID();
|
||||
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
||||
_skeletonModel->addToScene(scene, transaction);
|
||||
if (_skeletonModel->addToScene(scene, transaction)) {
|
||||
_renderItemID = scene->allocateID();
|
||||
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
||||
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->addToScene(scene, transaction);
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->addToScene(scene, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -929,6 +928,17 @@ QVector<glm::quat> Avatar::getJointRotations() const {
|
|||
return jointRotations;
|
||||
}
|
||||
|
||||
QVector<glm::vec3> Avatar::getJointTranslations() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointTranslations();
|
||||
}
|
||||
QVector<glm::vec3> jointTranslations(_skeletonModel->getJointStateCount());
|
||||
for (int i = 0; i < _skeletonModel->getJointStateCount(); ++i) {
|
||||
_skeletonModel->getJointTranslation(i, jointTranslations[i]);
|
||||
}
|
||||
return jointTranslations;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getJointRotation(int index) const {
|
||||
glm::quat rotation;
|
||||
_skeletonModel->getJointRotation(index, rotation);
|
||||
|
@ -1112,11 +1122,20 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
void Avatar::setModelURLFinished(bool success) {
|
||||
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
||||
qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL;
|
||||
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
||||
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
||||
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
||||
const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts
|
||||
if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
|
||||
_skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) {
|
||||
qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL
|
||||
<< "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts.";
|
||||
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
||||
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
||||
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "Avatar model: " << _skeletonModelURL
|
||||
<< "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts()
|
||||
<< "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
|
||||
virtual QVector<glm::quat> getJointRotations() const override;
|
||||
virtual glm::quat getJointRotation(int index) const override;
|
||||
virtual QVector<glm::vec3> getJointTranslations() const override;
|
||||
virtual glm::vec3 getJointTranslation(int index) const override;
|
||||
virtual int getJointIndex(const QString& name) const override;
|
||||
virtual QStringList getJointNames() const override;
|
||||
|
|
64
interface/src/avatar/AvatarActionFarGrab.cpp
Normal file
64
interface/src/avatar/AvatarActionFarGrab.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// AvatarActionFarGrab.cpp
|
||||
// interface/src/avatar/
|
||||
//
|
||||
// Created by Seth Alves 2017-4-14
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AvatarActionFarGrab.h"
|
||||
|
||||
AvatarActionFarGrab::AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(id, ownerEntity) {
|
||||
_type = DYNAMIC_TYPE_FAR_GRAB;
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionFarGrab::AvatarActionFarGrab";
|
||||
#endif
|
||||
}
|
||||
|
||||
AvatarActionFarGrab::~AvatarActionFarGrab() {
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionFarGrab::~AvatarActionFarGrab";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
QByteArray AvatarActionFarGrab::serialize() const {
|
||||
QByteArray serializedActionArguments;
|
||||
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << DYNAMIC_TYPE_FAR_GRAB;
|
||||
dataStream << getID();
|
||||
dataStream << ObjectActionSpring::springVersion;
|
||||
|
||||
serializeParameters(dataStream);
|
||||
|
||||
return serializedActionArguments;
|
||||
}
|
||||
|
||||
void AvatarActionFarGrab::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
|
||||
QUuid id;
|
||||
dataStream >> id;
|
||||
|
||||
if (type != getType() || id != getID()) {
|
||||
qDebug() << "AvatarActionFarGrab::deserialize type or ID don't match." << type << id << getID();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t serializationVersion;
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectActionSpring::springVersion) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
deserializeParameters(serializedArguments, dataStream);
|
||||
}
|
27
interface/src/avatar/AvatarActionFarGrab.h
Normal file
27
interface/src/avatar/AvatarActionFarGrab.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// AvatarActionFarGrab.h
|
||||
// interface/src/avatar/
|
||||
//
|
||||
// Created by Seth Alves 2017-4-14
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarActionFarGrab_h
|
||||
#define hifi_AvatarActionFarGrab_h
|
||||
|
||||
#include <EntityItem.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
|
||||
class AvatarActionFarGrab : public ObjectActionSpring {
|
||||
public:
|
||||
AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~AvatarActionFarGrab();
|
||||
|
||||
QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarActionFarGrab_h
|
|
@ -23,7 +23,7 @@ const int AvatarActionHold::velocitySmoothFrames = 6;
|
|||
AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(id, ownerEntity)
|
||||
{
|
||||
_type = ACTION_TYPE_HOLD;
|
||||
_type = DYNAMIC_TYPE_HOLD;
|
||||
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
@ -323,28 +323,28 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
bool ignoreIK;
|
||||
bool needUpdate = false;
|
||||
|
||||
bool somethingChanged = ObjectAction::updateArguments(arguments);
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
withReadLock([&]{
|
||||
bool ok = true;
|
||||
relativePosition = EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
|
||||
relativePosition = EntityDynamicInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
|
||||
if (!ok) {
|
||||
relativePosition = _relativePosition;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
relativeRotation = EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
|
||||
relativeRotation = EntityDynamicInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
|
||||
if (!ok) {
|
||||
relativeRotation = _relativeRotation;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
timeScale = EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
|
||||
timeScale = EntityDynamicInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
|
||||
if (!ok) {
|
||||
timeScale = _linearTimeScale;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
hand = EntityActionInterface::extractStringArgument("hold", arguments, "hand", ok, false);
|
||||
hand = EntityDynamicInterface::extractStringArgument("hold", arguments, "hand", ok, false);
|
||||
if (!ok || !(hand == "left" || hand == "right")) {
|
||||
hand = _hand;
|
||||
}
|
||||
|
@ -353,20 +353,20 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
holderID = myAvatar->getSessionUUID();
|
||||
|
||||
ok = true;
|
||||
kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||
kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||
if (!ok) {
|
||||
kinematic = _kinematic;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments,
|
||||
kinematicSetVelocity = EntityDynamicInterface::extractBooleanArgument("hold", arguments,
|
||||
"kinematicSetVelocity", ok, false);
|
||||
if (!ok) {
|
||||
kinematicSetVelocity = _kinematicSetVelocity;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
ignoreIK = EntityActionInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false);
|
||||
ignoreIK = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false);
|
||||
if (!ok) {
|
||||
ignoreIK = _ignoreIK;
|
||||
}
|
||||
|
@ -400,8 +400,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setActionDataDirty(true);
|
||||
ownerEntity->setActionDataNeedsTransmit(true);
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -410,7 +410,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
}
|
||||
|
||||
QVariantMap AvatarActionHold::getArguments() {
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
QVariantMap arguments = ObjectDynamic::getArguments();
|
||||
withReadLock([&]{
|
||||
arguments["holderID"] = _holderID;
|
||||
arguments["relativePosition"] = glmToQMap(_relativePosition);
|
||||
|
@ -429,7 +429,7 @@ QByteArray AvatarActionHold::serialize() const {
|
|||
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
|
||||
|
||||
withReadLock([&]{
|
||||
dataStream << ACTION_TYPE_HOLD;
|
||||
dataStream << DYNAMIC_TYPE_HOLD;
|
||||
dataStream << getID();
|
||||
dataStream << AvatarActionHold::holdVersion;
|
||||
|
||||
|
@ -451,7 +451,7 @@ QByteArray AvatarActionHold::serialize() const {
|
|||
void AvatarActionHold::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityActionType type;
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
|
||||
|
|
|
@ -213,10 +213,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
}
|
||||
}
|
||||
avatar->animateScaleChanges(deltaTime);
|
||||
if (avatar->shouldDie()) {
|
||||
avatar->die();
|
||||
removeAvatar(avatar->getID());
|
||||
}
|
||||
|
||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
@ -330,44 +326,12 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
|||
return std::make_shared<Avatar>(qApp->thread(), std::make_shared<Rig>());
|
||||
}
|
||||
|
||||
void AvatarManager::processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
PerformanceTimer perfTimer("receiveAvatar");
|
||||
// enumerate over all of the avatars in this packet
|
||||
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
|
||||
while (message->getBytesLeftToRead()) {
|
||||
AvatarSharedPointer avatarData = parseAvatarData(message, sendingNode);
|
||||
if (avatarData) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||
if (avatar->isInScene()) {
|
||||
if (!_shouldRender) {
|
||||
// rare transition so we process the transaction immediately
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
avatar->removeFromScene(avatar, scene, transaction);
|
||||
if (scene) {
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
} else if (_shouldRender) {
|
||||
// very rare transition so we process the transaction immediately
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
avatar->addToScene(avatar, scene, transaction);
|
||||
if (scene) {
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||
AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason);
|
||||
|
||||
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
|
||||
// class in this context so we can call methods that don't exist at the base class.
|
||||
Avatar* avatar = static_cast<Avatar*>(removedAvatar.get());
|
||||
avatar->die();
|
||||
auto avatar = std::static_pointer_cast<Avatar>(removedAvatar);
|
||||
|
||||
AvatarMotionState* motionState = avatar->getMotionState();
|
||||
if (motionState) {
|
||||
|
@ -403,14 +367,11 @@ void AvatarManager::clearOtherAvatars() {
|
|||
if (avatar->isInScene()) {
|
||||
avatar->removeFromScene(avatar, scene, transaction);
|
||||
}
|
||||
AvatarMotionState* motionState = avatar->getMotionState();
|
||||
if (motionState) {
|
||||
_motionStatesThatMightUpdate.remove(motionState);
|
||||
_motionStatesToAddToPhysics.remove(motionState);
|
||||
_motionStatesToRemoveFromPhysics.push_back(motionState);
|
||||
}
|
||||
handleRemovedAvatar(avatar);
|
||||
avatarIterator = _avatarHash.erase(avatarIterator);
|
||||
} else {
|
||||
++avatarIterator;
|
||||
}
|
||||
++avatarIterator;
|
||||
}
|
||||
scene->enqueueTransaction(transaction);
|
||||
_myAvatar->clearLookAtTargetAvatar();
|
||||
|
|
|
@ -98,9 +98,6 @@ public slots:
|
|||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||
|
||||
protected slots:
|
||||
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
|
||||
|
||||
private:
|
||||
explicit AvatarManager(QObject* parent = 0);
|
||||
explicit AvatarManager(const AvatarManager& other);
|
||||
|
|
|
@ -412,9 +412,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
||||
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
|
||||
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now > _identityPacketExpiry || _avatarEntityDataLocallyEdited) {
|
||||
_identityPacketExpiry = now + AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS;
|
||||
if (getIdentityDataChanged()) {
|
||||
sendIdentityPacket();
|
||||
}
|
||||
|
||||
|
@ -1258,7 +1256,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
|||
setSkeletonModelURL(fullAvatarURL);
|
||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||
}
|
||||
_identityPacketExpiry = 0; // triggers an identity packet next update()
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
|
|
|
@ -701,8 +701,6 @@ private:
|
|||
std::mutex _holdActionsMutex;
|
||||
std::vector<AvatarActionHold*> _holdActions;
|
||||
|
||||
uint64_t _identityPacketExpiry { 0 };
|
||||
|
||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||
|
|
|
@ -242,7 +242,7 @@ void SkeletonModel::updateAttitude() {
|
|||
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
||||
updateAttitude();
|
||||
if (fullUpdate) {
|
||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients());
|
||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
|
||||
|
||||
Model::simulate(deltaTime, fullUpdate);
|
||||
|
||||
|
|
|
@ -79,6 +79,25 @@ int main(int argc, const char* argv[]) {
|
|||
instanceMightBeRunning = false;
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
||||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||
parser.addOption(checkMinSpecOption);
|
||||
parser.addOption(runServerOption);
|
||||
parser.addOption(serverContentPathOption);
|
||||
parser.addOption(allowMultipleInstancesOption);
|
||||
parser.parse(arguments);
|
||||
bool runServer = parser.isSet(runServerOption);
|
||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption);
|
||||
|
||||
if (allowMultipleInstances) {
|
||||
instanceMightBeRunning = false;
|
||||
}
|
||||
|
||||
if (instanceMightBeRunning) {
|
||||
// Try to connect and send message to existing interface instance
|
||||
QLocalSocket socket;
|
||||
|
@ -137,18 +156,6 @@ int main(int argc, const char* argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
||||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||
parser.addOption(checkMinSpecOption);
|
||||
parser.addOption(runServerOption);
|
||||
parser.addOption(serverContentPathOption);
|
||||
parser.parse(arguments);
|
||||
bool runServer = parser.isSet(runServerOption);
|
||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||
|
||||
QElapsedTimer startupTime;
|
||||
startupTime.start();
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
|
||||
static const QString LAST_BROWSE_ASSETS_LOCATION_SETTING = "LastBrowseAssetsLocation";
|
||||
|
||||
|
||||
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result) {
|
||||
|
@ -149,6 +150,15 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location
|
|||
Setting::Handle<QVariant>(LAST_BROWSE_LOCATION_SETTING).set(location);
|
||||
}
|
||||
|
||||
QString WindowScriptingInterface::getPreviousBrowseAssetLocation() const {
|
||||
QString ASSETS_ROOT_PATH = "/";
|
||||
return Setting::Handle<QString>(LAST_BROWSE_ASSETS_LOCATION_SETTING, ASSETS_ROOT_PATH).get();
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& location) {
|
||||
Setting::Handle<QVariant>(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location);
|
||||
}
|
||||
|
||||
/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and
|
||||
/// might be in same thread as a script that sets the reticle to invisible
|
||||
void WindowScriptingInterface::ensureReticleVisible() const {
|
||||
|
@ -202,6 +212,31 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString&
|
|||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
|
||||
/// directory the browser will start at the root directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the asset browser at
|
||||
/// \param const QString& nameFilter filter to filter asset names by - see `QFileDialog`
|
||||
/// \return QScriptValue asset path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
if (path.isEmpty()) {
|
||||
path = getPreviousBrowseAssetLocation();
|
||||
}
|
||||
if (path.left(1) != "/") {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.right(1) != "/") {
|
||||
path = path + "/";
|
||||
}
|
||||
QString result = OffscreenUi::getOpenAssetName(nullptr, title, path, nameFilter);
|
||||
if (!result.isEmpty()) {
|
||||
setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath());
|
||||
}
|
||||
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::showAssetServer(const QString& upload) {
|
||||
QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload));
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public slots:
|
|||
CustomPromptResult customPrompt(const QVariant& config);
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void showAssetServer(const QString& upload = "");
|
||||
void copyToClipboard(const QString& text);
|
||||
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
|
||||
|
@ -88,6 +89,9 @@ private:
|
|||
QString getPreviousBrowseLocation() const;
|
||||
void setPreviousBrowseLocation(const QString& location);
|
||||
|
||||
QString getPreviousBrowseAssetLocation() const;
|
||||
void setPreviousBrowseAssetLocation(const QString& location);
|
||||
|
||||
void ensureReticleVisible() const;
|
||||
|
||||
int createMessageBox(QString title, QString text, int buttons, int defaultButton);
|
||||
|
|
|
@ -94,7 +94,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
|||
PROFILE_RANGE(app, __FUNCTION__);
|
||||
|
||||
if (!_uiTexture) {
|
||||
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_uiTexture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
|
||||
_uiTexture->setSource(__FUNCTION__);
|
||||
}
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
|
@ -207,13 +207,13 @@ void ApplicationOverlay::buildFramebufferObject() {
|
|||
auto width = uiSize.x;
|
||||
auto height = uiSize.y;
|
||||
if (!_overlayFramebuffer->getDepthStencilBuffer()) {
|
||||
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, gpu::Texture::SINGLE_MIP, DEFAULT_SAMPLER));
|
||||
auto overlayDepthTexture = gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, gpu::Texture::SINGLE_MIP, DEFAULT_SAMPLER);
|
||||
_overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT);
|
||||
}
|
||||
|
||||
if (!_overlayFramebuffer->getRenderBuffer(0)) {
|
||||
const gpu::Sampler OVERLAY_SAMPLER(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP);
|
||||
auto colorBuffer = gpu::TexturePointer(gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, gpu::Texture::SINGLE_MIP, OVERLAY_SAMPLER));
|
||||
auto colorBuffer = gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, gpu::Texture::SINGLE_MIP, OVERLAY_SAMPLER);
|
||||
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "Menu.h"
|
||||
#include "Util.h"
|
||||
#include "SequenceNumberStats.h"
|
||||
#include "StatTracker.h"
|
||||
|
||||
HIFI_QML_DEF(Stats)
|
||||
|
||||
|
@ -250,6 +251,9 @@ void Stats::updateStats(bool force) {
|
|||
STAT_UPDATE(downloads, loadingRequests.size());
|
||||
STAT_UPDATE(downloadLimit, ResourceCache::getRequestLimit())
|
||||
STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount());
|
||||
STAT_UPDATE(processing, DependencyManager::get<StatTracker>()->getStat("Processing").toInt());
|
||||
STAT_UPDATE(processingPending, DependencyManager::get<StatTracker>()->getStat("PendingProcessing").toInt());
|
||||
|
||||
|
||||
// See if the active download urls have changed
|
||||
bool shouldUpdateUrls = _downloads != _downloadUrls.size();
|
||||
|
@ -335,6 +339,8 @@ void Stats::updateStats(bool force) {
|
|||
// Update Frame timing (in ms)
|
||||
STAT_UPDATE(gpuFrameTime, (float)gpuContext->getFrameTimerGPUAverage());
|
||||
STAT_UPDATE(batchFrameTime, (float)gpuContext->getFrameTimerBatchAverage());
|
||||
auto config = qApp->getRenderEngine()->getConfiguration().get();
|
||||
STAT_UPDATE(engineFrameTime, (float) config->getCPURunTime());
|
||||
STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime());
|
||||
|
||||
|
||||
|
|
|
@ -89,6 +89,8 @@ class Stats : public QQuickItem {
|
|||
STATS_PROPERTY(int, downloadLimit, 0)
|
||||
STATS_PROPERTY(int, downloadsPending, 0)
|
||||
Q_PROPERTY(QStringList downloadUrls READ downloadUrls NOTIFY downloadUrlsChanged)
|
||||
STATS_PROPERTY(int, processing, 0)
|
||||
STATS_PROPERTY(int, processingPending, 0)
|
||||
STATS_PROPERTY(int, triangles, 0)
|
||||
STATS_PROPERTY(int, quads, 0)
|
||||
STATS_PROPERTY(int, materialSwitches, 0)
|
||||
|
@ -128,6 +130,7 @@ class Stats : public QQuickItem {
|
|||
STATS_PROPERTY(int, gpuFreeMemory, 0)
|
||||
STATS_PROPERTY(float, gpuFrameTime, 0)
|
||||
STATS_PROPERTY(float, batchFrameTime, 0)
|
||||
STATS_PROPERTY(float, engineFrameTime, 0)
|
||||
STATS_PROPERTY(float, avatarSimulationTime, 0)
|
||||
|
||||
public:
|
||||
|
@ -213,6 +216,8 @@ signals:
|
|||
void downloadLimitChanged();
|
||||
void downloadsPendingChanged();
|
||||
void downloadUrlsChanged();
|
||||
void processingChanged();
|
||||
void processingPendingChanged();
|
||||
void trianglesChanged();
|
||||
void quadsChanged();
|
||||
void materialSwitchesChanged();
|
||||
|
@ -250,6 +255,7 @@ signals:
|
|||
void gpuFreeMemoryChanged();
|
||||
void gpuFrameTimeChanged();
|
||||
void batchFrameTimeChanged();
|
||||
void engineFrameTimeChanged();
|
||||
void avatarSimulationTimeChanged();
|
||||
void rectifiedTextureCountChanged();
|
||||
void decimatedTextureCountChanged();
|
||||
|
|
|
@ -126,6 +126,55 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
|||
QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection,
|
||||
Q_ARG(const QVariantMap&, textureMap));
|
||||
}
|
||||
|
||||
// relative
|
||||
auto jointTranslationsValue = properties["jointTranslations"];
|
||||
if (jointTranslationsValue.canConvert(QVariant::List)) {
|
||||
const QVariantList& jointTranslations = jointTranslationsValue.toList();
|
||||
int translationCount = jointTranslations.size();
|
||||
int jointCount = _model->getJointStateCount();
|
||||
if (translationCount < jointCount) {
|
||||
jointCount = translationCount;
|
||||
}
|
||||
for (int i=0; i < jointCount; i++) {
|
||||
const auto& translationValue = jointTranslations[i];
|
||||
if (translationValue.isValid()) {
|
||||
_model->setJointTranslation(i, true, vec3FromVariant(translationValue), 1.0f);
|
||||
}
|
||||
}
|
||||
_updateModel = true;
|
||||
}
|
||||
|
||||
// relative
|
||||
auto jointRotationsValue = properties["jointRotations"];
|
||||
if (jointRotationsValue.canConvert(QVariant::List)) {
|
||||
const QVariantList& jointRotations = jointRotationsValue.toList();
|
||||
int rotationCount = jointRotations.size();
|
||||
int jointCount = _model->getJointStateCount();
|
||||
if (rotationCount < jointCount) {
|
||||
jointCount = rotationCount;
|
||||
}
|
||||
for (int i=0; i < jointCount; i++) {
|
||||
const auto& rotationValue = jointRotations[i];
|
||||
if (rotationValue.isValid()) {
|
||||
_model->setJointRotation(i, true, quatFromVariant(rotationValue), 1.0f);
|
||||
}
|
||||
}
|
||||
_updateModel = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename vectorType, typename itemType>
|
||||
vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
|
||||
vectorType result;
|
||||
if (_model && _model->isActive()) {
|
||||
const int jointCount = _model->getJointStateCount();
|
||||
result.reserve(jointCount);
|
||||
for (int i = 0; i < jointCount; i++) {
|
||||
result << function(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariant ModelOverlay::getProperty(const QString& property) {
|
||||
|
@ -150,6 +199,58 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
}
|
||||
|
||||
if (property == "jointNames") {
|
||||
if (_model && _model->isActive()) {
|
||||
// note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty
|
||||
const RigPointer rig = _model->getRig();
|
||||
if (rig) {
|
||||
return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString {
|
||||
return rig->nameOfJoint(jointIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// relative
|
||||
if (property == "jointRotations") {
|
||||
return mapJoints<QVariantList, QVariant>(
|
||||
[this](int jointIndex) -> QVariant {
|
||||
glm::quat rotation;
|
||||
_model->getJointRotation(jointIndex, rotation);
|
||||
return quatToVariant(rotation);
|
||||
});
|
||||
}
|
||||
|
||||
// relative
|
||||
if (property == "jointTranslations") {
|
||||
return mapJoints<QVariantList, QVariant>(
|
||||
[this](int jointIndex) -> QVariant {
|
||||
glm::vec3 translation;
|
||||
_model->getJointTranslation(jointIndex, translation);
|
||||
return vec3toVariant(translation);
|
||||
});
|
||||
}
|
||||
|
||||
// absolute
|
||||
if (property == "jointOrientations") {
|
||||
return mapJoints<QVariantList, QVariant>(
|
||||
[this](int jointIndex) -> QVariant {
|
||||
glm::quat orientation;
|
||||
_model->getJointRotationInWorldFrame(jointIndex, orientation);
|
||||
return quatToVariant(orientation);
|
||||
});
|
||||
}
|
||||
|
||||
// absolute
|
||||
if (property == "jointPositions") {
|
||||
return mapJoints<QVariantList, QVariant>(
|
||||
[this](int jointIndex) -> QVariant {
|
||||
glm::vec3 position;
|
||||
_model->getJointPositionInWorldFrame(jointIndex, position);
|
||||
return vec3toVariant(position);
|
||||
});
|
||||
}
|
||||
|
||||
return Volume3DOverlay::getProperty(property);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,12 @@ public:
|
|||
|
||||
void locationChanged(bool tellPhysics) override;
|
||||
|
||||
protected:
|
||||
// helper to extract metadata from our Model's rigged joints
|
||||
template <typename itemType> using mapFunction = std::function<itemType(int jointIndex)>;
|
||||
template <typename vectorType, typename itemType>
|
||||
vectorType mapJoints(mapFunction<itemType> function) const;
|
||||
|
||||
private:
|
||||
|
||||
ModelPointer _model;
|
||||
|
|
|
@ -298,7 +298,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
|
|
|
@ -1006,7 +1006,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
|
|||
_timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE;
|
||||
}
|
||||
|
||||
emit inputReceived({ audioBuffer.data(), numSamples });
|
||||
emit inputReceived(audioBuffer);
|
||||
|
||||
if (_noiseGate.openedInLastBlock()) {
|
||||
emit noiseGateOpened();
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "AudioLogging.h"
|
||||
#include "SoundCache.h"
|
||||
|
||||
static const int SOUNDS_LOADING_PRIORITY { -7 }; // Make sure sounds load after the low rez texture mips
|
||||
|
||||
int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
|
||||
|
||||
SoundCache::SoundCache(QObject* parent) :
|
||||
|
@ -37,5 +39,7 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) {
|
|||
QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
const void* extra) {
|
||||
qCDebug(audio) << "Requesting sound at" << url.toString();
|
||||
return QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
|
||||
auto resource = QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
|
||||
resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY);
|
||||
return resource;
|
||||
}
|
||||
|
|
|
@ -967,6 +967,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
const int coefficientsSize = sizeof(float) * numCoefficients;
|
||||
PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize);
|
||||
_headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy!
|
||||
_headData->_baseBlendshapeCoefficients.resize(numCoefficients);
|
||||
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize);
|
||||
sourceBuffer += coefficientsSize;
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
|
@ -1391,6 +1392,22 @@ void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
|
|||
}
|
||||
}
|
||||
|
||||
QVector<glm::vec3> AvatarData::getJointTranslations() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVector<glm::vec3> result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
|
||||
"getJointTranslations", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QVector<glm::vec3>, result));
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
QVector<glm::vec3> jointTranslations(_jointData.size());
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
jointTranslations[i] = _jointData[i].translation;
|
||||
}
|
||||
return jointTranslations;
|
||||
}
|
||||
|
||||
void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVector<glm::quat> result;
|
||||
|
@ -1456,7 +1473,22 @@ QStringList AvatarData::getJointNames() const {
|
|||
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
|
||||
QDataStream packetStream(data);
|
||||
|
||||
packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData;
|
||||
packetStream >> identityOut.uuid
|
||||
>> identityOut.skeletonModelURL
|
||||
>> identityOut.attachmentData
|
||||
>> identityOut.displayName
|
||||
>> identityOut.sessionDisplayName
|
||||
>> identityOut.avatarEntityData
|
||||
>> identityOut.updatedAt;
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(avatars) << __FUNCTION__
|
||||
<< "identityOut.uuid:" << identityOut.uuid
|
||||
<< "identityOut.skeletonModelURL:" << identityOut.skeletonModelURL
|
||||
<< "identityOut.displayName:" << identityOut.displayName
|
||||
<< "identityOut.sessionDisplayName:" << identityOut.sessionDisplayName;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static const QUrl emptyURL("");
|
||||
|
@ -1467,6 +1499,12 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
|||
|
||||
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) {
|
||||
|
||||
if (identity.updatedAt < _identityUpdatedAt) {
|
||||
qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID()
|
||||
<< "identity.updatedAt:" << identity.updatedAt << "_identityUpdatedAt:" << _identityUpdatedAt;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
||||
setSkeletonModelURL(identity.skeletonModelURL);
|
||||
identityChanged = true;
|
||||
|
@ -1496,24 +1534,35 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
|
|||
setAvatarEntityData(identity.avatarEntityData);
|
||||
identityChanged = true;
|
||||
}
|
||||
// flag this avatar as non-stale by updating _averageBytesReceived
|
||||
const int BOGUS_NUM_BYTES = 1;
|
||||
_averageBytesReceived.updateAverage(BOGUS_NUM_BYTES);
|
||||
|
||||
// use the timestamp from this identity, since we want to honor the updated times in "server clock"
|
||||
// this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt
|
||||
_identityUpdatedAt = identity.updatedAt;
|
||||
}
|
||||
|
||||
QByteArray AvatarData::identityByteArray() const {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL);
|
||||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL
|
||||
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() << _avatarEntityData;
|
||||
identityStream << getSessionUUID()
|
||||
<< urlToSend
|
||||
<< _attachmentData
|
||||
<< _displayName
|
||||
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||
<< _avatarEntityData
|
||||
<< _identityUpdatedAt;
|
||||
});
|
||||
|
||||
return identityData;
|
||||
}
|
||||
|
||||
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
if (skeletonModelURL.isEmpty()) {
|
||||
qCDebug(avatars) << __FUNCTION__ << "caller called with empty URL.";
|
||||
}
|
||||
|
||||
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
||||
if (expanded == _skeletonModelURL) {
|
||||
return;
|
||||
|
@ -1522,6 +1571,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString();
|
||||
|
||||
updateJointMappings();
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
void AvatarData::setDisplayName(const QString& displayName) {
|
||||
|
@ -1531,6 +1581,7 @@ void AvatarData::setDisplayName(const QString& displayName) {
|
|||
sendIdentityPacket();
|
||||
|
||||
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
QVector<AttachmentData> AvatarData::getAttachmentData() const {
|
||||
|
@ -1549,6 +1600,7 @@ void AvatarData::setAttachmentData(const QVector<AttachmentData>& attachmentData
|
|||
return;
|
||||
}
|
||||
_attachmentData = attachmentData;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
void AvatarData::attach(const QString& modelURL, const QString& jointName,
|
||||
|
@ -1678,7 +1730,6 @@ void AvatarData::sendAvatarDataPacket() {
|
|||
|
||||
void AvatarData::sendIdentityPacket() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray identityData = identityByteArray();
|
||||
|
||||
auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||
|
@ -1692,6 +1743,7 @@ void AvatarData::sendIdentityPacket() {
|
|||
});
|
||||
|
||||
_avatarEntityDataLocallyEdited = false;
|
||||
_identityDataChanged = false;
|
||||
}
|
||||
|
||||
void AvatarData::updateJointMappings() {
|
||||
|
@ -2228,10 +2280,12 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent
|
|||
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
||||
_avatarEntityData.insert(entityID, entityData);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
} else {
|
||||
itr.value() = entityData;
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2245,6 +2299,7 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID) {
|
|||
_avatarEntitiesLock.withWriteLock([&] {
|
||||
_avatarEntityData.remove(entityID);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
markIdentityDataChanged();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -110,9 +110,7 @@ const char LEFT_HAND_POINTING_FLAG = 1;
|
|||
const char RIGHT_HAND_POINTING_FLAG = 2;
|
||||
const char IS_FINGER_POINTING_FLAG = 4;
|
||||
|
||||
const qint64 AVATAR_UPDATE_TIMEOUT = 5 * USECS_PER_SECOND;
|
||||
|
||||
// AvatarData state flags - we store the details about the packet encoding in the first byte,
|
||||
// AvatarData state flags - we store the details about the packet encoding in the first byte,
|
||||
// before the "header" structure
|
||||
const char AVATARDATA_FLAGS_MINIMUM = 0;
|
||||
|
||||
|
@ -497,6 +495,7 @@ public:
|
|||
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
||||
|
||||
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
|
||||
Q_INVOKABLE virtual QVector<glm::vec3> getJointTranslations() const;
|
||||
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
|
||||
Q_INVOKABLE virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
|
||||
|
||||
|
@ -530,6 +529,7 @@ public:
|
|||
QString displayName;
|
||||
QString sessionDisplayName;
|
||||
AvatarEntityMap avatarEntityData;
|
||||
quint64 updatedAt;
|
||||
};
|
||||
|
||||
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
||||
|
@ -546,7 +546,10 @@ public:
|
|||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
virtual void setSessionDisplayName(const QString& sessionDisplayName) { _sessionDisplayName = sessionDisplayName; };
|
||||
virtual void setSessionDisplayName(const QString& sessionDisplayName) {
|
||||
_sessionDisplayName = sessionDisplayName;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
||||
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
@ -564,7 +567,6 @@ public:
|
|||
|
||||
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
||||
|
||||
int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); }
|
||||
int getAverageBytesReceivedPerSecond() const;
|
||||
int getReceiveRate() const;
|
||||
|
||||
|
@ -600,9 +602,6 @@ public:
|
|||
return _lastSentJointData;
|
||||
}
|
||||
|
||||
|
||||
bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; }
|
||||
|
||||
static const float OUT_OF_VIEW_PENALTY;
|
||||
|
||||
static void sortAvatars(
|
||||
|
@ -619,11 +618,15 @@ public:
|
|||
static float _avatarSortCoefficientCenter;
|
||||
static float _avatarSortCoefficientAge;
|
||||
|
||||
|
||||
bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called
|
||||
void markIdentityDataChanged() {
|
||||
_identityDataChanged = true;
|
||||
_identityUpdatedAt = usecTimestampNow();
|
||||
}
|
||||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
|
||||
|
||||
public slots:
|
||||
void sendAvatarDataPacket();
|
||||
void sendIdentityPacket();
|
||||
|
@ -778,6 +781,9 @@ protected:
|
|||
quint64 _audioLoudnessChanged { 0 };
|
||||
float _audioAverageLoudness { 0.0f };
|
||||
|
||||
bool _identityDataChanged { false };
|
||||
quint64 _identityUpdatedAt { 0 };
|
||||
|
||||
private:
|
||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||
static QUrl _defaultFullAvatarModelUrl;
|
||||
|
|
|
@ -57,7 +57,7 @@ public slots:
|
|||
protected slots:
|
||||
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
|
||||
|
||||
virtual void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QVector>
|
||||
|
||||
#include <FaceshiftConstants.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
@ -38,6 +39,8 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
|||
_rightEyeBlink(0.0f),
|
||||
_averageLoudness(0.0f),
|
||||
_browAudioLift(0.0f),
|
||||
_baseBlendshapeCoefficients(QVector<float>(0, 0.0f)),
|
||||
_currBlendShapeCoefficients(QVector<float>(0, 0.0f)),
|
||||
_owningAvatar(owningAvatar)
|
||||
{
|
||||
|
||||
|
@ -86,6 +89,24 @@ static const QMap<QString, int>& getBlendshapesLookupMap() {
|
|||
return blendshapeLookupMap;
|
||||
}
|
||||
|
||||
const QVector<float>& HeadData::getSummedBlendshapeCoefficients() {
|
||||
int maxSize = std::max(_baseBlendshapeCoefficients.size(), _blendshapeCoefficients.size());
|
||||
if (_currBlendShapeCoefficients.size() != maxSize) {
|
||||
_currBlendShapeCoefficients.resize(maxSize);
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxSize; i++) {
|
||||
if (i >= _baseBlendshapeCoefficients.size()) {
|
||||
_currBlendShapeCoefficients[i] = _blendshapeCoefficients[i];
|
||||
} else if (i >= _blendshapeCoefficients.size()) {
|
||||
_currBlendShapeCoefficients[i] = _baseBlendshapeCoefficients[i];
|
||||
} else {
|
||||
_currBlendShapeCoefficients[i] = _baseBlendshapeCoefficients[i] + _blendshapeCoefficients[i];
|
||||
}
|
||||
}
|
||||
|
||||
return _currBlendShapeCoefficients;
|
||||
}
|
||||
|
||||
void HeadData::setBlendshape(QString name, float val) {
|
||||
const auto& blendshapeLookupMap = getBlendshapesLookupMap();
|
||||
|
@ -96,7 +117,10 @@ void HeadData::setBlendshape(QString name, float val) {
|
|||
if (_blendshapeCoefficients.size() <= it.value()) {
|
||||
_blendshapeCoefficients.resize(it.value() + 1);
|
||||
}
|
||||
_blendshapeCoefficients[it.value()] = val;
|
||||
if (_baseBlendshapeCoefficients.size() <= it.value()) {
|
||||
_baseBlendshapeCoefficients.resize(it.value() + 1);
|
||||
}
|
||||
_baseBlendshapeCoefficients[it.value()] = val;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
|
||||
void setBlendshape(QString name, float val);
|
||||
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
const QVector<float>& getSummedBlendshapeCoefficients();
|
||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
||||
|
||||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||
|
@ -92,6 +93,8 @@ protected:
|
|||
float _browAudioLift;
|
||||
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
QVector<float> _baseBlendshapeCoefficients;
|
||||
QVector<float> _currBlendShapeCoefficients;
|
||||
AvatarData* _owningAvatar;
|
||||
|
||||
private:
|
||||
|
|
|
@ -210,6 +210,13 @@ QVector<glm::quat> ScriptAvatarData::getJointRotations() const {
|
|||
return QVector<glm::quat>();
|
||||
}
|
||||
}
|
||||
QVector<glm::vec3> ScriptAvatarData::getJointTranslations() const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getJointTranslations();
|
||||
} else {
|
||||
return QVector<glm::vec3>();
|
||||
}
|
||||
}
|
||||
bool ScriptAvatarData::isJointDataValid(const QString& name) const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->isJointDataValid(name);
|
||||
|
|
|
@ -106,6 +106,7 @@ public:
|
|||
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
|
||||
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
||||
Q_INVOKABLE QVector<glm::quat> getJointRotations() const;
|
||||
Q_INVOKABLE QVector<glm::vec3> getJointTranslations() const;
|
||||
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
|
||||
Q_INVOKABLE int getJointIndex(const QString& name) const;
|
||||
Q_INVOKABLE QStringList getJointNames() const;
|
||||
|
|
|
@ -10,4 +10,4 @@ GroupSources("src/controllers")
|
|||
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS} "${CMAKE_BINARY_DIR}/includes")
|
||||
|
|
290
libraries/controllers/src/controllers/InputRecorder.cpp
Normal file
290
libraries/controllers/src/controllers/InputRecorder.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
//
|
||||
// Created by Dante Ruiz 2017/04/16
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "InputRecorder.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QStandardPaths>
|
||||
#include <QDateTime>
|
||||
#include <QByteArray>
|
||||
#include <QStandardPaths>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
|
||||
QString FILE_PREFIX_NAME = "input-recording-";
|
||||
QString COMPRESS_EXTENSION = ".tar.gz";
|
||||
namespace controller {
|
||||
|
||||
QJsonObject poseToJsonObject(const Pose pose) {
|
||||
QJsonObject newPose;
|
||||
|
||||
QJsonArray translation;
|
||||
translation.append(pose.translation.x);
|
||||
translation.append(pose.translation.y);
|
||||
translation.append(pose.translation.z);
|
||||
|
||||
QJsonArray rotation;
|
||||
rotation.append(pose.rotation.x);
|
||||
rotation.append(pose.rotation.y);
|
||||
rotation.append(pose.rotation.z);
|
||||
rotation.append(pose.rotation.w);
|
||||
|
||||
QJsonArray velocity;
|
||||
velocity.append(pose.velocity.x);
|
||||
velocity.append(pose.velocity.y);
|
||||
velocity.append(pose.velocity.z);
|
||||
|
||||
QJsonArray angularVelocity;
|
||||
angularVelocity.append(pose.angularVelocity.x);
|
||||
angularVelocity.append(pose.angularVelocity.y);
|
||||
angularVelocity.append(pose.angularVelocity.z);
|
||||
|
||||
newPose["translation"] = translation;
|
||||
newPose["rotation"] = rotation;
|
||||
newPose["velocity"] = velocity;
|
||||
newPose["angularVelocity"] = angularVelocity;
|
||||
newPose["valid"] = pose.valid;
|
||||
|
||||
return newPose;
|
||||
}
|
||||
|
||||
Pose jsonObjectToPose(const QJsonObject object) {
|
||||
Pose pose;
|
||||
QJsonArray translation = object["translation"].toArray();
|
||||
QJsonArray rotation = object["rotation"].toArray();
|
||||
QJsonArray velocity = object["velocity"].toArray();
|
||||
QJsonArray angularVelocity = object["angularVelocity"].toArray();
|
||||
|
||||
pose.valid = object["valid"].toBool();
|
||||
|
||||
pose.translation.x = translation[0].toDouble();
|
||||
pose.translation.y = translation[1].toDouble();
|
||||
pose.translation.z = translation[2].toDouble();
|
||||
|
||||
pose.rotation.x = rotation[0].toDouble();
|
||||
pose.rotation.y = rotation[1].toDouble();
|
||||
pose.rotation.z = rotation[2].toDouble();
|
||||
pose.rotation.w = rotation[3].toDouble();
|
||||
|
||||
pose.velocity.x = velocity[0].toDouble();
|
||||
pose.velocity.y = velocity[1].toDouble();
|
||||
pose.velocity.z = velocity[2].toDouble();
|
||||
|
||||
pose.angularVelocity.x = angularVelocity[0].toDouble();
|
||||
pose.angularVelocity.y = angularVelocity[1].toDouble();
|
||||
pose.angularVelocity.z = angularVelocity[2].toDouble();
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
|
||||
void exportToFile(QJsonObject& object) {
|
||||
if (!QDir(SAVE_DIRECTORY).exists()) {
|
||||
QDir().mkdir(SAVE_DIRECTORY);
|
||||
}
|
||||
|
||||
QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
timeStamp.replace(":", "-");
|
||||
QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
|
||||
qDebug() << fileName;
|
||||
QFile saveFile (fileName);
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "could not open file: " << fileName;
|
||||
return;
|
||||
}
|
||||
QJsonDocument saveData(object);
|
||||
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
|
||||
saveFile.write(compressedData);
|
||||
}
|
||||
|
||||
QJsonObject openFile(const QString& file, bool& status) {
|
||||
QJsonObject object;
|
||||
QFile openFile(file);
|
||||
if (!openFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "could not open file: " << file;
|
||||
status = false;
|
||||
return object;
|
||||
}
|
||||
QByteArray compressedData = qUncompress(openFile.readAll());
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
|
||||
object = jsonDoc.object();
|
||||
status = true;
|
||||
return object;
|
||||
}
|
||||
|
||||
InputRecorder::InputRecorder() {}
|
||||
|
||||
InputRecorder::~InputRecorder() {}
|
||||
|
||||
InputRecorder* InputRecorder::getInstance() {
|
||||
static InputRecorder inputRecorder;
|
||||
return &inputRecorder;
|
||||
}
|
||||
|
||||
QString InputRecorder::getSaveDirectory() {
|
||||
return SAVE_DIRECTORY;
|
||||
}
|
||||
|
||||
void InputRecorder::startRecording() {
|
||||
_recording = true;
|
||||
_playback = false;
|
||||
_framesRecorded = 0;
|
||||
_poseStateList.clear();
|
||||
_actionStateList.clear();
|
||||
}
|
||||
|
||||
void InputRecorder::saveRecording() {
|
||||
QJsonObject data;
|
||||
data["frameCount"] = _framesRecorded;
|
||||
|
||||
QJsonArray actionArrayList;
|
||||
QJsonArray poseArrayList;
|
||||
for(const ActionStates actionState: _actionStateList) {
|
||||
QJsonArray actionArray;
|
||||
for (const float value: actionState) {
|
||||
actionArray.append(value);
|
||||
}
|
||||
actionArrayList.append(actionArray);
|
||||
}
|
||||
|
||||
for (const PoseStates poseState: _poseStateList) {
|
||||
QJsonArray poseArray;
|
||||
for (const Pose pose: poseState) {
|
||||
poseArray.append(poseToJsonObject(pose));
|
||||
}
|
||||
poseArrayList.append(poseArray);
|
||||
}
|
||||
|
||||
data["actionList"] = actionArrayList;
|
||||
data["poseList"] = poseArrayList;
|
||||
exportToFile(data);
|
||||
}
|
||||
|
||||
void InputRecorder::loadRecording(const QString& path) {
|
||||
_recording = false;
|
||||
_playback = false;
|
||||
_loading = true;
|
||||
_playCount = 0;
|
||||
resetFrame();
|
||||
_poseStateList.clear();
|
||||
_actionStateList.clear();
|
||||
QString filePath = path;
|
||||
filePath.remove(0,8);
|
||||
QFileInfo info(filePath);
|
||||
QString extension = info.suffix();
|
||||
if (extension != "gz") {
|
||||
qWarning() << "can not load file with exentsion of " << extension;
|
||||
return;
|
||||
}
|
||||
bool success = false;
|
||||
QJsonObject data = openFile(info.absoluteFilePath(), success);
|
||||
if (success) {
|
||||
_framesRecorded = data["frameCount"].toInt();
|
||||
QJsonArray actionArrayList = data["actionList"].toArray();
|
||||
QJsonArray poseArrayList = data["poseList"].toArray();
|
||||
|
||||
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
|
||||
QJsonArray actionState = actionArrayList[actionIndex].toArray();
|
||||
for (int index = 0; index < actionState.size(); index++) {
|
||||
_currentFrameActions[index] = actionState[index].toInt();
|
||||
}
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
_currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
}
|
||||
|
||||
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
|
||||
QJsonArray poseState = poseArrayList[poseIndex].toArray();
|
||||
for (int index = 0; index < poseState.size(); index++) {
|
||||
_currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject());
|
||||
}
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
}
|
||||
}
|
||||
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void InputRecorder::stopRecording() {
|
||||
_recording = false;
|
||||
}
|
||||
|
||||
void InputRecorder::startPlayback() {
|
||||
_playback = true;
|
||||
_recording = false;
|
||||
_playCount = 0;
|
||||
}
|
||||
|
||||
void InputRecorder::stopPlayback() {
|
||||
_playback = false;
|
||||
_playCount = 0;
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, float value) {
|
||||
if (_recording) {
|
||||
_currentFrameActions[toInt(action)] += value;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) {
|
||||
if (_recording) {
|
||||
_currentFramePoses[toInt(action)] = pose;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::resetFrame() {
|
||||
if (_recording) {
|
||||
for(auto& channel : _currentFramePoses) {
|
||||
channel = Pose();
|
||||
}
|
||||
|
||||
for(auto& channel : _currentFrameActions) {
|
||||
channel = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float InputRecorder::getActionState(controller::Action action) {
|
||||
if (_actionStateList.size() > 0 ) {
|
||||
return _actionStateList[_playCount][toInt(action)];
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
controller::Pose InputRecorder::getPoseState(controller::Action action) {
|
||||
if (_poseStateList.size() > 0) {
|
||||
return _poseStateList[_playCount][toInt(action)];
|
||||
}
|
||||
|
||||
return Pose();
|
||||
}
|
||||
|
||||
void InputRecorder::frameTick() {
|
||||
if (_recording) {
|
||||
_framesRecorded++;
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
}
|
||||
|
||||
if (_playback) {
|
||||
_playCount++;
|
||||
if (_playCount == _framesRecorded) {
|
||||
_playCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
libraries/controllers/src/controllers/InputRecorder.h
Normal file
62
libraries/controllers/src/controllers/InputRecorder.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// Created by Dante Ruiz on 2017/04/16
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_InputRecorder_h
|
||||
#define hifi_InputRecorder_h
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "Pose.h"
|
||||
#include "Actions.h"
|
||||
|
||||
namespace controller {
|
||||
class InputRecorder {
|
||||
public:
|
||||
using PoseStates = std::vector<Pose>;
|
||||
using ActionStates = std::vector<float>;
|
||||
|
||||
InputRecorder();
|
||||
~InputRecorder();
|
||||
|
||||
static InputRecorder* getInstance();
|
||||
|
||||
void saveRecording();
|
||||
void loadRecording(const QString& path);
|
||||
void startRecording();
|
||||
void startPlayback();
|
||||
void stopPlayback();
|
||||
void stopRecording();
|
||||
void toggleRecording() { _recording = !_recording; }
|
||||
void togglePlayback() { _playback = !_playback; }
|
||||
void resetFrame();
|
||||
bool isRecording() { return _recording; }
|
||||
bool isPlayingback() { return (_playback && !_loading); }
|
||||
void setActionState(controller::Action action, float value);
|
||||
void setActionState(controller::Action action, const controller::Pose pose);
|
||||
float getActionState(controller::Action action);
|
||||
controller::Pose getPoseState(controller::Action action);
|
||||
QString getSaveDirectory();
|
||||
void frameTick();
|
||||
private:
|
||||
bool _recording { false };
|
||||
bool _playback { false };
|
||||
bool _loading { false };
|
||||
std::vector<PoseStates> _poseStateList = std::vector<PoseStates>();
|
||||
std::vector<ActionStates> _actionStateList = std::vector<ActionStates>();
|
||||
PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
|
||||
int _framesRecorded { 0 };
|
||||
int _playCount { 0 };
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -23,6 +23,7 @@
|
|||
#include "impl/MappingBuilderProxy.h"
|
||||
#include "Logging.h"
|
||||
#include "InputDevice.h"
|
||||
#include "InputRecorder.h"
|
||||
|
||||
|
||||
static QRegularExpression SANITIZE_NAME_EXPRESSION{ "[\\(\\)\\.\\s]" };
|
||||
|
@ -154,6 +155,41 @@ namespace controller {
|
|||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulse(strength, SHORT_HAPTIC_DURATION_MS, hand);
|
||||
}
|
||||
|
||||
void ScriptingInterface::startInputRecording() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->startRecording();
|
||||
}
|
||||
|
||||
void ScriptingInterface::stopInputRecording() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->stopRecording();
|
||||
}
|
||||
|
||||
void ScriptingInterface::startInputPlayback() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->startPlayback();
|
||||
}
|
||||
|
||||
void ScriptingInterface::stopInputPlayback() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->stopPlayback();
|
||||
}
|
||||
|
||||
void ScriptingInterface::saveInputRecording() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->saveRecording();
|
||||
}
|
||||
|
||||
void ScriptingInterface::loadInputRecording(const QString& file) {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->loadRecording(file);
|
||||
}
|
||||
|
||||
QString ScriptingInterface::getInputRecorderSaveDirectory() {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
return inputRecorder->getSaveDirectory();
|
||||
}
|
||||
|
||||
bool ScriptingInterface::triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand) const {
|
||||
return DependencyManager::get<UserInputMapper>()->triggerHapticPulseOnDevice(device, strength, duration, hand);
|
||||
}
|
||||
|
|
|
@ -99,6 +99,13 @@ namespace controller {
|
|||
Q_INVOKABLE const QVariantMap& getHardware() { return _hardware; }
|
||||
Q_INVOKABLE const QVariantMap& getActions() { return _actions; }
|
||||
Q_INVOKABLE const QVariantMap& getStandard() { return _standard; }
|
||||
Q_INVOKABLE void startInputRecording();
|
||||
Q_INVOKABLE void stopInputRecording();
|
||||
Q_INVOKABLE void startInputPlayback();
|
||||
Q_INVOKABLE void stopInputPlayback();
|
||||
Q_INVOKABLE void saveInputRecording();
|
||||
Q_INVOKABLE void loadInputRecording(const QString& file);
|
||||
Q_INVOKABLE QString getInputRecorderSaveDirectory();
|
||||
|
||||
bool isMouseCaptured() const { return _mouseCaptured; }
|
||||
bool isTouchCaptured() const { return _touchCaptured; }
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include "StandardController.h"
|
||||
#include "StateController.h"
|
||||
|
||||
#include "InputRecorder.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include "impl/conditionals/AndConditional.h"
|
||||
|
@ -245,10 +245,11 @@ void fixBisectedAxis(float& full, float& negative, float& positive) {
|
|||
|
||||
void UserInputMapper::update(float deltaTime) {
|
||||
Locker locker(_lock);
|
||||
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
static uint64_t updateCount = 0;
|
||||
++updateCount;
|
||||
|
||||
inputRecorder->resetFrame();
|
||||
// Reset the axis state for next loop
|
||||
for (auto& channel : _actionStates) {
|
||||
channel = 0.0f;
|
||||
|
@ -300,6 +301,7 @@ void UserInputMapper::update(float deltaTime) {
|
|||
emit inputEvent(input.id, value);
|
||||
}
|
||||
}
|
||||
inputRecorder->frameTick();
|
||||
}
|
||||
|
||||
Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
|
||||
|
|
|
@ -11,19 +11,32 @@
|
|||
#include <DependencyManager.h>
|
||||
|
||||
#include "../../UserInputMapper.h"
|
||||
#include "../../InputRecorder.h"
|
||||
|
||||
using namespace controller;
|
||||
|
||||
void ActionEndpoint::apply(float newValue, const Pointer& source) {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
if(inputRecorder->isPlayingback()) {
|
||||
newValue = inputRecorder->getActionState(Action(_input.getChannel()));
|
||||
}
|
||||
|
||||
_currentValue += newValue;
|
||||
if (_input != Input::INVALID_INPUT) {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
|
||||
}
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), newValue);
|
||||
}
|
||||
|
||||
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
|
||||
_currentPose = value;
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), _currentPose);
|
||||
if (inputRecorder->isPlayingback()) {
|
||||
_currentPose = inputRecorder->getPoseState(Action(_input.getChannel()));
|
||||
}
|
||||
|
||||
if (!_currentPose.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -354,12 +354,11 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
}
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
|
||||
cursorData.texture.reset(
|
||||
gpu::Texture::createStrict(
|
||||
cursorData.texture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
cursorData.texture->setSource("cursor texture");
|
||||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
cursorData.texture->setUsage(usage.build());
|
||||
|
|
|
@ -295,12 +295,11 @@ void HmdDisplayPlugin::internalPresent() {
|
|||
image = image.mirrored();
|
||||
image = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
if (!_previewTexture) {
|
||||
_previewTexture.reset(
|
||||
gpu::Texture::createStrict(
|
||||
_previewTexture = gpu::Texture::createStrict(
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
|
||||
image.width(), image.height(),
|
||||
gpu::Texture::MAX_NUM_MIPS,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
_previewTexture->setSource("HMD Preview Texture");
|
||||
_previewTexture->setUsage(gpu::Texture::Usage::Builder().withColor().build());
|
||||
_previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
|
||||
|
@ -592,7 +591,7 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() {
|
|||
auto ps = gpu::Shader::createPixel(fsSource.toLocal8Bit().toStdString());
|
||||
auto program = gpu::Shader::createProgram(vs, ps);
|
||||
gpu::gl::GLBackend::makeProgram(*program, gpu::Shader::BindingSet());
|
||||
this->uniformsLocation = program->getBuffers().findLocation("overlayBuffer");
|
||||
this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer");
|
||||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setDepthTest(gpu::State::DepthTest(false));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
set(TARGET_NAME entities-renderer)
|
||||
AUTOSCRIBE_SHADER_LIB(gpu model procedural render render-utils)
|
||||
setup_hifi_library(Widgets Network Script)
|
||||
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils)
|
||||
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils image)
|
||||
|
||||
target_bullet()
|
||||
|
||||
|
|
|
@ -495,7 +495,7 @@ bool EntityTreeRenderer::applySkyboxAndHasAmbient() {
|
|||
|
||||
bool isAmbientSet = false;
|
||||
if (_pendingAmbientTexture && !_ambientTexture) {
|
||||
_ambientTexture = textureCache->getTexture(_ambientTextureURL, NetworkTexture::CUBE_TEXTURE);
|
||||
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
|
||||
}
|
||||
if (_ambientTexture && _ambientTexture->isLoaded()) {
|
||||
_pendingAmbientTexture = false;
|
||||
|
@ -512,7 +512,7 @@ bool EntityTreeRenderer::applySkyboxAndHasAmbient() {
|
|||
|
||||
if (_pendingSkyboxTexture &&
|
||||
(!_skyboxTexture || (_skyboxTexture->getURL() != _skyboxTextureURL))) {
|
||||
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, NetworkTexture::CUBE_TEXTURE);
|
||||
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
|
||||
}
|
||||
if (_skyboxTexture && _skyboxTexture->isLoaded()) {
|
||||
_pendingSkyboxTexture = false;
|
||||
|
@ -1015,11 +1015,11 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
|||
}
|
||||
|
||||
|
||||
void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, bool reload) {
|
||||
checkAndCallPreload(entityID, reload, true);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload, const bool unloadFirst) {
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool reload, bool unloadFirst) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
|
@ -1027,11 +1027,11 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const
|
|||
}
|
||||
bool shouldLoad = entity->shouldPreloadScript() && _entitiesScriptEngine;
|
||||
QString scriptUrl = entity->getScript();
|
||||
if (shouldLoad && (unloadFirst || scriptUrl.isEmpty())) {
|
||||
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
entity->scriptHasUnloaded();
|
||||
}
|
||||
if (shouldLoad && !scriptUrl.isEmpty()) {
|
||||
if (shouldLoad) {
|
||||
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
|
||||
_entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload);
|
||||
entity->scriptHasPreloaded();
|
||||
|
|
|
@ -152,7 +152,7 @@ private:
|
|||
bool applySkyboxAndHasAmbient();
|
||||
bool applyLayeredZones();
|
||||
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false, const bool unloadFirst = false);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false);
|
||||
|
||||
QList<ModelPointer> _releasedModels;
|
||||
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
|
||||
|
|
|
@ -216,7 +216,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
|
|
|
@ -1,332 +0,0 @@
|
|||
//
|
||||
// EntityActionInterface.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-4
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
+-----------------------+ +-------------------+ +------------------------------+
|
||||
| | | | | |
|
||||
| EntityActionInterface | | btActionInterface | | EntityActionFactoryInterface |
|
||||
| (entities) | | (bullet) | | (entities) |
|
||||
+-----------------------+ +-------------------+ +------------------------------+
|
||||
| | | | |
|
||||
+----+ +--+ +----------+ | |
|
||||
| | | | |
|
||||
+-------------------+ +--------------+ +------------------------+ +-------------------------+
|
||||
| | | | | | | |
|
||||
| AssignmentAction | | ObjectAction | | InterfaceActionFactory | | AssignmentActionFactory |
|
||||
|(assignment client)| | (physics) | | (interface) | | (assignment client) |
|
||||
+-------------------+ +--------------+ +------------------------+ +-------------------------+
|
||||
|
|
||||
|
|
||||
|
|
||||
+--------------------+
|
||||
| |
|
||||
| ObjectActionSpring |
|
||||
| (physics) |
|
||||
+--------------------+
|
||||
|
||||
|
||||
|
||||
|
||||
An action is a callback which is registered with bullet. An action is called-back every physics
|
||||
simulation step and can do whatever it wants with the various datastructures it has available. An
|
||||
action, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
|
||||
point by a spring.
|
||||
|
||||
In this system, an action is a property of an EntityItem (rather, an EntityItem has a property which
|
||||
encodes a list of actions). Each action has a type and some arguments. Actions can be created by a
|
||||
script or when receiving information via an EntityTree data-stream (either over the network or from an
|
||||
svo file).
|
||||
|
||||
In the interface, if an EntityItem has actions, this EntityItem will have pointers to ObjectAction
|
||||
subclass (like ObjectActionSpring) instantiations. Code in the entities library affects an action-object
|
||||
via the EntityActionInterface (which knows nothing about bullet). When the ObjectAction subclass
|
||||
instance is created, it is registered as an action with bullet. Bullet will call into code in this
|
||||
instance with the btActionInterface every physics-simulation step.
|
||||
|
||||
Because the action can exist next to the interface's EntityTree or the entity-server's EntityTree,
|
||||
parallel versions of the factories and actions are needed.
|
||||
|
||||
In an entity-server, any type of action is instantiated as an AssignmentAction. This action isn't called
|
||||
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
|
||||
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
|
||||
right now the AssignmentAction class is a place-holder.
|
||||
|
||||
The action-objects are instantiated by singleton (dependecy) subclasses of EntityActionFactoryInterface.
|
||||
In the interface, the subclass is an InterfaceActionFactory and it will produce things like
|
||||
ObjectActionSpring. In an entity-server the subclass is an AssignmentActionFactory and it always
|
||||
produces AssignmentActions.
|
||||
|
||||
Depending on the action's type, it will have various arguments. When a script changes an argument of an
|
||||
action, the argument-holding member-variables of ObjectActionSpring (in this example) are updated and
|
||||
also serialized into _actionData in the EntityItem. Each subclass of ObjectAction knows how to serialize
|
||||
and deserialize its own arguments. _actionData is what gets sent over the wire or saved in an svo file.
|
||||
When a packet-reader receives data for _actionData, it will save it in the EntityItem; this causes the
|
||||
deserializer in the ObjectAction subclass to be called with the new data, thereby updating its argument
|
||||
variables. These argument variables are used by the code which is run when bullet does a callback.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
|
||||
|
||||
EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeString) {
|
||||
QString normalizedActionTypeString = actionTypeString.toLower().remove('-').remove('_');
|
||||
if (normalizedActionTypeString == "none") {
|
||||
return ACTION_TYPE_NONE;
|
||||
}
|
||||
if (normalizedActionTypeString == "offset") {
|
||||
return ACTION_TYPE_OFFSET;
|
||||
}
|
||||
if (normalizedActionTypeString == "spring") {
|
||||
return ACTION_TYPE_SPRING;
|
||||
}
|
||||
if (normalizedActionTypeString == "hold") {
|
||||
return ACTION_TYPE_HOLD;
|
||||
}
|
||||
if (normalizedActionTypeString == "traveloriented") {
|
||||
return ACTION_TYPE_TRAVEL_ORIENTED;
|
||||
}
|
||||
|
||||
qCDebug(entities) << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString;
|
||||
return ACTION_TYPE_NONE;
|
||||
}
|
||||
|
||||
QString EntityActionInterface::actionTypeToString(EntityActionType actionType) {
|
||||
switch(actionType) {
|
||||
case ACTION_TYPE_NONE:
|
||||
return "none";
|
||||
case ACTION_TYPE_OFFSET:
|
||||
return "offset";
|
||||
case ACTION_TYPE_SPRING:
|
||||
return "spring";
|
||||
case ACTION_TYPE_HOLD:
|
||||
return "hold";
|
||||
case ACTION_TYPE_TRAVEL_ORIENTED:
|
||||
return "travel-oriented";
|
||||
}
|
||||
assert(false);
|
||||
return "none";
|
||||
}
|
||||
|
||||
glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariant resultV = arguments[argumentName];
|
||||
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariantMap resultVM = resultV.toMap();
|
||||
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariant xV = resultVM["x"];
|
||||
QVariant yV = resultVM["y"];
|
||||
QVariant zV = resultVM["z"];
|
||||
|
||||
bool xOk = true;
|
||||
bool yOk = true;
|
||||
bool zOk = true;
|
||||
float x = xV.toFloat(&xOk);
|
||||
float y = yV.toFloat(&yOk);
|
||||
float z = zV.toFloat(&zOk);
|
||||
if (!xOk || !yOk || !zOk) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
if (x != x || y != y || z != z) {
|
||||
// at least one of the values is NaN
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
return glm::vec3(x, y, z);
|
||||
}
|
||||
|
||||
glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariant resultV = arguments[argumentName];
|
||||
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName();
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariantMap resultVM = resultV.toMap();
|
||||
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariant xV = resultVM["x"];
|
||||
QVariant yV = resultVM["y"];
|
||||
QVariant zV = resultVM["z"];
|
||||
QVariant wV = resultVM["w"];
|
||||
|
||||
bool xOk = true;
|
||||
bool yOk = true;
|
||||
bool zOk = true;
|
||||
bool wOk = true;
|
||||
float x = xV.toFloat(&xOk);
|
||||
float y = yV.toFloat(&yOk);
|
||||
float z = zV.toFloat(&zOk);
|
||||
float w = wV.toFloat(&wOk);
|
||||
if (!xOk || !yOk || !zOk || !wOk) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName
|
||||
<< "must be a map with keys: x, y, z, and w of type float.";
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
if (x != x || y != y || z != z || w != w) {
|
||||
// at least one of the components is NaN!
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
return glm::normalize(glm::quat(w, x, y, z));
|
||||
}
|
||||
|
||||
float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QVariant variant = arguments[argumentName];
|
||||
bool variantOk = true;
|
||||
float value = variant.toFloat(&variantOk);
|
||||
|
||||
if (!variantOk || std::isnan(value)) {
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int EntityActionInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QVariant variant = arguments[argumentName];
|
||||
bool variantOk = true;
|
||||
int value = variant.toInt(&variantOk);
|
||||
|
||||
if (!variantOk) {
|
||||
ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
QString EntityActionInterface::extractStringArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return "";
|
||||
}
|
||||
return arguments[argumentName].toString();
|
||||
}
|
||||
|
||||
bool EntityActionInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
return arguments[argumentName].toBool();
|
||||
}
|
||||
|
||||
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType)
|
||||
{
|
||||
return stream << (quint16)entityActionType;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType)
|
||||
{
|
||||
quint16 actionTypeAsInt;
|
||||
stream >> actionTypeAsInt;
|
||||
entityActionType = (EntityActionType)actionTypeAsInt;
|
||||
return stream;
|
||||
}
|
||||
|
||||
QString serializedActionsToDebugString(QByteArray data) {
|
||||
if (data.size() == 0) {
|
||||
return QString();
|
||||
}
|
||||
QVector<QByteArray> serializedActions;
|
||||
QDataStream serializedActionsStream(data);
|
||||
serializedActionsStream >> serializedActions;
|
||||
|
||||
QString result;
|
||||
foreach(QByteArray serializedAction, serializedActions) {
|
||||
QDataStream serializedActionStream(serializedAction);
|
||||
EntityActionType actionType;
|
||||
QUuid actionID;
|
||||
serializedActionStream >> actionType;
|
||||
serializedActionStream >> actionID;
|
||||
result += EntityActionInterface::actionTypeToString(actionType) + "-" + actionID.toString() + " ";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// EntityActionFactoryInterface.cpp
|
||||
// EntityDynamicFactoryInterface.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-2
|
||||
|
@ -9,26 +9,26 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityActionFactoryInterface_h
|
||||
#define hifi_EntityActionFactoryInterface_h
|
||||
#ifndef hifi_EntityDynamicFactoryInterface_h
|
||||
#define hifi_EntityDynamicFactoryInterface_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
class EntityActionFactoryInterface : public QObject, public Dependency {
|
||||
class EntityDynamicFactoryInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
EntityActionFactoryInterface() { }
|
||||
virtual ~EntityActionFactoryInterface() { }
|
||||
virtual EntityActionPointer factory(EntityActionType type,
|
||||
EntityDynamicFactoryInterface() { }
|
||||
virtual ~EntityDynamicFactoryInterface() { }
|
||||
virtual EntityDynamicPointer factory(EntityDynamicType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments) { assert(false); return nullptr; }
|
||||
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity,
|
||||
virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity,
|
||||
QByteArray data) { assert(false); return nullptr; }
|
||||
};
|
||||
|
||||
#endif // hifi_EntityActionFactoryInterface_h
|
||||
#endif // hifi_EntityDynamicFactoryInterface_h
|
347
libraries/entities/src/EntityDynamicInterface.cpp
Normal file
347
libraries/entities/src/EntityDynamicInterface.cpp
Normal file
|
@ -0,0 +1,347 @@
|
|||
//
|
||||
// EntityDynamicInterface.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-4
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
+-------------------------+ +--------------------------------+
|
||||
| | | |
|
||||
| EntityDynamicsInterface | | EntityDynamicsFactoryInterface |
|
||||
| (entities) | | (entities) |
|
||||
+-------------------------+ +--------------------------------+
|
||||
| | | |
|
||||
+----+ +--+ | |
|
||||
| | | |
|
||||
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
|
||||
| | | | | | | |
|
||||
| AssignmentDynamics | | ObjectDynamics | | InterfaceDynamicsFactory | | AssignmentDynamicsFactory |
|
||||
|(assignment client) | | (physics) | | (interface) | | (assignment client) |
|
||||
+---------------------+ +----------------+ +--------------------------+ +---------------------------+
|
||||
| |
|
||||
| |
|
||||
+---------------------+ | |
|
||||
| | | |
|
||||
| btActionInterface | | |
|
||||
| (bullet) | | |
|
||||
+---------------------+ | |
|
||||
| | |
|
||||
+--------------+ +------------------+ +-------------------+
|
||||
| | | | | |
|
||||
| ObjectAction | | ObjectConstraint | | btTypedConstraint |
|
||||
| (physics) | | (physics) --|--->| (bullet) |
|
||||
+--------------+ +------------------+ +-------------------+
|
||||
| |
|
||||
| |
|
||||
+--------------------+ +-----------------------+
|
||||
| | | |
|
||||
| ObjectActionSpring | | ObjectConstraintHinge |
|
||||
| (physics) | | (physics) |
|
||||
+--------------------+ +-----------------------+
|
||||
|
||||
|
||||
|
||||
An dynamic is a callback which is registered with bullet. An dynamic is called-back every physics
|
||||
simulation step and can do whatever it wants with the various datastructures it has available. An
|
||||
dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
|
||||
point by a spring.
|
||||
|
||||
In this system, an dynamic is a property of an EntityItem (rather, an EntityItem has a property which
|
||||
encodes a list of dynamics). Each dynamic has a type and some arguments. Dynamics can be created by a
|
||||
script or when receiving information via an EntityTree data-stream (either over the network or from an
|
||||
svo file).
|
||||
|
||||
In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic
|
||||
subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects an dynamic-object
|
||||
via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass
|
||||
instance is created, it is registered as an dynamic with bullet. Bullet will call into code in this
|
||||
instance with the btDynamicInterface every physics-simulation step.
|
||||
|
||||
Because the dynamic can exist next to the interface's EntityTree or the entity-server's EntityTree,
|
||||
parallel versions of the factories and dynamics are needed.
|
||||
|
||||
In an entity-server, any type of dynamic is instantiated as an AssignmentDynamic. This dynamic isn't called
|
||||
by bullet (which isn't part of an assignment-client). It does nothing but remember its type and its
|
||||
arguments. This may change as we try to make the entity-server's simple physics simulation better, but
|
||||
right now the AssignmentDynamic class is a place-holder.
|
||||
|
||||
The dynamic-objects are instantiated by singleton (dependecy) subclasses of EntityDynamicFactoryInterface.
|
||||
In the interface, the subclass is an InterfaceDynamicFactory and it will produce things like
|
||||
ObjectDynamicSpring. In an entity-server the subclass is an AssignmentDynamicFactory and it always
|
||||
produces AssignmentDynamics.
|
||||
|
||||
Depending on the dynamic's type, it will have various arguments. When a script changes an argument of an
|
||||
dynamic, the argument-holding member-variables of ObjectDynamicSpring (in this example) are updated and
|
||||
also serialized into _dynamicData in the EntityItem. Each subclass of ObjectDynamic knows how to serialize
|
||||
and deserialize its own arguments. _dynamicData is what gets sent over the wire or saved in an svo file.
|
||||
When a packet-reader receives data for _dynamicData, it will save it in the EntityItem; this causes the
|
||||
deserializer in the ObjectDynamic subclass to be called with the new data, thereby updating its argument
|
||||
variables. These argument variables are used by the code which is run when bullet does a callback.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
|
||||
EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) {
|
||||
QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_');
|
||||
if (normalizedDynamicTypeString == "none") {
|
||||
return DYNAMIC_TYPE_NONE;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "offset") {
|
||||
return DYNAMIC_TYPE_OFFSET;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "spring") {
|
||||
return DYNAMIC_TYPE_SPRING;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "hold") {
|
||||
return DYNAMIC_TYPE_HOLD;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "traveloriented") {
|
||||
return DYNAMIC_TYPE_TRAVEL_ORIENTED;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "hinge") {
|
||||
return DYNAMIC_TYPE_HINGE;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "fargrab") {
|
||||
return DYNAMIC_TYPE_FAR_GRAB;
|
||||
}
|
||||
|
||||
qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name"
|
||||
<< dynamicTypeString;
|
||||
return DYNAMIC_TYPE_NONE;
|
||||
}
|
||||
|
||||
QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicType) {
|
||||
switch(dynamicType) {
|
||||
case DYNAMIC_TYPE_NONE:
|
||||
return "none";
|
||||
case DYNAMIC_TYPE_OFFSET:
|
||||
return "offset";
|
||||
case DYNAMIC_TYPE_SPRING:
|
||||
return "spring";
|
||||
case DYNAMIC_TYPE_HOLD:
|
||||
return "hold";
|
||||
case DYNAMIC_TYPE_TRAVEL_ORIENTED:
|
||||
return "travel-oriented";
|
||||
case DYNAMIC_TYPE_HINGE:
|
||||
return "hinge";
|
||||
case DYNAMIC_TYPE_FAR_GRAB:
|
||||
return "far-grab";
|
||||
}
|
||||
assert(false);
|
||||
return "none";
|
||||
}
|
||||
|
||||
glm::vec3 EntityDynamicInterface::extractVec3Argument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariant resultV = arguments[argumentName];
|
||||
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariantMap resultVM = resultV.toMap();
|
||||
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
QVariant xV = resultVM["x"];
|
||||
QVariant yV = resultVM["y"];
|
||||
QVariant zV = resultVM["z"];
|
||||
|
||||
bool xOk = true;
|
||||
bool yOk = true;
|
||||
bool zOk = true;
|
||||
float x = xV.toFloat(&xOk);
|
||||
float y = yV.toFloat(&yOk);
|
||||
float z = zV.toFloat(&zOk);
|
||||
if (!xOk || !yOk || !zOk) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, and z of type float.";
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
if (x != x || y != y || z != z) {
|
||||
// at least one of the values is NaN
|
||||
ok = false;
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
return glm::vec3(x, y, z);
|
||||
}
|
||||
|
||||
glm::quat EntityDynamicInterface::extractQuatArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariant resultV = arguments[argumentName];
|
||||
if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName();
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariantMap resultVM = resultV.toMap();
|
||||
if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z") || !resultVM.contains("w")) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName << "must be a map with keys: x, y, z, and w";
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
QVariant xV = resultVM["x"];
|
||||
QVariant yV = resultVM["y"];
|
||||
QVariant zV = resultVM["z"];
|
||||
QVariant wV = resultVM["w"];
|
||||
|
||||
bool xOk = true;
|
||||
bool yOk = true;
|
||||
bool zOk = true;
|
||||
bool wOk = true;
|
||||
float x = xV.toFloat(&xOk);
|
||||
float y = yV.toFloat(&yOk);
|
||||
float z = zV.toFloat(&zOk);
|
||||
float w = wV.toFloat(&wOk);
|
||||
if (!xOk || !yOk || !zOk || !wOk) {
|
||||
qCDebug(entities) << objectName << "argument" << argumentName
|
||||
<< "must be a map with keys: x, y, z, and w of type float.";
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
if (x != x || y != y || z != z || w != w) {
|
||||
// at least one of the components is NaN!
|
||||
ok = false;
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
return glm::normalize(glm::quat(w, x, y, z));
|
||||
}
|
||||
|
||||
float EntityDynamicInterface::extractFloatArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QVariant variant = arguments[argumentName];
|
||||
bool variantOk = true;
|
||||
float value = variant.toFloat(&variantOk);
|
||||
|
||||
if (!variantOk || std::isnan(value)) {
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int EntityDynamicInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QVariant variant = arguments[argumentName];
|
||||
bool variantOk = true;
|
||||
int value = variant.toInt(&variantOk);
|
||||
|
||||
if (!variantOk) {
|
||||
ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
QString EntityDynamicInterface::extractStringArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return "";
|
||||
}
|
||||
return arguments[argumentName].toString();
|
||||
}
|
||||
|
||||
bool EntityDynamicInterface::extractBooleanArgument(QString objectName, QVariantMap arguments,
|
||||
QString argumentName, bool& ok, bool required) {
|
||||
if (!arguments.contains(argumentName)) {
|
||||
if (required) {
|
||||
qCDebug(entities) << objectName << "requires argument:" << argumentName;
|
||||
}
|
||||
ok = false;
|
||||
return false;
|
||||
}
|
||||
return arguments[argumentName].toBool();
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityDynamicType& entityDynamicType) {
|
||||
return stream << (quint16)entityDynamicType;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& stream, EntityDynamicType& entityDynamicType) {
|
||||
quint16 dynamicTypeAsInt;
|
||||
stream >> dynamicTypeAsInt;
|
||||
entityDynamicType = (EntityDynamicType)dynamicTypeAsInt;
|
||||
return stream;
|
||||
}
|
||||
|
||||
QString serializedDynamicsToDebugString(QByteArray data) {
|
||||
if (data.size() == 0) {
|
||||
return QString();
|
||||
}
|
||||
QVector<QByteArray> serializedDynamics;
|
||||
QDataStream serializedDynamicsStream(data);
|
||||
serializedDynamicsStream >> serializedDynamics;
|
||||
|
||||
QString result;
|
||||
foreach(QByteArray serializedDynamic, serializedDynamics) {
|
||||
QDataStream serializedDynamicStream(serializedDynamic);
|
||||
EntityDynamicType dynamicType;
|
||||
QUuid dynamicID;
|
||||
serializedDynamicStream >> dynamicType;
|
||||
serializedDynamicStream >> dynamicID;
|
||||
result += EntityDynamicInterface::dynamicTypeToString(dynamicType) + "-" + dynamicID.toString() + " ";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// EntityActionInterface.h
|
||||
// EntityDynamicInterface.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Seth Alves on 2015-6-2
|
||||
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityActionInterface_h
|
||||
#define hifi_EntityActionInterface_h
|
||||
#ifndef hifi_EntityDynamicInterface_h
|
||||
#define hifi_EntityDynamicInterface_h
|
||||
|
||||
#include <memory>
|
||||
#include <QUuid>
|
||||
|
@ -23,22 +23,27 @@ using EntityItemWeakPointer = std::weak_ptr<EntityItem>;
|
|||
class EntitySimulation;
|
||||
using EntitySimulationPointer = std::shared_ptr<EntitySimulation>;
|
||||
|
||||
enum EntityActionType {
|
||||
// keep these synchronized with actionTypeFromString and actionTypeToString
|
||||
ACTION_TYPE_NONE = 0,
|
||||
ACTION_TYPE_OFFSET = 1000,
|
||||
ACTION_TYPE_SPRING = 2000,
|
||||
ACTION_TYPE_HOLD = 3000,
|
||||
ACTION_TYPE_TRAVEL_ORIENTED = 4000
|
||||
enum EntityDynamicType {
|
||||
// keep these synchronized with dynamicTypeFromString and dynamicTypeToString
|
||||
DYNAMIC_TYPE_NONE = 0,
|
||||
DYNAMIC_TYPE_OFFSET = 1000,
|
||||
DYNAMIC_TYPE_SPRING = 2000,
|
||||
DYNAMIC_TYPE_HOLD = 3000,
|
||||
DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000,
|
||||
DYNAMIC_TYPE_HINGE = 5000,
|
||||
DYNAMIC_TYPE_FAR_GRAB = 6000
|
||||
};
|
||||
|
||||
|
||||
class EntityActionInterface {
|
||||
class EntityDynamicInterface {
|
||||
public:
|
||||
EntityActionInterface(EntityActionType type, const QUuid& id) : _id(id), _type(type) { }
|
||||
virtual ~EntityActionInterface() { }
|
||||
EntityDynamicInterface(EntityDynamicType type, const QUuid& id) : _id(id), _type(type) { }
|
||||
virtual ~EntityDynamicInterface() { }
|
||||
const QUuid& getID() const { return _id; }
|
||||
EntityActionType getType() const { return _type; }
|
||||
EntityDynamicType getType() const { return _type; }
|
||||
virtual bool isAction() const { return false; }
|
||||
virtual bool isConstraint() const { return false; }
|
||||
virtual bool isReadyForAdd() const { return true; }
|
||||
|
||||
bool isActive() { return _active; }
|
||||
|
||||
|
@ -51,8 +56,8 @@ public:
|
|||
virtual QByteArray serialize() const = 0;
|
||||
virtual void deserialize(QByteArray serializedArguments) = 0;
|
||||
|
||||
static EntityActionType actionTypeFromString(QString actionTypeString);
|
||||
static QString actionTypeToString(EntityActionType actionType);
|
||||
static EntityDynamicType dynamicTypeFromString(QString dynamicTypeString);
|
||||
static QString dynamicTypeToString(EntityDynamicType dynamicType);
|
||||
|
||||
virtual bool lifetimeIsOver() { return false; }
|
||||
virtual quint64 getExpires() { return 0; }
|
||||
|
@ -82,26 +87,26 @@ public:
|
|||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() = 0;
|
||||
virtual void setPosition(glm::vec3 position) = 0;
|
||||
// virtual void setPosition(glm::vec3 position) = 0;
|
||||
virtual glm::quat getRotation() = 0;
|
||||
virtual void setRotation(glm::quat rotation) = 0;
|
||||
// virtual void setRotation(glm::quat rotation) = 0;
|
||||
virtual glm::vec3 getLinearVelocity() = 0;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) = 0;
|
||||
virtual glm::vec3 getAngularVelocity() = 0;
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0;
|
||||
|
||||
QUuid _id;
|
||||
EntityActionType _type;
|
||||
EntityDynamicType _type;
|
||||
bool _active { false };
|
||||
bool _isMine { false }; // did this interface create / edit this action?
|
||||
bool _isMine { false }; // did this interface create / edit this dynamic?
|
||||
};
|
||||
|
||||
|
||||
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
|
||||
typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer;
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityActionType& entityActionType);
|
||||
QDataStream& operator>>(QDataStream& stream, EntityActionType& entityActionType);
|
||||
QDataStream& operator<<(QDataStream& stream, const EntityDynamicType& entityDynamicType);
|
||||
QDataStream& operator>>(QDataStream& stream, EntityDynamicType& entityDynamicType);
|
||||
|
||||
QString serializedActionsToDebugString(QByteArray data);
|
||||
QString serializedDynamicsToDebugString(QByteArray data);
|
||||
|
||||
#endif // hifi_EntityActionInterface_h
|
||||
#endif // hifi_EntityDynamicInterface_h
|
|
@ -30,7 +30,7 @@
|
|||
#include "EntitiesLogging.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntitySimulation.h"
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "EntityDynamicFactoryInterface.h"
|
||||
|
||||
|
||||
int EntityItem::_maxActionsDataSize = 800;
|
||||
|
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getDynamicData());
|
||||
|
||||
// convert AVATAR_SELF_ID to actual sessionUUID.
|
||||
QUuid actualParentID = getParentID();
|
||||
|
@ -821,7 +821,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
|
||||
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
|
||||
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
|
||||
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData);
|
||||
READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setDynamicData);
|
||||
|
||||
{ // parentID and parentJointIndex are also protected by simulation ownership
|
||||
bool oldOverwrite = overwriteLocalData;
|
||||
|
@ -1251,7 +1251,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getDynamicData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube);
|
||||
|
@ -1358,7 +1358,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setDynamicData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, updateParentID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
|
||||
|
@ -1872,10 +1872,20 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
|||
iAmHoldingThis = true;
|
||||
}
|
||||
// also, don't bootstrap our own avatar with a hold action
|
||||
QList<EntityActionPointer> holdActions = getActionsOfType(ACTION_TYPE_HOLD);
|
||||
QList<EntityActionPointer>::const_iterator i = holdActions.begin();
|
||||
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
|
||||
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
|
||||
while (i != holdActions.end()) {
|
||||
EntityActionPointer action = *i;
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
iAmHoldingThis = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
|
||||
i = farGrabActions.begin();
|
||||
while (i != farGrabActions.end()) {
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
iAmHoldingThis = true;
|
||||
break;
|
||||
|
@ -1941,18 +1951,18 @@ void EntityItem::rememberHasSimulationOwnershipBid() const {
|
|||
QString EntityItem::actionsToDebugString() {
|
||||
QString result;
|
||||
QVector<QByteArray> serializedActions;
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
EntityActionPointer action = _objectActions[id];
|
||||
EntityActionType actionType = action->getType();
|
||||
EntityDynamicPointer action = _objectActions[id];
|
||||
EntityDynamicType actionType = action->getType();
|
||||
result += QString("") + actionType + ":" + action->getID().toString() + " ";
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPointer action) {
|
||||
bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPointer action) {
|
||||
bool result;
|
||||
withWriteLock([&] {
|
||||
checkWaitingToRemove(simulation);
|
||||
|
@ -1960,7 +1970,7 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPoint
|
|||
result = addActionInternal(simulation, action);
|
||||
if (result) {
|
||||
action->setIsMine(true);
|
||||
_actionDataDirty = true;
|
||||
_dynamicDataDirty = true;
|
||||
} else {
|
||||
removeActionInternal(action->getID());
|
||||
}
|
||||
|
@ -1969,7 +1979,7 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPoint
|
|||
return result;
|
||||
}
|
||||
|
||||
bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action) {
|
||||
bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action) {
|
||||
assert(action);
|
||||
assert(simulation);
|
||||
auto actionOwnerEntity = action->getOwnerEntity().lock();
|
||||
|
@ -1979,7 +1989,7 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityAct
|
|||
const QUuid& actionID = action->getID();
|
||||
assert(!_objectActions.contains(actionID) || _objectActions[actionID] == action);
|
||||
_objectActions[actionID] = action;
|
||||
simulation->addAction(action);
|
||||
simulation->addDynamic(action);
|
||||
|
||||
bool success;
|
||||
QByteArray newDataCache;
|
||||
|
@ -2003,14 +2013,13 @@ bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& a
|
|||
return;
|
||||
}
|
||||
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
EntityDynamicPointer action = _objectActions[actionID];
|
||||
|
||||
success = action->updateArguments(arguments);
|
||||
if (success) {
|
||||
action->setIsMine(true);
|
||||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
} else {
|
||||
qCDebug(entities) << "EntityItem::updateAction failed";
|
||||
}
|
||||
|
@ -2035,7 +2044,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
}
|
||||
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
EntityDynamicPointer action = _objectActions[actionID];
|
||||
|
||||
action->setOwnerEntity(nullptr);
|
||||
action->setIsMine(false);
|
||||
|
@ -2049,7 +2058,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
setActionDataNeedsTransmit(true);
|
||||
setDynamicDataNeedsTransmit(true);
|
||||
return success;
|
||||
}
|
||||
return false;
|
||||
|
@ -2057,10 +2066,10 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
|
||||
bool EntityItem::clearActions(EntitySimulationPointer simulation) {
|
||||
withWriteLock([&] {
|
||||
QHash<QUuid, EntityActionPointer>::iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
EntityActionPointer action = _objectActions[id];
|
||||
EntityDynamicPointer action = _objectActions[id];
|
||||
i = _objectActions.erase(i);
|
||||
action->setOwnerEntity(nullptr);
|
||||
action->removeFromSimulation(simulation);
|
||||
|
@ -2101,12 +2110,12 @@ void EntityItem::deserializeActionsInternal() {
|
|||
serializedActionsStream >> serializedActions;
|
||||
}
|
||||
|
||||
// Keep track of which actions got added or updated by the new actionData
|
||||
// Keep track of which actions got added or updated by the new dynamicData
|
||||
QSet<QUuid> updated;
|
||||
|
||||
foreach(QByteArray serializedAction, serializedActions) {
|
||||
QDataStream serializedActionStream(serializedAction);
|
||||
EntityActionType actionType;
|
||||
EntityDynamicType actionType;
|
||||
QUuid actionID;
|
||||
serializedActionStream >> actionType;
|
||||
serializedActionStream >> actionID;
|
||||
|
@ -2115,7 +2124,7 @@ void EntityItem::deserializeActionsInternal() {
|
|||
}
|
||||
|
||||
if (_objectActions.contains(actionID)) {
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
EntityDynamicPointer action = _objectActions[actionID];
|
||||
// TODO: make sure types match? there isn't currently a way to
|
||||
// change the type of an existing action.
|
||||
if (!action->isMine()) {
|
||||
|
@ -2123,9 +2132,9 @@ void EntityItem::deserializeActionsInternal() {
|
|||
}
|
||||
updated << actionID;
|
||||
} else {
|
||||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
|
||||
EntityItemPointer entity = getThisPointer();
|
||||
EntityActionPointer action = actionFactory->factoryBA(entity, serializedAction);
|
||||
EntityDynamicPointer action = actionFactory->factoryBA(entity, serializedAction);
|
||||
if (action) {
|
||||
entity->addActionInternal(simulation, action);
|
||||
updated << actionID;
|
||||
|
@ -2140,15 +2149,15 @@ void EntityItem::deserializeActionsInternal() {
|
|||
}
|
||||
|
||||
// remove any actions that weren't included in the new data.
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
QUuid id = i.key();
|
||||
if (!updated.contains(id)) {
|
||||
EntityActionPointer action = i.value();
|
||||
EntityDynamicPointer action = i.value();
|
||||
|
||||
if (action->isMine()) {
|
||||
// we just received an update that didn't include one of our actions. tell the server about it (again).
|
||||
setActionDataNeedsTransmit(true);
|
||||
setDynamicDataNeedsTransmit(true);
|
||||
} else {
|
||||
// don't let someone else delete my action.
|
||||
_actionsToRemove << id;
|
||||
|
@ -2167,7 +2176,7 @@ void EntityItem::deserializeActionsInternal() {
|
|||
}
|
||||
}
|
||||
|
||||
_actionDataDirty = true;
|
||||
_dynamicDataDirty = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -2179,15 +2188,15 @@ void EntityItem::checkWaitingToRemove(EntitySimulationPointer simulation) {
|
|||
_actionsToRemove.clear();
|
||||
}
|
||||
|
||||
void EntityItem::setActionData(QByteArray actionData) {
|
||||
void EntityItem::setDynamicData(QByteArray dynamicData) {
|
||||
withWriteLock([&] {
|
||||
setActionDataInternal(actionData);
|
||||
setDynamicDataInternal(dynamicData);
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::setActionDataInternal(QByteArray actionData) {
|
||||
if (_allActionsDataCache != actionData) {
|
||||
_allActionsDataCache = actionData;
|
||||
void EntityItem::setDynamicDataInternal(QByteArray dynamicData) {
|
||||
if (_allActionsDataCache != dynamicData) {
|
||||
_allActionsDataCache = dynamicData;
|
||||
deserializeActionsInternal();
|
||||
}
|
||||
checkWaitingToRemove();
|
||||
|
@ -2201,10 +2210,10 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const {
|
|||
}
|
||||
|
||||
QVector<QByteArray> serializedActions;
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
const QUuid id = i.key();
|
||||
EntityActionPointer action = _objectActions[id];
|
||||
EntityDynamicPointer action = _objectActions[id];
|
||||
QByteArray bytesForAction = action->serialize();
|
||||
serializedActions << bytesForAction;
|
||||
i++;
|
||||
|
@ -2224,23 +2233,23 @@ void EntityItem::serializeActions(bool& success, QByteArray& result) const {
|
|||
return;
|
||||
}
|
||||
|
||||
const QByteArray EntityItem::getActionDataInternal() const {
|
||||
if (_actionDataDirty) {
|
||||
const QByteArray EntityItem::getDynamicDataInternal() const {
|
||||
if (_dynamicDataDirty) {
|
||||
bool success;
|
||||
serializeActions(success, _allActionsDataCache);
|
||||
if (success) {
|
||||
_actionDataDirty = false;
|
||||
_dynamicDataDirty = false;
|
||||
}
|
||||
}
|
||||
return _allActionsDataCache;
|
||||
}
|
||||
|
||||
const QByteArray EntityItem::getActionData() const {
|
||||
const QByteArray EntityItem::getDynamicData() const {
|
||||
QByteArray result;
|
||||
|
||||
if (_actionDataDirty) {
|
||||
if (_dynamicDataDirty) {
|
||||
withWriteLock([&] {
|
||||
getActionDataInternal();
|
||||
getDynamicDataInternal();
|
||||
result = _allActionsDataCache;
|
||||
});
|
||||
} else {
|
||||
|
@ -2255,9 +2264,9 @@ QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
|
|||
QVariantMap result;
|
||||
withReadLock([&] {
|
||||
if (_objectActions.contains(actionID)) {
|
||||
EntityActionPointer action = _objectActions[actionID];
|
||||
EntityDynamicPointer action = _objectActions[actionID];
|
||||
result = action->getArguments();
|
||||
result["type"] = EntityActionInterface::actionTypeToString(action->getType());
|
||||
result["type"] = EntityDynamicInterface::dynamicTypeToString(action->getType());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2265,7 +2274,7 @@ QVariantMap EntityItem::getActionArguments(const QUuid& actionID) const {
|
|||
}
|
||||
|
||||
bool EntityItem::shouldSuppressLocationEdits() const {
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
if (i.value()->shouldSuppressLocationEdits()) {
|
||||
return true;
|
||||
|
@ -2276,12 +2285,12 @@ bool EntityItem::shouldSuppressLocationEdits() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
QList<EntityActionPointer> EntityItem::getActionsOfType(EntityActionType typeToGet) const {
|
||||
QList<EntityActionPointer> result;
|
||||
QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
|
||||
QList<EntityDynamicPointer> result;
|
||||
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||
while (i != _objectActions.end()) {
|
||||
EntityActionPointer action = i.value();
|
||||
EntityDynamicPointer action = i.value();
|
||||
if (action->getType() == typeToGet && action->isActive()) {
|
||||
result += action;
|
||||
}
|
||||
|
|
|
@ -36,17 +36,17 @@
|
|||
#include "EntityTypes.h"
|
||||
#include "SimulationOwner.h"
|
||||
#include "SimulationFlags.h"
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
class EntitySimulation;
|
||||
class EntityTreeElement;
|
||||
class EntityTreeElementExtraEncodeData;
|
||||
class EntityActionInterface;
|
||||
class EntityDynamicInterface;
|
||||
class EntityItemProperties;
|
||||
class EntityTree;
|
||||
class btCollisionShape;
|
||||
typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
||||
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
|
||||
typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer;
|
||||
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
||||
using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>;
|
||||
|
||||
|
@ -398,22 +398,22 @@ public:
|
|||
void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
|
||||
|
||||
QString actionsToDebugString();
|
||||
bool addAction(EntitySimulationPointer simulation, EntityActionPointer action);
|
||||
bool addAction(EntitySimulationPointer simulation, EntityDynamicPointer action);
|
||||
bool updateAction(EntitySimulationPointer simulation, const QUuid& actionID, const QVariantMap& arguments);
|
||||
bool removeAction(EntitySimulationPointer simulation, const QUuid& actionID);
|
||||
bool clearActions(EntitySimulationPointer simulation);
|
||||
void setActionData(QByteArray actionData);
|
||||
const QByteArray getActionData() const;
|
||||
void setDynamicData(QByteArray dynamicData);
|
||||
const QByteArray getDynamicData() const;
|
||||
bool hasActions() const { return !_objectActions.empty(); }
|
||||
QList<QUuid> getActionIDs() const { return _objectActions.keys(); }
|
||||
QVariantMap getActionArguments(const QUuid& actionID) const;
|
||||
void deserializeActions();
|
||||
|
||||
void setActionDataDirty(bool value) const { _actionDataDirty = value; }
|
||||
bool actionDataDirty() const { return _actionDataDirty; }
|
||||
void setDynamicDataDirty(bool value) const { _dynamicDataDirty = value; }
|
||||
bool dynamicDataDirty() const { return _dynamicDataDirty; }
|
||||
|
||||
void setActionDataNeedsTransmit(bool value) const { _actionDataNeedsTransmit = value; }
|
||||
bool actionDataNeedsTransmit() const { return _actionDataNeedsTransmit; }
|
||||
void setDynamicDataNeedsTransmit(bool value) const { _dynamicDataNeedsTransmit = value; }
|
||||
bool dynamicDataNeedsTransmit() const { return _dynamicDataNeedsTransmit; }
|
||||
|
||||
bool shouldSuppressLocationEdits() const;
|
||||
|
||||
|
@ -421,7 +421,7 @@ public:
|
|||
const QUuid& getSourceUUID() const { return _sourceUUID; }
|
||||
bool matchesSourceUUID(const QUuid& sourceUUID) const { return _sourceUUID == sourceUUID; }
|
||||
|
||||
QList<EntityActionPointer> getActionsOfType(EntityActionType typeToGet) const;
|
||||
QList<EntityDynamicPointer> getActionsOfType(EntityDynamicType typeToGet) const;
|
||||
|
||||
// these are in the frame of this object
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); }
|
||||
|
@ -479,8 +479,8 @@ protected:
|
|||
|
||||
void setSimulated(bool simulated) { _simulated = simulated; }
|
||||
|
||||
const QByteArray getActionDataInternal() const;
|
||||
void setActionDataInternal(QByteArray actionData);
|
||||
const QByteArray getDynamicDataInternal() const;
|
||||
void setDynamicDataInternal(QByteArray dynamicData);
|
||||
|
||||
virtual void locationChanged(bool tellPhysics = true) override;
|
||||
virtual void dimensionsChanged() override;
|
||||
|
@ -570,22 +570,22 @@ protected:
|
|||
void* _physicsInfo { nullptr }; // set by EntitySimulation
|
||||
bool _simulated; // set by EntitySimulation
|
||||
|
||||
bool addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action);
|
||||
bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action);
|
||||
bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr);
|
||||
void deserializeActionsInternal();
|
||||
void serializeActions(bool& success, QByteArray& result) const;
|
||||
QHash<QUuid, EntityActionPointer> _objectActions;
|
||||
QHash<QUuid, EntityDynamicPointer> _objectActions;
|
||||
|
||||
static int _maxActionsDataSize;
|
||||
mutable QByteArray _allActionsDataCache;
|
||||
|
||||
// when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is
|
||||
// when an entity-server starts up, EntityItem::setDynamicData is called before the entity-tree is
|
||||
// ready. This means we can't find our EntityItemPointer or add the action to the simulation. These
|
||||
// are used to keep track of and work around this situation.
|
||||
void checkWaitingToRemove(EntitySimulationPointer simulation = nullptr);
|
||||
mutable QSet<QUuid> _actionsToRemove;
|
||||
mutable bool _actionDataDirty { false };
|
||||
mutable bool _actionDataNeedsTransmit { false };
|
||||
mutable bool _dynamicDataDirty { false };
|
||||
mutable bool _dynamicDataNeedsTransmit { false };
|
||||
// _previouslyDeletedActions is used to avoid an action being re-added due to server round-trip lag
|
||||
static quint64 _rememberDeletedActionTime;
|
||||
mutable QHash<QUuid, quint64> _previouslyDeletedActions;
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
#include <model-networking/MeshProxy.h>
|
||||
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityActionFactoryInterface.h"
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityDynamicFactoryInterface.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
#include "EntitySimulation.h"
|
||||
#include "EntityTree.h"
|
||||
#include "LightEntityItem.h"
|
||||
|
@ -1133,7 +1133,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
QUuid actionID = QUuid::createUuid();
|
||||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
|
||||
bool success = false;
|
||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
// create this action even if the entity doesn't have physics info. it will often be the
|
||||
|
@ -1142,11 +1142,11 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
// if (!entity->getPhysicsInfo()) {
|
||||
// return false;
|
||||
// }
|
||||
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
|
||||
if (actionType == ACTION_TYPE_NONE) {
|
||||
EntityDynamicType dynamicType = EntityDynamicInterface::dynamicTypeFromString(actionTypeString);
|
||||
if (dynamicType == DYNAMIC_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
|
||||
EntityDynamicPointer action = actionFactory->factory(dynamicType, actionID, entity, arguments);
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -279,25 +279,25 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) {
|
|||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::addAction(EntityActionPointer action) {
|
||||
void EntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_actionsToAdd += action;
|
||||
_dynamicsToAdd += dynamic;
|
||||
}
|
||||
|
||||
void EntitySimulation::removeAction(const QUuid actionID) {
|
||||
void EntitySimulation::removeDynamic(const QUuid dynamicID) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_actionsToRemove += actionID;
|
||||
_dynamicsToRemove += dynamicID;
|
||||
}
|
||||
|
||||
void EntitySimulation::removeActions(QList<QUuid> actionIDsToRemove) {
|
||||
void EntitySimulation::removeDynamics(QList<QUuid> dynamicIDsToRemove) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
foreach(QUuid uuid, actionIDsToRemove) {
|
||||
_actionsToRemove.insert(uuid);
|
||||
foreach(QUuid uuid, dynamicIDsToRemove) {
|
||||
_dynamicsToRemove.insert(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
void EntitySimulation::applyActionChanges() {
|
||||
void EntitySimulation::applyDynamicChanges() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_actionsToAdd.clear();
|
||||
_actionsToRemove.clear();
|
||||
_dynamicsToAdd.clear();
|
||||
_dynamicsToRemove.clear();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "EntityActionInterface.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
|
@ -59,10 +59,10 @@ public:
|
|||
|
||||
// friend class EntityTree;
|
||||
|
||||
virtual void addAction(EntityActionPointer action);
|
||||
virtual void removeAction(const QUuid actionID);
|
||||
virtual void removeActions(QList<QUuid> actionIDsToRemove);
|
||||
virtual void applyActionChanges();
|
||||
virtual void addDynamic(EntityDynamicPointer dynamic);
|
||||
virtual void removeDynamic(const QUuid dynamicID);
|
||||
virtual void removeDynamics(QList<QUuid> dynamicIDsToRemove);
|
||||
virtual void applyDynamicChanges();
|
||||
|
||||
/// \param entity pointer to EntityItem to be added
|
||||
/// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked
|
||||
|
@ -103,8 +103,8 @@ protected:
|
|||
|
||||
SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree)
|
||||
SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion
|
||||
QList<EntityActionPointer> _actionsToAdd;
|
||||
QSet<QUuid> _actionsToRemove;
|
||||
QList<EntityDynamicPointer> _dynamicsToAdd;
|
||||
QSet<QUuid> _dynamicsToRemove;
|
||||
|
||||
protected:
|
||||
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue