First merge with Sam's fader branch with custom pipeline support in shape plumber

This commit is contained in:
Olivier Prat 2017-07-12 17:29:21 +02:00
commit 2f9885870b
72 changed files with 1703 additions and 740 deletions

View file

@ -604,6 +604,24 @@ void Agent::processAgentAvatar() {
AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData;
QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail);
int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data";
avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData";
avatarByteArray = scriptedAvatar->toByteArrayStateful(AvatarData::MinimumData, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!";
return;
}
}
}
scriptedAvatar->doneEncoding(true);
static AvatarDataSequenceNumber sequenceNumber = 0;

View file

@ -85,7 +85,22 @@ void AvatarMixer::handleReplicatedPacket(QSharedPointer<ReceivedMessage> message
auto nodeList = DependencyManager::get<NodeList>();
auto nodeID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID));
auto replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr());
SharedNodePointer replicatedNode;
if (message->getType() == PacketType::ReplicatedKillAvatar) {
// this is a kill packet, which we should only process if we already have the node in our list
// since it of course does not make sense to add a node just to remove it an instant later
replicatedNode = nodeList->nodeWithUUID(nodeID);
if (!replicatedNode) {
return;
}
} else {
replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr());
}
// we better have a node to work with at this point
assert(replicatedNode);
if (message->getType() == PacketType::ReplicatedAvatarIdentity) {
handleAvatarIdentityPacket(message, replicatedNode);

View file

@ -383,11 +383,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData";
bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
}
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!";
includeThisAvatar = false;
if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!";
includeThisAvatar = false;
}
}
}

View file

@ -19,7 +19,7 @@
#include "ScriptableAvatar.h"
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
_globalPosition = getPosition();
return AvatarData::toByteArrayStateful(dataDetail);
}

View file

@ -28,7 +28,7 @@ public:
Q_INVOKABLE AnimationDetails getAnimationDetails();
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
private slots:

View file

@ -1101,9 +1101,9 @@ Rectangle {
case 'nearbyUsers':
var data = message.params;
var index = -1;
iAmAdmin = Users.canKick;
index = findNearbySessionIndex('', data);
if (index !== -1) {
iAmAdmin = Users.canKick;
myData = data[index];
data.splice(index, 1);
} else {

View file

@ -166,7 +166,7 @@ Rectangle {
ListView {
anchors { left: parent.left; right: parent.right; leftMargin: 70 }
height: 125;
height: Math.min(250, contentHeight);
spacing: 0;
snapMode: ListView.SnapToItem;
clip: true;

View file

@ -99,7 +99,13 @@ StackView {
height: parent.height
MouseArea {
anchors.fill: parent
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: keyboard.top
}
propagateComposedEvents: true
onPressed: {
parent.forceActiveFocus();

View file

@ -52,8 +52,10 @@ Windows.ScrollingWindow {
// used to receive messages from interface script
function fromScript(message) {
if (loader.item.hasOwnProperty("fromScript")) {
loader.item.fromScript(message);
if (loader.item !== null) {
if (loader.item.hasOwnProperty("fromScript")) {
loader.item.fromScript(message);
}
}
}

View file

@ -2774,56 +2774,43 @@ bool Application::importSVOFromURL(const QString& urlString) {
return true;
}
bool _renderRequested { false };
bool Application::event(QEvent* event) {
if (!Menu::getInstance()) {
return false;
}
// Presentation/painting logic
// TODO: Decouple presentation and painting loops
static bool isPaintingThrottled = false;
if ((int)event->type() == (int)Present) {
if (isPaintingThrottled) {
// If painting (triggered by presentation) is hogging the main thread,
// repost as low priority to avoid hanging the GUI.
// This has the effect of allowing presentation to exceed the paint budget by X times and
// only dropping every (1/X) frames, instead of every ceil(X) frames
// (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS).
removePostedEvents(this, Present);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Present)), Qt::LowEventPriority);
isPaintingThrottled = false;
int type = event->type();
switch (type) {
case Event::Lambda:
static_cast<LambdaEvent*>(event)->call();
return true;
}
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
if (shouldPaint(nsecsElapsed)) {
_lastTimeUpdated.start();
idle(nsecsElapsed);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
isPaintingThrottled = true;
case Event::Present:
if (!_renderRequested) {
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
if (shouldPaint(nsecsElapsed)) {
_renderRequested = true;
_lastTimeUpdated.start();
idle(nsecsElapsed);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
return true;
return true;
} else if ((int)event->type() == (int)Paint) {
// NOTE: This must be updated as close to painting as possible,
// or AvatarInputs will mysteriously move to the bottom-right
AvatarInputs::getInstance()->update();
case Event::Paint:
// NOTE: This must be updated as close to painting as possible,
// or AvatarInputs will mysteriously move to the bottom-right
AvatarInputs::getInstance()->update();
paintGL();
// wait for the next present event before starting idle / paint again
removePostedEvents(this, Present);
_renderRequested = false;
return true;
paintGL();
isPaintingThrottled = false;
return true;
} else if ((int)event->type() == (int)Idle) {
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
idle(nsecsElapsed);
return true;
}
if ((int)event->type() == (int)Lambda) {
static_cast<LambdaEvent*>(event)->call();
return true;
default:
break;
}
{
@ -3211,59 +3198,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
break;
#endif
case Qt::Key_H: {
// whenever switching to/from full screen mirror from the keyboard, remember
// the state you were in before full screen mirror, and return to that.
auto previousMode = _myCamera.getMode();
if (previousMode != CAMERA_MODE_MIRROR) {
switch (previousMode) {
case CAMERA_MODE_FIRST_PERSON:
_returnFromFullScreenMirrorTo = MenuOption::FirstPerson;
break;
case CAMERA_MODE_THIRD_PERSON:
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
break;
// FIXME - it's not clear that these modes make sense to return to...
case CAMERA_MODE_INDEPENDENT:
_returnFromFullScreenMirrorTo = MenuOption::IndependentMode;
break;
case CAMERA_MODE_ENTITY:
_returnFromFullScreenMirrorTo = MenuOption::CameraEntityMode;
break;
default:
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
break;
}
}
bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror);
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked);
if (isMirrorChecked) {
// if we got here without coming in from a non-Full Screen mirror case, then our
// _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old
// behavior of returning to ThirdPerson
if (_returnFromFullScreenMirrorTo.isEmpty()) {
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
}
Menu::getInstance()->setIsOptionChecked(_returnFromFullScreenMirrorTo, true);
}
cameraMenuChanged();
break;
}
case Qt::Key_P: {
if (!(isShifted || isMeta || isOption)) {
bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked);
cameraMenuChanged();
}
break;
}
case Qt::Key_Slash:
Menu::getInstance()->triggerOption(MenuOption::Stats);
break;
@ -7177,6 +7111,12 @@ void Application::updateDisplayMode() {
// reset the avatar, to set head and hand palms back to a reasonable default pose.
getMyAvatar()->reset(false);
// switch to first person if entering hmd and setting is checked
if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
cameraMenuChanged();
}
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
}

View file

@ -223,7 +223,7 @@ Menu::Menu() {
// View > First Person
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::FirstPerson, 0, // QML Qt:: Key_P
MenuOption::FirstPerson, 0,
true, qApp, SLOT(cameraMenuChanged())));
// View > Third Person
@ -233,7 +233,7 @@ Menu::Menu() {
// View > Mirror
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::FullscreenMirror, 0, // QML Qt::Key_H,
MenuOption::FullscreenMirror, 0,
false, qApp, SLOT(cameraMenuChanged())));
// View > Independent [advanced]
@ -258,6 +258,9 @@ Menu::Menu() {
// View > Overlays
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
// View > Enter First Person Mode in HMD
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPersonHMD, 0, true);
// Navigate menu ----------------------------------
MenuWrapper* navigateMenu = addMenu("Navigate");

View file

@ -105,6 +105,7 @@ namespace MenuOption {
const QString ExpandPhysicsSimulationTiming = "Expand /physics";
const QString ExpandUpdateTiming = "Expand /update";
const QString FirstPerson = "First Person";
const QString FirstPersonHMD = "Enter First Person Mode in HMD";
const QString FivePointCalibration = "5 Point Calibration";
const QString FixGaze = "Fix Gaze (no saccade)";
const QString Forward = "Forward";

View file

@ -295,7 +295,7 @@ void MyAvatar::simulateAttachments(float deltaTime) {
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
}
QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
CameraMode mode = qApp->getCamera().getMode();
_globalPosition = getPosition();
// This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17
@ -1356,6 +1356,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
_headBoneSet.clear();
emit skeletonChanged();
}

View file

@ -606,12 +606,13 @@ signals:
void onLoadComplete();
void wentAway();
void wentActive();
void skeletonChanged();
private:
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
void simulate(float deltaTime);
void updateFromTrackers(float deltaTime);

View file

@ -80,8 +80,8 @@ void Circle3DOverlay::render(RenderArgs* args) {
Q_ASSERT(args->_batch);
auto& batch = *args->_batch;
if (args->_pipeline) {
batch.setPipeline(args->_pipeline->pipeline);
if (args->_shapePipeline) {
batch.setPipeline(args->_shapePipeline->pipeline);
}
// FIXME: THe line width of _lineWidth is not supported anymore, we ll need a workaround

View file

@ -65,15 +65,15 @@ void Cube3DOverlay::render(RenderArgs* args) {
transform.setTranslation(position);
transform.setRotation(rotation);
auto geometryCache = DependencyManager::get<GeometryCache>();
auto pipeline = args->_pipeline;
if (!pipeline) {
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
auto shapePipeline = args->_shapePipeline;
if (!shapePipeline) {
shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
}
if (_isSolid) {
transform.setScale(dimensions);
batch->setModelTransform(transform);
geometryCache->renderSolidCubeInstance(args, *batch, cubeColor, pipeline);
geometryCache->renderSolidCubeInstance(args, *batch, cubeColor, shapePipeline);
} else {
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
if (getIsDashedLine()) {
@ -109,7 +109,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
} else {
transform.setScale(dimensions);
batch->setModelTransform(transform);
geometryCache->renderWireCubeInstance(args, *batch, cubeColor, pipeline);
geometryCache->renderWireCubeInstance(args, *batch, cubeColor, shapePipeline);
}
}
}

View file

@ -152,6 +152,7 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const {
OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties) {
if (QThread::currentThread() != thread()) {
OverlayID result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(QString, type), Q_ARG(QVariant, properties));
return result;
}
@ -220,6 +221,7 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) {
OverlayID Overlays::cloneOverlay(OverlayID id) {
if (QThread::currentThread() != thread()) {
OverlayID result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(OverlayID, id));
return result;
}
@ -307,6 +309,7 @@ void Overlays::deleteOverlay(OverlayID id) {
}
#endif
_overlaysToDelete.push_back(overlayToDelete);
emit overlayDeleted(id);
}
@ -314,6 +317,7 @@ void Overlays::deleteOverlay(OverlayID id) {
QString Overlays::getOverlayType(OverlayID overlayId) {
if (QThread::currentThread() != thread()) {
QString result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(OverlayID, overlayId));
return result;
}
@ -328,6 +332,7 @@ QString Overlays::getOverlayType(OverlayID overlayId) {
QObject* Overlays::getOverlayObject(OverlayID id) {
if (QThread::currentThread() != thread()) {
QObject* result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
return result;
}
@ -383,12 +388,6 @@ void Overlays::setParentPanel(OverlayID childId, OverlayID panelId) {
#endif
OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
if (QThread::currentThread() != thread()) {
OverlayID result;
BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_RETURN_ARG(OverlayID, result), Q_ARG(glm::vec2, point));
return result;
}
if (!_enabled) {
return UNKNOWN_OVERLAY_ID;
}
@ -413,20 +412,47 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
}
OverlayPropertyResult Overlays::getProperty(OverlayID id, const QString& property) {
if (QThread::currentThread() != thread()) {
OverlayPropertyResult result;
BLOCKING_INVOKE_METHOD(this, "getProperty", Q_RETURN_ARG(OverlayPropertyResult, result), Q_ARG(OverlayID, id), Q_ARG(QString, property));
return result;
}
OverlayPropertyResult result;
Overlay::Pointer thisOverlay = getOverlay(id);
OverlayPropertyResult result;
if (thisOverlay && thisOverlay->supportsGetProperty()) {
result.value = thisOverlay->getProperty(property);
}
return result;
}
OverlayPropertyResult Overlays::getProperties(const OverlayID& id, const QStringList& properties) {
Overlay::Pointer thisOverlay = getOverlay(id);
OverlayPropertyResult result;
if (thisOverlay && thisOverlay->supportsGetProperty()) {
QVariantMap mapResult;
for (const auto& property : properties) {
mapResult.insert(property, thisOverlay->getProperty(property));
}
result.value = mapResult;
}
return result;
}
OverlayPropertyResult Overlays::getOverlaysProperties(const QVariant& propertiesById) {
QVariantMap map = propertiesById.toMap();
OverlayPropertyResult result;
QVariantMap resultMap;
for (const auto& key : map.keys()) {
OverlayID id = OverlayID(key);
QVariantMap overlayResult;
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay && thisOverlay->supportsGetProperty()) {
QStringList propertiesToFetch = map[key].toStringList();
for (const auto& property : propertiesToFetch) {
overlayResult[property] = thisOverlay->getProperty(property);
}
}
resultMap[key] = overlayResult;
}
result.value = resultMap;
return result;
}
OverlayPropertyResult::OverlayPropertyResult() {
}
@ -458,18 +484,6 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickR
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly, bool collidableOnly) {
if (QThread::currentThread() != thread()) {
RayToOverlayIntersectionResult result;
BLOCKING_INVOKE_METHOD(this, "findRayIntersectionInternal", Q_RETURN_ARG(RayToOverlayIntersectionResult, result),
Q_ARG(PickRay, ray),
Q_ARG(bool, precisionPicking),
Q_ARG(QVector<OverlayID>, overlaysToInclude),
Q_ARG(QVector<OverlayID>, overlaysToDiscard),
Q_ARG(bool, visibleOnly),
Q_ARG(bool, collidableOnly));
return result;
}
float bestDistance = std::numeric_limits<float>::max();
bool bestIsFront = false;
@ -588,6 +602,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar
bool Overlays::isLoaded(OverlayID id) {
if (QThread::currentThread() != thread()) {
bool result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id));
return result;
}
@ -602,26 +617,21 @@ bool Overlays::isLoaded(OverlayID id) {
QSizeF Overlays::textSize(OverlayID id, const QString& text) {
if (QThread::currentThread() != thread()) {
QSizeF result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(OverlayID, id), Q_ARG(QString, text));
return result;
}
Overlay::Pointer thisOverlay;
{
QMutexLocker locker(&_mutex);
thisOverlay = _overlaysHUD[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
if (auto textOverlay = std::dynamic_pointer_cast<TextOverlay>(thisOverlay)) {
return textOverlay->textSize(text);
}
} else {
{
QMutexLocker locker(&_mutex);
thisOverlay = _overlaysWorld[id];
}
if (auto text3dOverlay = std::dynamic_pointer_cast<Text3DOverlay>(thisOverlay)) {
return text3dOverlay->textSize(text);
if (thisOverlay->is3D()) {
if (auto text3dOverlay = std::dynamic_pointer_cast<Text3DOverlay>(thisOverlay)) {
return text3dOverlay->textSize(text);
}
} else {
if (auto textOverlay = std::dynamic_pointer_cast<TextOverlay>(thisOverlay)) {
return textOverlay->textSize(text);
}
}
}
return QSizeF(0.0f, 0.0f);
@ -686,6 +696,7 @@ void Overlays::deletePanel(OverlayID panelId) {
bool Overlays::isAddedOverlay(OverlayID id) {
if (QThread::currentThread() != thread()) {
bool result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "isAddedOverlay", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id));
return result;
}
@ -721,6 +732,7 @@ void Overlays::sendHoverLeaveOverlay(OverlayID id, PointerEvent event) {
OverlayID Overlays::getKeyboardFocusOverlay() {
if (QThread::currentThread() != thread()) {
OverlayID result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "getKeyboardFocusOverlay", Q_RETURN_ARG(OverlayID, result));
return result;
}
@ -740,6 +752,7 @@ void Overlays::setKeyboardFocusOverlay(OverlayID id) {
float Overlays::width() {
if (QThread::currentThread() != thread()) {
float result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "width", Q_RETURN_ARG(float, result));
return result;
}
@ -751,6 +764,7 @@ float Overlays::width() {
float Overlays::height() {
if (QThread::currentThread() != thread()) {
float result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "height", Q_RETURN_ARG(float, result));
return result;
}
@ -960,10 +974,11 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) {
QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
QVector<QUuid> result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "findOverlays", Q_RETURN_ARG(QVector<QUuid>, result), Q_ARG(glm::vec3, center), Q_ARG(float, radius));
return result;
}
//if (QThread::currentThread() != thread()) {
// PROFILE_RANGE(script, __FUNCTION__);
// BLOCKING_INVOKE_METHOD(this, "findOverlays", Q_RETURN_ARG(QVector<QUuid>, result), Q_ARG(glm::vec3, center), Q_ARG(float, radius));
// return result;
//}
QMutexLocker locker(&_mutex);
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);

View file

@ -190,6 +190,10 @@ public slots:
*/
OverlayPropertyResult getProperty(OverlayID id, const QString& property);
OverlayPropertyResult getProperties(const OverlayID& id, const QStringList& properties);
OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties);
/*jsdoc
* Find the closest 3D overlay hit by a pick ray.
*

View file

@ -45,17 +45,17 @@ void Shape3DOverlay::render(RenderArgs* args) {
transform.setTranslation(position);
transform.setRotation(rotation);
auto geometryCache = DependencyManager::get<GeometryCache>();
auto pipeline = args->_pipeline;
if (!pipeline) {
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
auto shapePipeline = args->_shapePipeline;
if (!shapePipeline) {
shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
}
transform.setScale(dimensions);
batch->setModelTransform(transform);
if (_isSolid) {
geometryCache->renderSolidShapeInstance(args, *batch, _shape, cubeColor, pipeline);
geometryCache->renderSolidShapeInstance(args, *batch, _shape, cubeColor, shapePipeline);
} else {
geometryCache->renderWireShapeInstance(args, *batch, _shape, cubeColor, pipeline);
geometryCache->renderWireShapeInstance(args, *batch, _shape, cubeColor, shapePipeline);
}
}
}

View file

@ -44,15 +44,15 @@ void Sphere3DOverlay::render(RenderArgs* args) {
batch->setModelTransform(transform);
auto geometryCache = DependencyManager::get<GeometryCache>();
auto pipeline = args->_pipeline;
if (!pipeline) {
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
auto shapePipeline = args->_shapePipeline;
if (!shapePipeline) {
shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
}
if (_isSolid) {
geometryCache->renderSolidSphereInstance(args, *batch, sphereColor, pipeline);
geometryCache->renderSolidSphereInstance(args, *batch, sphereColor, shapePipeline);
} else {
geometryCache->renderWireSphereInstance(args, *batch, sphereColor, pipeline);
geometryCache->renderWireSphereInstance(args, *batch, sphereColor, shapePipeline);
}
}
}

View file

@ -137,8 +137,8 @@ void Text3DOverlay::render(RenderArgs* args) {
// Text renderer sets its own pipeline,
_textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), getDrawInFront());
// so before we continue, we must reset the pipeline
batch.setPipeline(args->_pipeline->pipeline);
args->_pipeline->prepare(batch, args);
batch.setPipeline(args->_shapePipeline->pipeline);
args->_shapePipeline->prepare(batch, args);
}
const render::ShapeKey Text3DOverlay::getShapeKey() {

View file

@ -451,7 +451,7 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
// In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will
// receive mouse events
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
if (!(this->_pressed && event.getType() == PointerEvent::Move)) {
if (event.getType() == PointerEvent::Move) {
QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
}
@ -459,11 +459,10 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
if (this->_pressed && event.getType() == PointerEvent::Move) {
return;
if (event.getType() == PointerEvent::Move) {
QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
}
QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
#endif
}

View file

@ -404,8 +404,18 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
}
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
if (QThread::currentThread() == thread()) {
if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
return true;
} else {
return false;
}
}
QReadLocker readLock(&_externalPoseSetLock);
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
return true;
} else {
return false;
@ -413,17 +423,31 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
}
bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const {
if (isIndexValid(jointIndex)) {
position = _internalPoseSet._absolutePoses[jointIndex].trans();
return true;
if (QThread::currentThread() == thread()) {
if (isIndexValid(jointIndex)) {
position = _internalPoseSet._absolutePoses[jointIndex].trans();
return true;
} else {
return false;
}
} else {
return false;
return getAbsoluteJointTranslationInRigFrame(jointIndex, position);
}
}
bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const {
if (isIndexValid(jointIndex)) {
result = rotation * _internalPoseSet._absolutePoses[jointIndex].rot();
if (QThread::currentThread() == thread()) {
if (isIndexValid(jointIndex)) {
result = rotation * _internalPoseSet._absolutePoses[jointIndex].rot();
return true;
} else {
return false;
}
}
QReadLocker readLock(&_externalPoseSetLock);
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
result = rotation * _externalPoseSet._absolutePoses[jointIndex].rot();
return true;
} else {
return false;
@ -431,6 +455,15 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const
}
bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (QThread::currentThread() == thread()) {
if (isIndexValid(jointIndex)) {
rotation = _internalPoseSet._relativePoses[jointIndex].rot();
return true;
} else {
return false;
}
}
QReadLocker readLock(&_externalPoseSetLock);
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._relativePoses.size()) {
rotation = _externalPoseSet._relativePoses[jointIndex].rot();

View file

@ -1067,49 +1067,60 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
}
}
int Avatar::getJointIndex(const QString& name) const {
if (QThread::currentThread() != thread()) {
int result;
BLOCKING_INVOKE_METHOD(const_cast<Avatar*>(this), "getJointIndex",
Q_RETURN_ARG(int, result), Q_ARG(const QString&, name));
return result;
void Avatar::invalidateJointIndicesCache() const {
QWriteLocker writeLock(&_modelJointIndicesCacheLock);
_modelJointsCached = false;
}
void Avatar::withValidJointIndicesCache(std::function<void()> const& worker) const {
QReadLocker readLock(&_modelJointIndicesCacheLock);
if (_modelJointsCached) {
worker();
} else {
readLock.unlock();
{
QWriteLocker writeLock(&_modelJointIndicesCacheLock);
if (!_modelJointsCached) {
_modelJointIndicesCache.clear();
if (_skeletonModel && _skeletonModel->isActive()) {
_modelJointIndicesCache = _skeletonModel->getFBXGeometry().jointIndices;
_modelJointsCached = true;
}
}
worker();
}
}
}
int Avatar::getJointIndex(const QString& name) const {
int result = getFauxJointIndex(name);
if (result != -1) {
return result;
}
return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointIndex(name) : -1;
withValidJointIndicesCache([&]() {
if (_modelJointIndicesCache.contains(name)) {
result = _modelJointIndicesCache[name] - 1;
}
});
return result;
}
QStringList Avatar::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
BLOCKING_INVOKE_METHOD(const_cast<Avatar*>(this), "getJointNames",
Q_RETURN_ARG(QStringList, result));
return result;
}
return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointNames() : QStringList();
QStringList result;
withValidJointIndicesCache([&]() {
result = _modelJointIndicesCache.keys();
});
return result;
}
glm::vec3 Avatar::getJointPosition(int index) const {
if (QThread::currentThread() != thread()) {
glm::vec3 position;
BLOCKING_INVOKE_METHOD(const_cast<Avatar*>(this), "getJointPosition",
Q_RETURN_ARG(glm::vec3, position), Q_ARG(const int, index));
return position;
}
glm::vec3 position;
_skeletonModel->getJointPositionInWorldFrame(index, position);
return position;
}
glm::vec3 Avatar::getJointPosition(const QString& name) const {
if (QThread::currentThread() != thread()) {
glm::vec3 position;
BLOCKING_INVOKE_METHOD(const_cast<Avatar*>(this), "getJointPosition",
Q_RETURN_ARG(glm::vec3, position), Q_ARG(const QString&, name));
return position;
}
glm::vec3 position;
_skeletonModel->getJointPositionInWorldFrame(getJointIndex(name), position);
return position;
@ -1130,6 +1141,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
}
void Avatar::setModelURLFinished(bool success) {
invalidateJointIndicesCache();
if (!success && _skeletonModelURL != 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 ||

View file

@ -271,6 +271,13 @@ protected:
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
SkeletonModelPointer _skeletonModel;
void invalidateJointIndicesCache() const;
void withValidJointIndicesCache(std::function<void()> const& worker) const;
mutable QHash<QString, int> _modelJointIndicesCache;
mutable QReadWriteLock _modelJointIndicesCacheLock;
mutable bool _modelJointsCached { false };
glm::vec3 _skeletonOffset;
std::vector<std::shared_ptr<Model>> _attachmentModels;
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;

View file

@ -57,6 +57,27 @@ static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients) {
return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float);
}
size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) {
const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE);
size_t totalSize = sizeof(uint8_t); // numJoints
totalSize += validityBitsSize; // Orientations mask
totalSize += numJoints * sizeof(SixByteQuat); // Orientations
totalSize += validityBitsSize; // Translations mask
totalSize += numJoints * sizeof(SixByteTrans); // Translations
size_t NUM_FAUX_JOINT = 2;
totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints
return totalSize;
}
AvatarData::AvatarData() :
SpatiallyNestable(NestableType::Avatar, QUuid()),
_handPosition(0.0f),
@ -73,19 +94,6 @@ AvatarData::AvatarData() :
setBodyPitch(0.0f);
setBodyYaw(-90.0f);
setBodyRoll(0.0f);
ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE);
ASSERT(sizeof(AvatarDataPacket::AvatarGlobalPosition) == AvatarDataPacket::AVATAR_GLOBAL_POSITION_SIZE);
ASSERT(sizeof(AvatarDataPacket::AvatarLocalPosition) == AvatarDataPacket::AVATAR_LOCAL_POSITION_SIZE);
ASSERT(sizeof(AvatarDataPacket::AvatarBoundingBox) == AvatarDataPacket::AVATAR_BOUNDING_BOX_SIZE);
ASSERT(sizeof(AvatarDataPacket::AvatarOrientation) == AvatarDataPacket::AVATAR_ORIENTATION_SIZE);
ASSERT(sizeof(AvatarDataPacket::AvatarScale) == AvatarDataPacket::AVATAR_SCALE_SIZE);
ASSERT(sizeof(AvatarDataPacket::LookAtPosition) == AvatarDataPacket::LOOK_AT_POSITION_SIZE);
ASSERT(sizeof(AvatarDataPacket::AudioLoudness) == AvatarDataPacket::AUDIO_LOUDNESS_SIZE);
ASSERT(sizeof(AvatarDataPacket::SensorToWorldMatrix) == AvatarDataPacket::SENSOR_TO_WORLD_SIZE);
ASSERT(sizeof(AvatarDataPacket::AdditionalFlags) == AvatarDataPacket::ADDITIONAL_FLAGS_SIZE);
ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE);
ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE);
}
AvatarData::~AvatarData() {
@ -169,12 +177,12 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio
// we want to track outbound data in this case...
QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail) {
QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
AvatarDataPacket::HasFlags hasFlagsOut;
auto lastSentTime = _lastToByteArray;
_lastToByteArray = usecTimestampNow();
return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
hasFlagsOut, false, false, glm::vec3(0), nullptr,
hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr,
&_outboundDataRate);
}
@ -189,15 +197,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
lazyInitHeadData();
QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0);
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
unsigned char* startPosition = destinationBuffer;
// special case, if we were asked for no data, then just include the flags all set to nothing
if (dataDetail == NoData) {
AvatarDataPacket::HasFlags packetStateFlags = 0;
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
return avatarDataByteArray.left(sizeof(packetStateFlags));
QByteArray avatarDataByteArray(reinterpret_cast<char*>(&packetStateFlags), sizeof(packetStateFlags));
return avatarDataByteArray;
}
// FIXME -
@ -258,6 +262,15 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
hasJointData = sendAll || !sendMinimum;
}
const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE +
(hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getNumSummedBlendshapeCoefficients()) : 0) +
(hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0);
QByteArray avatarDataByteArray((int)byteArraySize, 0);
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
unsigned char* startPosition = destinationBuffer;
// Leading flags, to indicate how much data is actually included in the packet...
AvatarDataPacket::HasFlags packetStateFlags =
(hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0)
@ -478,12 +491,15 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
unsigned char* validityPosition = destinationBuffer;
unsigned char validity = 0;
int validityBit = 0;
int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE);
#ifdef WANT_DEBUG
int rotationSentCount = 0;
unsigned char* beforeRotations = destinationBuffer;
#endif
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
if (sentJointDataOut) {
sentJointDataOut->resize(_jointData.size()); // Make sure the destination is resized before using it
}
@ -503,6 +519,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
#ifdef WANT_DEBUG
rotationSentCount++;
#endif
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
if (sentJointDataOut) {
auto jointDataOut = *sentJointDataOut;
jointDataOut[i].rotation = data.rotation;
@ -512,28 +530,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
}
if (++validityBit == BITS_IN_BYTE) {
*destinationBuffer++ = validity;
*validityPosition++ = validity;
validityBit = validity = 0;
}
}
if (validityBit != 0) {
*destinationBuffer++ = validity;
*validityPosition++ = validity;
}
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
if (validity & (1 << validityBit)) {
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
}
if (++validityBit == BITS_IN_BYTE) {
validityBit = 0;
validity = *validityPosition++;
}
}
// joint translation data
validityPosition = destinationBuffer;
validity = 0;
@ -544,6 +548,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
unsigned char* beforeTranslations = destinationBuffer;
#endif
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition);
float maxTranslationDimension = 0.0;
@ -562,6 +568,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
destinationBuffer +=
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
if (sentJointDataOut) {
auto jointDataOut = *sentJointDataOut;
jointDataOut[i].translation = data.translation;
@ -571,27 +580,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
}
if (++validityBit == BITS_IN_BYTE) {
*destinationBuffer++ = validity;
*validityPosition++ = validity;
validityBit = validity = 0;
}
}
if (validityBit != 0) {
*destinationBuffer++ = validity;
}
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
if (validity & (1 << validityBit)) {
destinationBuffer +=
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
}
if (++validityBit == BITS_IN_BYTE) {
validityBit = 0;
validity = *validityPosition++;
}
*validityPosition++ = validity;
}
// faux joints
@ -624,6 +619,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
int avatarDataSize = destinationBuffer - startPosition;
if (avatarDataSize > (int)byteArraySize) {
qCCritical(avatars) << "AvatarData::toByteArray buffer overflow"; // We've overflown into the heap
ASSERT(false);
}
return avatarDataByteArray.left(avatarDataSize);
}
// NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation
@ -1462,12 +1463,12 @@ int AvatarData::getJointIndex(const QString& name) const {
return result;
}
QReadLocker readLock(&_jointDataLock);
return _jointIndices.value(name) - 1;
return _fstJointIndices.value(name) - 1;
}
QStringList AvatarData::getJointNames() const {
QReadLocker readLock(&_jointDataLock);
return _jointNames;
return _fstJointNames;
}
glm::quat AvatarData::getOrientationOutbound() const {
@ -1720,14 +1721,14 @@ void AvatarData::setJointMappingsFromNetworkReply() {
bool ok;
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
if (ok) {
while (_jointNames.size() < jointIndex + 1) {
_jointNames.append(QString());
while (_fstJointNames.size() < jointIndex + 1) {
_fstJointNames.append(QString());
}
_jointNames[jointIndex] = jointName;
_fstJointNames[jointIndex] = jointName;
}
}
for (int i = 0; i < _jointNames.size(); i++) {
_jointIndices.insert(_jointNames.at(i), i + 1);
for (int i = 0; i < _fstJointNames.size(); i++) {
_fstJointIndices.insert(_fstJointNames.at(i), i + 1);
}
}
@ -1743,6 +1744,24 @@ void AvatarData::sendAvatarDataPacket() {
bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
auto dataDetail = cullSmallData ? SendAllData : CullSmallData;
QByteArray avatarByteArray = toByteArrayStateful(dataDetail);
int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber);
if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data";
avatarByteArray = toByteArrayStateful(dataDetail, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData";
avatarByteArray = toByteArrayStateful(MinimumData, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!";
return;
}
}
}
doneEncoding(cullSmallData);
static AvatarDataSequenceNumber sequenceNumber = 0;
@ -1781,8 +1800,8 @@ void AvatarData::sendIdentityPacket() {
void AvatarData::updateJointMappings() {
{
QWriteLocker writeLock(&_jointDataLock);
_jointIndices.clear();
_jointNames.clear();
_fstJointIndices.clear();
_fstJointNames.clear();
_jointData.clear();
}

View file

@ -140,35 +140,41 @@ namespace AvatarDataPacket {
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11;
const size_t AVATAR_HAS_FLAGS_SIZE = 2;
using SixByteQuat = uint8_t[6];
using SixByteTrans = uint8_t[6];
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
PACKED_BEGIN struct Header {
HasFlags packetHasFlags; // state flags, indicated which additional records are included in the packet
} PACKED_END;
const size_t HEADER_SIZE = 2;
static_assert(sizeof(Header) == HEADER_SIZE, "AvatarDataPacket::Header size doesn't match.");
PACKED_BEGIN struct AvatarGlobalPosition {
float globalPosition[3]; // avatar's position
} PACKED_END;
const size_t AVATAR_GLOBAL_POSITION_SIZE = 12;
static_assert(sizeof(AvatarGlobalPosition) == AVATAR_GLOBAL_POSITION_SIZE, "AvatarDataPacket::AvatarGlobalPosition size doesn't match.");
PACKED_BEGIN struct AvatarBoundingBox {
float avatarDimensions[3]; // avatar's bounding box in world space units, but relative to the position.
float boundOriginOffset[3]; // offset from the position of the avatar to the origin of the bounding box
} PACKED_END;
const size_t AVATAR_BOUNDING_BOX_SIZE = 24;
static_assert(sizeof(AvatarBoundingBox) == AVATAR_BOUNDING_BOX_SIZE, "AvatarDataPacket::AvatarBoundingBox size doesn't match.");
using SixByteQuat = uint8_t[6];
PACKED_BEGIN struct AvatarOrientation {
SixByteQuat avatarOrientation; // encodeded and compressed by packOrientationQuatToSixBytes()
} PACKED_END;
const size_t AVATAR_ORIENTATION_SIZE = 6;
static_assert(sizeof(AvatarOrientation) == AVATAR_ORIENTATION_SIZE, "AvatarDataPacket::AvatarOrientation size doesn't match.");
PACKED_BEGIN struct AvatarScale {
SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte()
} PACKED_END;
const size_t AVATAR_SCALE_SIZE = 2;
static_assert(sizeof(AvatarScale) == AVATAR_SCALE_SIZE, "AvatarDataPacket::AvatarScale size doesn't match.");
PACKED_BEGIN struct LookAtPosition {
float lookAtPosition[3]; // world space position that eyes are focusing on.
@ -180,11 +186,13 @@ namespace AvatarDataPacket {
// POTENTIAL SAVINGS - 12 bytes
} PACKED_END;
const size_t LOOK_AT_POSITION_SIZE = 12;
static_assert(sizeof(LookAtPosition) == LOOK_AT_POSITION_SIZE, "AvatarDataPacket::LookAtPosition size doesn't match.");
PACKED_BEGIN struct AudioLoudness {
uint8_t audioLoudness; // current loudness of microphone compressed with packFloatGainToByte()
} PACKED_END;
const size_t AUDIO_LOUDNESS_SIZE = 1;
static_assert(sizeof(AudioLoudness) == AUDIO_LOUDNESS_SIZE, "AvatarDataPacket::AudioLoudness size doesn't match.");
PACKED_BEGIN struct SensorToWorldMatrix {
// FIXME - these 20 bytes are only used by viewers if my avatar has "attachments"
@ -199,11 +207,13 @@ namespace AvatarDataPacket {
// relative to the avatar position.
} PACKED_END;
const size_t SENSOR_TO_WORLD_SIZE = 20;
static_assert(sizeof(SensorToWorldMatrix) == SENSOR_TO_WORLD_SIZE, "AvatarDataPacket::SensorToWorldMatrix size doesn't match.");
PACKED_BEGIN struct AdditionalFlags {
uint8_t flags; // additional flags: hand state, key state, eye tracking
} PACKED_END;
const size_t ADDITIONAL_FLAGS_SIZE = 1;
static_assert(sizeof(AdditionalFlags) == ADDITIONAL_FLAGS_SIZE, "AvatarDataPacket::AdditionalFlags size doesn't match.");
// only present if HAS_REFERENTIAL flag is set in AvatarInfo.flags
PACKED_BEGIN struct ParentInfo {
@ -211,6 +221,7 @@ namespace AvatarDataPacket {
uint16_t parentJointIndex;
} PACKED_END;
const size_t PARENT_INFO_SIZE = 18;
static_assert(sizeof(ParentInfo) == PARENT_INFO_SIZE, "AvatarDataPacket::ParentInfo size doesn't match.");
// will only ever be included if the avatar has a parent but can change independent of changes to parent info
// and so we keep it a separate record
@ -218,6 +229,22 @@ namespace AvatarDataPacket {
float localPosition[3]; // parent frame translation of the avatar
} PACKED_END;
const size_t AVATAR_LOCAL_POSITION_SIZE = 12;
static_assert(sizeof(AvatarLocalPosition) == AVATAR_LOCAL_POSITION_SIZE, "AvatarDataPacket::AvatarLocalPosition size doesn't match.");
const size_t MAX_CONSTANT_HEADER_SIZE = HEADER_SIZE +
AVATAR_GLOBAL_POSITION_SIZE +
AVATAR_BOUNDING_BOX_SIZE +
AVATAR_ORIENTATION_SIZE +
AVATAR_SCALE_SIZE +
LOOK_AT_POSITION_SIZE +
AUDIO_LOUDNESS_SIZE +
SENSOR_TO_WORLD_SIZE +
ADDITIONAL_FLAGS_SIZE +
PARENT_INFO_SIZE +
AVATAR_LOCAL_POSITION_SIZE;
// variable length structure follows
// only present if IS_FACE_TRACKER_CONNECTED flag is set in AvatarInfo.flags
PACKED_BEGIN struct FaceTrackerInfo {
@ -229,8 +256,9 @@ namespace AvatarDataPacket {
// float blendshapeCoefficients[numBlendshapeCoefficients];
} PACKED_END;
const size_t FACE_TRACKER_INFO_SIZE = 17;
static_assert(sizeof(FaceTrackerInfo) == FACE_TRACKER_INFO_SIZE, "AvatarDataPacket::FaceTrackerInfo size doesn't match.");
size_t maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients);
// variable length structure follows
/*
struct JointData {
uint8_t numJoints;
@ -240,6 +268,7 @@ namespace AvatarDataPacket {
SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed()
};
*/
size_t maxJointDataSize(size_t numJoints);
}
static const float MAX_AVATAR_SCALE = 1000.0f;
@ -387,7 +416,7 @@ public:
SendAllData
} AvatarDataDetail;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail);
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false);
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
@ -704,8 +733,8 @@ protected:
QString _displayName;
QString _sessionDisplayName { };
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _jointNames; ///< in order of depth-first traversal
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _fstJointNames; ///< in order of depth-first traversal
quint64 _errorLogExpiry; ///< time in future when to log an error

View file

@ -83,6 +83,11 @@ static const QMap<QString, int>& getBlendshapesLookupMap() {
return blendshapeLookupMap;
}
int HeadData::getNumSummedBlendshapeCoefficients() const {
int maxSize = std::max(_blendshapeCoefficients.size(), _transientBlendshapeCoefficients.size());
return maxSize;
}
const QVector<float>& HeadData::getSummedBlendshapeCoefficients() {
int maxSize = std::max(_blendshapeCoefficients.size(), _transientBlendshapeCoefficients.size());
if (_summedBlendshapeCoefficients.size() != maxSize) {

View file

@ -57,6 +57,7 @@ public:
void setBlendshape(QString name, float val);
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
const QVector<float>& getSummedBlendshapeCoefficients();
int getNumSummedBlendshapeCoefficients() const;
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }

View file

@ -13,14 +13,13 @@
#include <DependencyManager.h>
#include <PerfStat.h>
#include <GeometryCache.h>
#include <render/ShapePipeline.h>
#include <StencilMaskPass.h>
#include <AbstractViewStateInterface.h>
#include "EntitiesRendererLogging.h"
#include "RenderableParticleEffectEntityItem.h"
#include "untextured_particle_vert.h"
#include "untextured_particle_frag.h"
#include "textured_particle_vert.h"
#include "textured_particle_frag.h"
@ -29,6 +28,16 @@ class ParticlePayloadData {
public:
static const size_t VERTEX_PER_PARTICLE = 4;
static uint8_t CUSTOM_PIPELINE_NUMBER;
static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key);
static void registerShapePipeline() {
if (!CUSTOM_PIPELINE_NUMBER) {
CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory);
}
}
static std::weak_ptr<gpu::Pipeline> _texturedPipeline;
template<typename T>
struct InterpolationData {
T start;
@ -70,9 +79,6 @@ public:
offsetof(ParticlePrimitive, uv), gpu::Stream::PER_INSTANCE);
}
void setPipeline(PipelinePointer pipeline) { _pipeline = pipeline; }
const PipelinePointer& getPipeline() const { return _pipeline; }
const Transform& getModelTransform() const { return _modelTransform; }
void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; }
@ -90,15 +96,15 @@ public:
bool getVisibleFlag() const { return _visibleFlag; }
void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; }
void render(RenderArgs* args) const {
assert(_pipeline);
gpu::Batch& batch = *args->_batch;
batch.setPipeline(_pipeline);
if (_texture) {
batch.setResourceTexture(0, _texture);
} else {
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
}
batch.setModelTransform(_modelTransform);
@ -113,7 +119,6 @@ public:
protected:
Transform _modelTransform;
AABox _bound;
PipelinePointer _pipeline;
FormatPointer _vertexFormat { std::make_shared<Format>() };
BufferPointer _particleBuffer { std::make_shared<Buffer>() };
BufferView _uniformBuffer;
@ -142,23 +147,49 @@ namespace render {
payload->render(args);
}
}
template <>
const ShapeKey shapeGetShapeKey(const ParticlePayloadData::Pointer& payload) {
return render::ShapeKey::Builder().withCustom(ParticlePayloadData::CUSTOM_PIPELINE_NUMBER).withTranslucent().build();
}
}
uint8_t ParticlePayloadData::CUSTOM_PIPELINE_NUMBER = 0;
std::weak_ptr<gpu::Pipeline> ParticlePayloadData::_texturedPipeline;
render::ShapePipelinePointer ParticlePayloadData::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) {
auto texturedPipeline = _texturedPipeline.lock();
if (!texturedPipeline) {
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert));
auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag));
auto program = gpu::Shader::createProgram(vertShader, fragShader);
_texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state);
}
return std::make_shared<render::ShapePipeline>(texturedPipeline, nullptr, nullptr, nullptr);
}
EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID,
const EntityItemProperties& properties) {
auto entity = std::make_shared<RenderableParticleEffectEntityItem>(entityID);
entity->setProperties(properties);
// As we create the first ParticuleSystem entity, let s register its special shapePIpeline factory:
ParticlePayloadData::registerShapePipeline();
return entity;
}
RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID) :
ParticleEffectEntityItem(entityItemID) {
// lazy creation of particle system pipeline
if (!_untexturedPipeline || !_texturedPipeline) {
createPipelines();
}
}
bool RenderableParticleEffectEntityItem::addToScene(const EntityItemPointer& self,
@ -167,7 +198,6 @@ bool RenderableParticleEffectEntityItem::addToScene(const EntityItemPointer& sel
_scene = scene;
_renderItemId = _scene->allocateID();
auto particlePayloadData = std::make_shared<ParticlePayloadData>();
particlePayloadData->setPipeline(_untexturedPipeline);
auto renderPayload = std::make_shared<ParticlePayloadData::Payload>(particlePayloadData);
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
@ -276,47 +306,14 @@ void RenderableParticleEffectEntityItem::updateRenderItem() {
if (_texture && _texture->isLoaded()) {
payload.setTexture(_texture->getGPUTexture());
payload.setPipeline(_texturedPipeline);
} else {
payload.setTexture(nullptr);
payload.setPipeline(_untexturedPipeline);
}
});
_scene->enqueueTransaction(transaction);
}
void RenderableParticleEffectEntityItem::createPipelines() {
if (!_untexturedPipeline) {
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert));
auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag));
auto program = gpu::Shader::createProgram(vertShader, fragShader);
_untexturedPipeline = gpu::Pipeline::create(program, state);
}
if (!_texturedPipeline) {
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert));
auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag));
auto program = gpu::Shader::createProgram(vertShader, fragShader);
_texturedPipeline = gpu::Pipeline::create(program, state);
}
}
void RenderableParticleEffectEntityItem::notifyBoundChanged() {
if (!render::Item::isValidID(_renderItemId)) {
return;

View file

@ -34,16 +34,13 @@ protected:
virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); notifyBoundChanged(); }
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
void notifyBoundChanged();
void notifyBoundChanged();
void createPipelines();
render::ScenePointer _scene;
render::ItemID _renderItemId{ render::Item::INVALID_ITEM_ID };
NetworkTexturePointer _texture;
gpu::PipelinePointer _untexturedPipeline;
gpu::PipelinePointer _texturedPipeline;
};

View file

@ -63,9 +63,6 @@
#include "EntityEditPacketSender.h"
#include "PhysicalEntitySimulation.h"
gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipelines[2] = { nullptr, nullptr };
gpu::PipelinePointer RenderablePolyVoxEntityItem::_wireframePipelines[2] = { nullptr, nullptr };
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
@ -120,6 +117,10 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit
EntityItemPointer entity{ new RenderablePolyVoxEntityItem(entityID) };
entity->setProperties(properties);
std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->initializePolyVox();
// As we create the first Polyvox entity, let's register its special shapePipeline factory:
PolyVoxPayload::registerShapePipeline();
return entity;
}
@ -737,7 +738,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
return;
}
if (!_pipelines[0]) {
/* if (!_pipelines[0]) {
gpu::ShaderPointer vertexShaders[2] = { gpu::Shader::createVertex(std::string(polyvox_vert)), gpu::Shader::createVertex(std::string(polyvox_fade_vert)) };
gpu::ShaderPointer pixelShaders[2] = { gpu::Shader::createPixel(std::string(polyvox_frag)), gpu::Shader::createPixel(std::string(polyvox_fade_frag)) };
@ -768,7 +769,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
_pipelines[i] = gpu::Pipeline::create(program, state);
_wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState);
}
}
}*/
if (!_vertexFormat) {
auto vf = std::make_shared<gpu::Stream::Format>();
@ -780,10 +781,10 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
// Pick correct Pipeline
bool wireframe = (render::ShapeKey(args->_globalShapeKey).isWireframe());
auto pipelineVariation = isFading() & 1;
auto pipeline = (wireframe ? _wireframePipelines[pipelineVariation]: _pipelines[pipelineVariation]);
batch.setPipeline(pipeline);
// bool wireframe = (render::ShapeKey(args->_globalShapeKey).isWireframe());
// auto pipelineVariation = isFading() & 1;
// auto pipeline = (wireframe ? _wireframePipelines[pipelineVariation]: _pipelines[pipelineVariation]);
// batch.setPipeline(pipeline);
Transform transform(voxelToWorldMatrix());
batch.setModelTransform(transform);
@ -826,13 +827,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
batch.setResourceTexture(2, DependencyManager::get<TextureCache>()->getWhiteTexture());
}
// Apply fade effect
/* if (args->_enableFade) {
FadeRenderJob::bindPerBatch(batch, render::ShapePipeline::Slot::MAP::FADE_MASK, render::ShapePipeline::Slot::BUFFER::FADE_PARAMETERS);
FadeRenderJob::bindPerItem(batch, pipeline.get(), glm::vec3(0, 0, 0), _fadeStartTime);
}*/
int voxelVolumeSizeLocation = pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize");
int voxelVolumeSizeLocation = args->_shapePipeline->pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize");
batch._glUniform3f(voxelVolumeSizeLocation, voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z);
batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)mesh->getNumIndices(), 0);
@ -863,6 +858,63 @@ void RenderablePolyVoxEntityItem::removeFromScene(const EntityItemPointer& self,
render::Item::clearID(_myItem);
}
uint8_t PolyVoxPayload::CUSTOM_PIPELINE_NUMBER = 0;
gpu::PipelinePointer PolyVoxPayload::_pipelines[2] = { nullptr, nullptr };
gpu::PipelinePointer PolyVoxPayload::_wireframePipelines[2] = { nullptr, nullptr };
render::ShapePipelinePointer PolyVoxPayload::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) {
if (!_pipelines[0]) {
gpu::ShaderPointer vertexShaders[2] = { gpu::Shader::createVertex(std::string(polyvox_vert)), gpu::Shader::createVertex(std::string(polyvox_fade_vert)) };
gpu::ShaderPointer pixelShaders[2] = { gpu::Shader::createPixel(std::string(polyvox_frag)), gpu::Shader::createPixel(std::string(polyvox_fade_frag)) };
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), render::ShapePipeline::Slot::BUFFER::FADE_PARAMETERS));
slotBindings.insert(gpu::Shader::Binding(std::string("xMap"), 0));
slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1));
slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2));
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), render::ShapePipeline::Slot::MAP::FADE_MASK));
auto state = std::make_shared<gpu::State>();
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
PrepareStencil::testMaskDrawShape(*state);
auto wireframeState = std::make_shared<gpu::State>();
wireframeState->setCullMode(gpu::State::CULL_BACK);
wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL);
wireframeState->setFillMode(gpu::State::FILL_LINE);
PrepareStencil::testMaskDrawShape(*wireframeState);
// Two sets of pipelines: normal and fading
for (auto i = 0; i < 2; i++) {
gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShaders[i], pixelShaders[i]);
gpu::Shader::makeProgram(*program, slotBindings);
_pipelines[i] = gpu::Pipeline::create(program, state);
_wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState);
}
}
if (key.isFaded()) {
if (key.isWireframe()) {
return std::make_shared<render::ShapePipeline>(_wireframePipelines[1], nullptr, nullptr, nullptr);
}
else {
return std::make_shared<render::ShapePipeline>(_pipelines[1], nullptr, nullptr, nullptr);
}
}
else {
if (key.isWireframe()) {
return std::make_shared<render::ShapePipeline>(_wireframePipelines[0], nullptr, nullptr, nullptr);
}
else {
return std::make_shared<render::ShapePipeline>(_pipelines[0], nullptr, nullptr, nullptr);
}
}
}
namespace render {
template <> const ItemKey payloadGetKey(const PolyVoxPayload::Pointer& payload) {
return ItemKey::Builder::opaqueShape();
@ -886,6 +938,10 @@ namespace render {
payload->_owner->getRenderableInterface()->render(args);
}
}
template <> const ShapeKey shapeGetShapeKey(const PolyVoxPayload::Pointer& payload) {
return ShapeKey::Builder().withCustom(PolyVoxPayload::CUSTOM_PIPELINE_NUMBER).build();
}
}
@ -1634,7 +1690,8 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() {
void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
EntityItem::locationChanged(tellPhysics);
if (!_pipelines[0] || !render::Item::isValidID(_myItem)) {
if (/*!_pipelines[0] ||*/ !render::Item::isValidID(_myItem)) {
return;
}
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();

View file

@ -28,6 +28,19 @@
class PolyVoxPayload {
public:
static uint8_t CUSTOM_PIPELINE_NUMBER;
static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key);
static void registerShapePipeline() {
if (!CUSTOM_PIPELINE_NUMBER) {
CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory);
}
}
static const int MATERIAL_GPU_SLOT = 3;
static gpu::PipelinePointer _pipelines[2];
static gpu::PipelinePointer _wireframePipelines[2];
PolyVoxPayload(EntityItemPointer owner) : _owner(owner), _bounds(AABox()) { }
typedef render::Payload<PolyVoxPayload> Payload;
typedef Payload::DataPointer Pointer;
@ -40,6 +53,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const PolyVoxPayload::Pointer& payload);
template <> const Item::Bound payloadGetBound(const PolyVoxPayload::Pointer& payload);
template <> void payloadRender(const PolyVoxPayload::Pointer& payload, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const PolyVoxPayload::Pointer& payload);
}
@ -168,12 +182,8 @@ private:
NetworkTexturePointer _yTexture;
NetworkTexturePointer _zTexture;
const int MATERIAL_GPU_SLOT = 3;
render::ItemID _myItem{ render::Item::INVALID_ITEM_ID };
static gpu::PipelinePointer _pipelines[2];
static gpu::PipelinePointer _wireframePipelines[2];
ShapeInfo _shapeInfo;
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;

View file

@ -555,10 +555,12 @@ void RenderableZoneEntityItemMeta::setProceduralUserData(QString userData) {
void RenderableZoneEntityItemMeta::render(RenderArgs* args) {
if (!_stage) {
_stage = args->_scene->getStage<LightStage>();
assert(_stage);
}
if (!_backgroundStage) {
_backgroundStage = args->_scene->getStage<BackgroundStage>();
assert(_backgroundStage);
}
{ // Sun

View file

@ -1,18 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// fragment shader
//
// 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
//
in vec4 _color;
out vec4 outFragColor;
void main(void) {
outFragColor = _color;
}

View file

@ -1,25 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// particle vertex shader
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include gpu/Inputs.slh@>
<@include gpu/Color.slh@>
<@include gpu/Transform.slh@>
<$declareStandardTransform()$>
out vec4 _color;
void main(void) {
// pass along the color
_color = colorToLinearRGBA(inColor);
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
}

View file

@ -182,7 +182,6 @@ GLuint compileProgram(const std::vector<GLuint>& glshaders, std::string& error)
filestream.close();
}
*/
delete[] temp;
glDeleteProgram(glprogram);
return 0;

View file

@ -197,8 +197,12 @@ public:
*lockWaitOut = (endLock - start);
}
std::vector<SharedNodePointer> nodes(_nodeHash.size());
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), nodes.begin(), [](const NodeHash::value_type& it) {
// Size of _nodeHash could change at any time,
// so reserve enough memory for the current size
// and then back insert all the nodes found
std::vector<SharedNodePointer> nodes;
nodes.reserve(_nodeHash.size());
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) {
return it.second;
});
auto endTransform = usecTimestampNow();

View file

@ -32,13 +32,6 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return nullptr;
}
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
// tiny shapes are not supported
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return nullptr;
}
DoubleHashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {

View file

@ -61,6 +61,8 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext,
// Background rendering decision
auto backgroundStage = renderContext->_scene->getStage<BackgroundStage>();
assert(backgroundStage);
model::SunSkyStagePointer background;
model::SkyboxPointer skybox;
if (backgroundStage->_currentFrame._backgrounds.size()) {

View file

@ -433,6 +433,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
}
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
assert(lightStage->getNumLights() > 0);
auto lightAndShadow = lightStage->getLightAndShadow(0);
const auto& globalShadow = lightAndShadow.second;

View file

@ -429,6 +429,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input
// Prepare a fresh Light Frame
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
lightStage->_currentFrame.clear();
}
@ -493,6 +494,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
// Global directional light and ambient pass
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
assert(lightStage->getNumLights() > 0);
auto lightAndShadow = lightStage->getLightAndShadow(0);
const auto& globalShadow = lightAndShadow.second;

View file

@ -575,6 +575,7 @@ void LightClusteringPass::run(const render::RenderContextPointer& renderContext,
// From the LightStage and the current frame, update the light cluster Grid
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
_lightClusters->updateLightStage(lightStage);
_lightClusters->updateLightFrame(lightStage->_currentFrame, lightingModel->isPointLightEnabled(), lightingModel->isSpotLightEnabled());

View file

@ -56,6 +56,7 @@ LightPayload::~LightPayload() {
void LightPayload::render(RenderArgs* args) {
if (!_stage) {
_stage = args->_scene->getStage<LightStage>();
assert(_stage);
}
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_index)) {
@ -124,6 +125,7 @@ KeyLightPayload::~KeyLightPayload() {
void KeyLightPayload::render(RenderArgs* args) {
if (!_stage) {
_stage = args->_scene->getStage<LightStage>();
assert(_stage);
}
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_index)) {

View file

@ -259,7 +259,7 @@ void MeshPartPayload::render(RenderArgs* args) {
gpu::Batch& batch = *(args->_batch);
auto locations = args->_pipeline->locations;
auto locations = args->_shapePipeline->locations;
assert(locations);
// Bind the model transform and the skinCLusterMatrices if needed
@ -559,7 +559,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
}
gpu::Batch& batch = *(args->_batch);
auto locations = args->_pipeline->locations;
auto locations = args->_shapePipeline->locations;
assert(locations);
bindTransform(batch, locations, args->_renderMode);

View file

@ -42,7 +42,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext,
assert(renderContext->args->hasViewFrustum());
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
LightStage::Index globalLightIndex { 0 };
const auto globalLight = lightStage->getLight(globalLightIndex);
@ -76,7 +76,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext,
std::vector<ShapeKey> skinnedShapeKeys{};
// Iterate through all inShapes and render the unskinned
args->_pipeline = shadowPipeline;
args->_shapePipeline = shadowPipeline;
batch.setPipeline(shadowPipeline->pipeline);
for (auto items : inShapes) {
if (items.first.isSkinned()) {
@ -87,13 +87,13 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext,
}
// Reiterate to render the skinned
args->_pipeline = shadowSkinnedPipeline;
args->_shapePipeline = shadowSkinnedPipeline;
batch.setPipeline(shadowSkinnedPipeline->pipeline);
for (const auto& key : skinnedShapeKeys) {
renderItems(renderContext, inShapes.at(key));
}
args->_pipeline = nullptr;
args->_shapePipeline = nullptr;
args->_batch = nullptr;
});
}
@ -163,6 +163,7 @@ void RenderShadowTask::configure(const Config& configuration) {
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) {
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
const auto globalShadow = lightStage->getShadow(0);
// Cache old render args

View file

@ -532,7 +532,8 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo
auto lightStage = renderContext->_scene->getStage<LightStage>("LIGHT_STAGE");
auto lightStage = renderContext->_scene->getStage<LightStage>();
assert(lightStage);
// const auto light = DependencyManager::get<DeferredLightingEffect>()->getLightStage()->getLight(0);
const auto light = lightStage->getLight(0);

View file

@ -52,19 +52,21 @@ void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& oupu
}
void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) {
auto backgroundStage = context->_scene->getStage<BackgroundStage>("BACKGROUND_STAGE");
auto backgroundStage = context->_scene->getStage<BackgroundStage>();
assert(backgroundStage);
backgroundStage->_currentFrame.clear();
// call render in the correct order first...
render::renderItems(context, inputs);
// Finally add the default lights and background:
auto lightStage = context->_scene->getStage<LightStage>("LIGHT_STAGE");
auto lightStage = context->_scene->getStage<LightStage>();
assert(lightStage);
lightStage->_currentFrame.pushSunLight(0);
lightStage->_currentFrame.pushAmbientLight(0);
backgroundStage->_currentFrame.pushBackground(0);
}
const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() {

View file

@ -103,7 +103,7 @@ namespace render {
std::shared_ptr<gpu::Context> _context;
std::shared_ptr<gpu::Framebuffer> _blitFramebuffer;
std::shared_ptr<render::ShapePipeline> _pipeline;
std::shared_ptr<render::ShapePipeline> _shapePipeline;
QSharedPointer<QObject> _renderData;
std::stack<ViewFrustum> _viewFrustums;
glm::ivec4 _viewport { 0.0f, 0.0f, 1.0f, 1.0f };

View file

@ -43,12 +43,19 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons
assert(item.getKey().isShape());
auto key = item.getShapeKey() | globalKey;
if (key.isValid() && !key.hasOwnPipeline()) {
args->_pipeline = shapeContext->pickPipeline(args, key);
if (args->_pipeline) {
args->_pipeline->prepareShapeItem(args, key, item);
args->_shapePipeline = shapeContext->pickPipeline(args, key);
/* if (!args->_shapePipeline) {
if (key.isCustom()) {
if (item.defineCustomShapePipeline(*shapeContext, key)) {
args->_shapePipeline = shapeContext->pickPipeline(args, key);
}
}
}*/
if (args->_shapePipeline) {
args->_shapePipeline->prepareShapeItem(args, key, item);
item.render(args);
}
args->_pipeline = nullptr;
}
args->_shapePipeline = nullptr;
} else if (key.hasOwnPipeline()) {
item.render(args);
} else {
@ -110,16 +117,28 @@ void render::renderStateSortShapes(const RenderContextPointer& renderContext,
// Then render
for (auto& pipelineKey : sortedPipelines) {
auto& bucket = sortedShapes[pipelineKey];
args->_pipeline = shapeContext->pickPipeline(args, pipelineKey);
if (!args->_pipeline) {
args->_shapePipeline = shapeContext->pickPipeline(args, pipelineKey);
if (!args->_shapePipeline) {
/* if (pipelineKey.isCustom()) {
if (bucket.front().defineCustomShapePipeline(*shapeContext, pipelineKey)) {
args->_shapePipeline = shapeContext->pickPipeline(args, pipelineKey);
if (!args->_shapePipeline) {
} else {
continue;
}
} else {
continue;
}
}*/
continue;
}
for (auto& item : bucket) {
args->_pipeline->prepareShapeItem(args, pipelineKey, item);
args->_shapePipeline->prepareShapeItem(args, pipelineKey, item);
item.render(args);
}
}
args->_pipeline = nullptr;
args->_shapePipeline = nullptr;
for (auto& item : ownPipelineBucket) {
item.render(args);
}

View file

@ -17,6 +17,15 @@
using namespace render;
ShapePipeline::CustomFactoryMap ShapePipeline::_globalCustomFactoryMap;
ShapePipeline::CustomKey ShapePipeline::registerCustomShapePipelineFactory(CustomFactory factory) {
ShapePipeline::CustomKey custom = (ShapePipeline::CustomKey) _globalCustomFactoryMap.size() + 1;
_globalCustomFactoryMap[custom] = factory;
return custom;
}
void ShapePipeline::prepare(gpu::Batch& batch, RenderArgs* args) {
if (_batchSetter) {
_batchSetter(*this, batch, args);
@ -35,7 +44,7 @@ ShapeKey::Filter::Builder::Builder() {
_mask.set(INVALID);
}
void ShapePlumber::addPipelineHelper(const Filter& filter, ShapeKey key, int bit, const PipelinePointer& pipeline) {
void ShapePlumber::addPipelineHelper(const Filter& filter, ShapeKey key, int bit, const PipelinePointer& pipeline) const {
// Iterate over all keys
if (bit < (int)ShapeKey::FlagBit::NUM_FLAGS) {
addPipelineHelper(filter, key, bit + 1, pipeline);
@ -114,10 +123,22 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke
auto pipelineIterator = _pipelineMap.find(key);
if (pipelineIterator == _pipelineMap.end()) {
// The first time we can't find a pipeline, we should log it
// The first time we can't find a pipeline, we should try things to solve that
if (_missingKeys.find(key) == _missingKeys.end()) {
_missingKeys.insert(key);
qCDebug(renderlogging) << "Couldn't find a pipeline for" << key;
if (key.isCustom()) {
auto factoryIt = ShapePipeline::_globalCustomFactoryMap.find(key.getCustom());
if ((factoryIt != ShapePipeline::_globalCustomFactoryMap.end()) && (factoryIt)->second) {
// found a factory for the custom key, can now generate a shape pipeline for this case:
addPipelineHelper(Filter(key), key, 0, (factoryIt)->second(*this, key));
return pickPipeline(args, key);
} else {
qCDebug(renderlogging) << "ShapePlumber::Couldn't find a custom pipeline factory for " << key.getCustom() << " key is: " << key;
}
}
_missingKeys.insert(key);
qCDebug(renderlogging) << "ShapePlumber::Couldn't find a pipeline for" << key;
}
return PipelinePointer(nullptr);
}

View file

@ -40,7 +40,19 @@ public:
OWN_PIPELINE,
INVALID,
CUSTOM_0,
CUSTOM_1,
CUSTOM_2,
CUSTOM_3,
CUSTOM_4,
CUSTOM_5,
CUSTOM_6,
CUSTOM_7,
NUM_FLAGS, // Not a valid flag
CUSTOM_MASK = (0xFF << CUSTOM_0),
};
using Flags = std::bitset<NUM_FLAGS>;
@ -76,6 +88,8 @@ public:
Builder& withOwnPipeline() { _flags.set(OWN_PIPELINE); return (*this); }
Builder& invalidate() { _flags.set(INVALID); return (*this); }
Builder& withCustom(uint8_t custom) { _flags &= (~CUSTOM_MASK); _flags |= (custom << CUSTOM_0); return (*this); }
static const ShapeKey ownPipeline() { return Builder().withOwnPipeline(); }
static const ShapeKey invalid() { return Builder().invalidate(); }
@ -133,6 +147,9 @@ public:
Builder& withFade() { _flags.set(FADE); _mask.set(FADE); return (*this); }
Builder& withoutFade() { _flags.reset(FADE); _mask.set(FADE); return (*this); }
Builder& withCustom(uint8_t custom) { _flags &= (~CUSTOM_MASK); _flags |= (custom << CUSTOM_0); _mask |= (CUSTOM_MASK); return (*this); }
Builder& withoutCustom() { _flags &= (~CUSTOM_MASK); _mask |= (CUSTOM_MASK); return (*this); }
protected:
friend class Filter;
Flags _flags{0};
@ -162,6 +179,9 @@ public:
bool hasOwnPipeline() const { return _flags[OWN_PIPELINE]; }
bool isValid() const { return !_flags[INVALID]; }
uint8_t getCustom() const { return (_flags.to_ulong() & CUSTOM_MASK) >> CUSTOM_0; }
bool isCustom() const { return (_flags.to_ulong() & CUSTOM_MASK); }
// Comparator for use in stl containers
class Hash {
public:
@ -276,6 +296,15 @@ protected:
BatchSetter _batchSetter;
ItemSetter _itemSetter;
public:
using CustomKey = uint8_t;
using CustomFactory = std::function<std::shared_ptr<ShapePipeline> (const ShapePlumber& plumber, const ShapeKey& key)>;
using CustomFactoryMap = std::map<CustomKey, CustomFactory>;
static CustomFactoryMap _globalCustomFactoryMap;
static CustomKey registerCustomShapePipelineFactory(CustomFactory factory);
};
using ShapePipelinePointer = std::shared_ptr<ShapePipeline>;
@ -300,13 +329,14 @@ public:
const PipelinePointer pickPipeline(RenderArgs* args, const Key& key) const;
protected:
void addPipelineHelper(const Filter& filter, Key key, int bit, const PipelinePointer& pipeline);
PipelineMap _pipelineMap;
void addPipelineHelper(const Filter& filter, Key key, int bit, const PipelinePointer& pipeline) const;
mutable PipelineMap _pipelineMap;
private:
mutable std::unordered_set<Key, Key::Hash, Key::KeyEqual> _missingKeys;
};
using ShapePlumberPointer = std::shared_ptr<ShapePlumber>;
}

View file

@ -15,6 +15,9 @@
#include "NumericalConstants.h" // for MILLIMETERS_PER_METER
// Bullet doesn't support arbitrarily small shapes
const float MIN_HALF_EXTENT = 0.005f; // 0.5 cm
void ShapeInfo::clear() {
_url.clear();
_pointCollection.clear();
@ -26,14 +29,20 @@ void ShapeInfo::clear() {
}
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
_url = "";
_type = type;
_halfExtents = halfExtents;
setHalfExtents(halfExtents);
switch(type) {
case SHAPE_TYPE_NONE:
_halfExtents = glm::vec3(0.0f);
break;
case SHAPE_TYPE_BOX:
case SHAPE_TYPE_SPHERE:
break;
case SHAPE_TYPE_SPHERE: {
float radius = glm::length(halfExtents) / SQUARE_ROOT_OF_3;
radius = glm::max(radius, MIN_HALF_EXTENT);
_halfExtents = glm::vec3(radius);
}
break;
case SHAPE_TYPE_COMPOUND:
case SHAPE_TYPE_STATIC_MESH:
@ -48,14 +57,15 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_url = "";
_type = SHAPE_TYPE_BOX;
_halfExtents = halfExtents;
setHalfExtents(halfExtents);
_doubleHashKey.clear();
}
void ShapeInfo::setSphere(float radius) {
_url = "";
_type = SHAPE_TYPE_SPHERE;
_halfExtents = glm::vec3(radius, radius, radius);
radius = glm::max(radius, MIN_HALF_EXTENT);
_halfExtents = glm::vec3(radius);
_doubleHashKey.clear();
}
@ -68,6 +78,8 @@ void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollec
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
_url = "";
_type = SHAPE_TYPE_CAPSULE_Y;
radius = glm::max(radius, MIN_HALF_EXTENT);
halfHeight = glm::max(halfHeight, 0.0f);
_halfExtents = glm::vec3(radius, halfHeight, radius);
_doubleHashKey.clear();
}
@ -239,3 +251,7 @@ const DoubleHashKey& ShapeInfo::getHash() const {
}
return _doubleHashKey;
}
void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) {
_halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT));
}

View file

@ -89,6 +89,8 @@ public:
const DoubleHashKey& getHash() const;
protected:
void setHalfExtents(const glm::vec3& halfExtents);
QUrl _url; // url for model of convex collision hulls
PointCollection _pointCollection;
TriangleIndices _triangleIndices;

View file

@ -831,7 +831,9 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
mouseEvent->screenPos(), mouseEvent->button(),
mouseEvent->buttons(), mouseEvent->modifiers());
if (event->type() == QEvent::MouseMove) {
_qmlContext->setContextProperty("lastMousePosition", transformedPos);
// TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install
// need to investigate into why this crash is happening.
//_qmlContext->setContextProperty("lastMousePosition", transformedPos);
}
mappedEvent.ignore();
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {

View file

@ -14,7 +14,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller */
/* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller,
isInEditMode, HMD */
(function() { // BEGIN LOCAL_SCOPE
@ -22,6 +23,8 @@
Script.include("/~/system/libraries/utils.js");
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
var DELAY_FOR_30HZ = 33; // milliseconds
var ZERO_VEC3 = {
x: 0,
y: 0,
@ -46,7 +49,7 @@ var ACTION_TTL = 10; // seconds
function getTag() {
return "grab-" + MyAvatar.sessionUUID;
}
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
@ -411,10 +414,15 @@ Grabber.prototype.pressEvent = function(event) {
};
Grabber.prototype.releaseEvent = function(event) {
if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) {
if (event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) {
return;
}
if (this.moveEventTimer) {
Script.clearTimeout(this.moveEventTimer);
this.moveEventTimer = null;
}
if (this.isGrabbing) {
// this.deactivateEntity(this.entityID);
this.isGrabbing = false;
@ -439,12 +447,28 @@ Grabber.prototype.releaseEvent = function(event) {
}
};
Grabber.prototype.scheduleMouseMoveProcessor = function(event) {
var _this = this;
if (!this.moveEventTimer) {
this.moveEventTimer = Script.setTimeout(function() {
_this.moveEventProcess();
}, DELAY_FOR_30HZ);
}
};
Grabber.prototype.moveEvent = function(event) {
// during the handling of the event, do as little as possible. We save the updated mouse position,
// and start a timer to react to the change. If more changes arrive before the timer fires, only
// the last update will be considered. This is done to avoid backing-up Qt's event queue.
if (!this.isGrabbing) {
return;
}
mouse.updateDrag(event);
this.scheduleMouseMoveProcessor();
};
Grabber.prototype.moveEventProcess = function() {
this.moveEventTimer = null;
// see if something added/restored gravity
var entityProperties = Entities.getEntityProperties(this.entityID);
if (!entityProperties || !entityProperties.gravity) {
@ -489,7 +513,7 @@ Grabber.prototype.moveEvent = function(event) {
} else {
var newPointOnPlane;
if (this.mode === "verticalCylinder") {
// for this mode we recompute the plane based on current Camera
var planeNormal = Quat.getForward(Camera.getOrientation());
@ -505,7 +529,7 @@ Grabber.prototype.moveEvent = function(event) {
};
} else {
newPointOnPlane = mouseIntersectionWithPlane(
this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance);
var relativePosition = Vec3.subtract(newPointOnPlane, cameraPosition);
@ -538,6 +562,8 @@ Grabber.prototype.moveEvent = function(event) {
} else {
Entities.updateAction(this.entityID, this.actionID, actionArgs);
}
this.scheduleMouseMoveProcessor();
};
Grabber.prototype.keyReleaseEvent = function(event) {

View file

@ -1010,7 +1010,6 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Entity List...",
shortcutKey: "CTRL+META+L",
afterItem: "Entities",
grouping: "Advanced"
});
@ -1041,7 +1040,6 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Allow Selecting of Large Models",
shortcutKey: "CTRL+META+L",
afterItem: GRABBABLE_ENTITIES_MENU_ITEM,
isCheckable: true,
isChecked: true,
@ -1050,7 +1048,6 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Allow Selecting of Small Models",
shortcutKey: "CTRL+META+S",
afterItem: "Allow Selecting of Large Models",
isCheckable: true,
isChecked: true,
@ -1059,7 +1056,6 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Allow Selecting of Lights",
shortcutKey: "CTRL+SHIFT+META+L",
afterItem: "Allow Selecting of Small Models",
isCheckable: true,
grouping: "Advanced"
@ -1067,14 +1063,12 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Select All Entities In Box",
shortcutKey: "CTRL+SHIFT+META+A",
afterItem: "Allow Selecting of Lights",
grouping: "Advanced"
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Select All Entities Touching Box",
shortcutKey: "CTRL+SHIFT+META+T",
afterItem: "Select All Entities In Box",
grouping: "Advanced"
});
@ -1082,21 +1076,18 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Export Entities",
shortcutKey: "CTRL+META+E",
afterItem: "Entities",
grouping: "Advanced"
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Import Entities",
shortcutKey: "CTRL+META+I",
afterItem: "Export Entities",
grouping: "Advanced"
});
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Import Entities from URL",
shortcutKey: "CTRL+META+U",
afterItem: "Import Entities",
grouping: "Advanced"
});

View file

@ -14,8 +14,6 @@ Script.include(Script.resolvePath("../libraries/utils.js"));
Script.include(Script.resolvePath("../libraries/controllers.js"));
Script.include(Script.resolvePath("../libraries/Xform.js"));
var VEC3_ZERO = {x: 0, y: 0, z: 0};
var X_AXIS = {x: 1, y: 0, z: 0};
var Y_AXIS = {x: 0, y: 1, z: 0};
var DEFAULT_DPI = 34;
var DEFAULT_WIDTH = 0.4375;
@ -25,12 +23,13 @@ var CAMERA_MATRIX = -7;
var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0};
var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0};
var ROT_LANDSCAPE_WINDOW = {x: 0.0, y: 0.0, z: 0.0, w: 0};
var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1};
var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 };
var INCHES_TO_METERS = 1 / 39.3701;
var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
var NO_HANDS = -1;
var DELAY_FOR_30HZ = 33; // milliseconds
// will need to be recaclulated if dimensions of fbx model change.
var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269};
@ -561,9 +560,29 @@ function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) {
}
}
WebTablet.prototype.scheduleMouseMoveProcessor = function() {
var _this = this;
if (!this.moveEventTimer) {
this.moveEventTimer = Script.setTimeout(function() {
_this.mouseMoveProcessor();
}, DELAY_FOR_30HZ);
}
};
WebTablet.prototype.mouseMoveEvent = function (event) {
if (this.dragging) {
var pickRay = Camera.computePickRay(event.x, event.y);
this.currentMouse = {
x: event.x,
y: event.y
};
this.scheduleMouseMoveProcessor();
}
};
WebTablet.prototype.mouseMoveProcessor = function () {
this.moveEventTimer = null;
if (this.dragging) {
var pickRay = Camera.computePickRay(this.currentMouse.x, this.currentMouse.y);
// transform pickRay into camera local coordinates
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
@ -582,6 +601,7 @@ WebTablet.prototype.mouseMoveEvent = function (event) {
localPosition: localPosition
});
}
this.scheduleMouseMoveProcessor();
}
};

View file

@ -862,6 +862,9 @@ function avatarDisconnected(nodeID) {
function clearLocalQMLDataAndClosePAL() {
sendToQml({ method: 'clearLocalQMLData' });
if (onPalScreen) {
tablet.gotoHomeScreen();
}
}
function avatarAdded(avatarID) {

View file

@ -57,8 +57,6 @@
#include <render-utils/model_lightmap_specular_map_frag.h>
#include <render-utils/model_translucent_frag.h>
#include <entities-renderer/untextured_particle_frag.h>
#include <entities-renderer/untextured_particle_vert.h>
#include <entities-renderer/textured_particle_frag.h>
#include <entities-renderer/textured_particle_vert.h>
@ -172,7 +170,6 @@ void QTestWindow::draw() {
testShaderBuild(skin_model_normal_map_vert, model_translucent_frag);
testShaderBuild(model_shadow_vert, model_shadow_frag);
testShaderBuild(untextured_particle_vert, untextured_particle_frag);
testShaderBuild(textured_particle_vert, textured_particle_frag);
/* FIXME: Bring back the ssao shader tests
testShaderBuild(gaussian_blur_vertical_vert, gaussian_blur_frag);

View file

@ -17,8 +17,8 @@ set_target_properties(ac-client PROPERTIES FOLDER "Tools")
add_subdirectory(skeleton-dump)
set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools")
add_subdirectory(atp-get)
set_target_properties(atp-get PROPERTIES FOLDER "Tools")
add_subdirectory(atp-client)
set_target_properties(atp-client PROPERTIES FOLDER "Tools")
add_subdirectory(oven)
set_target_properties(oven PROPERTIES FOLDER "Tools")

View file

@ -14,6 +14,7 @@
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <NetworkLogging.h>
#include <NetworkingConstants.h>
#include <SharedLogging.h>
#include <AddressManager.h>
#include <DependencyManager.h>
@ -33,6 +34,9 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
const QCommandLineOption verboseOutput("v", "verbose output");
parser.addOption(verboseOutput);
const QCommandLineOption authOption("u", "set usename and pass", "username:password");
parser.addOption(authOption);
const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1");
parser.addOption(domainAddressOption);
@ -81,6 +85,18 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
listenPort = parser.value(listenPortOption).toInt();
}
if (parser.isSet(authOption)) {
QStringList pieces = parser.value(authOption).split(":");
if (pieces.size() != 2) {
qDebug() << "-u should be followed by username:password";
parser.showHelp();
Q_UNREACHABLE();
}
_username = pieces[0];
_password = pieces[1];
}
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
@ -88,6 +104,9 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
DependencyManager::set<AddressManager>();
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
auto nodeList = DependencyManager::get<NodeList>();
@ -96,27 +115,45 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// start the nodeThread so its event loop is running
// start the nodeThread so its event loop is running
// (must happen after the checkin timer is created with the nodelist as it's parent)
nodeList->startThread();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
// connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
// connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ACClientApp::domainConnectionRefused);
connect(nodeList.data(), &NodeList::nodeAdded, this, &ACClientApp::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &ACClientApp::nodeKilled);
connect(nodeList.data(), &NodeList::nodeActivated, this, &ACClientApp::nodeActivated);
// connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
// connect(nodeList.data(), &NodeList::uuidChanged, this, &ACClientApp::setSessionUUID);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ACClientApp::notifyPacketVersionMismatch);
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
if (_verbose) {
QString username = accountManager->getAccountInfo().getUsername();
qDebug() << "cached username is" << username << ", isLoggedIn =" << accountManager->isLoggedIn();
}
if (!_username.isEmpty()) {
connect(accountManager.data(), &AccountManager::newKeypair, this, [&](){
if (_verbose) {
qDebug() << "new keypair has been created.";
}
});
connect(accountManager.data(), &AccountManager::loginComplete, this, [&](){
if (_verbose) {
qDebug() << "login successful";
}
});
connect(accountManager.data(), &AccountManager::loginFailed, this, [&](){
qDebug() << "login failed.";
});
accountManager->requestAccessToken(_username, _password);
}
DependencyManager::get<AddressManager>()->handleLookupString(domainServerAddress, false);
QTimer* doTimer = new QTimer(this);

View file

@ -47,6 +47,9 @@ private:
bool _sawAvatarMixer { false };
bool _sawAssetServer { false };
bool _sawMessagesMixer { false };
QString _username;
QString _password;
};
#endif //hifi_ACClientApp_h

View file

@ -1,4 +1,4 @@
set(TARGET_NAME atp-get)
set(TARGET_NAME atp-client)
setup_hifi_project(Core Widgets)
setup_memory_debugger()
link_hifi_libraries(shared networking)

View file

@ -0,0 +1,406 @@
//
// ATPClientApp.cpp
// tools/atp-client/src
//
// Created by Seth Alves on 2017-3-15
// 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 <QDataStream>
#include <QTextStream>
#include <QThread>
#include <QFile>
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <NetworkLogging.h>
#include <NetworkingConstants.h>
#include <SharedLogging.h>
#include <AddressManager.h>
#include <DependencyManager.h>
#include <SettingHandle.h>
#include <AssetUpload.h>
#include "ATPClientApp.h"
#define HIGH_FIDELITY_ATP_CLIENT_USER_AGENT "Mozilla/5.0 (HighFidelityATPClient)"
#define TIMEOUT_MILLISECONDS 8000
ATPClientApp::ATPClientApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity ATP-Client");
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption verboseOutput("v", "verbose output");
parser.addOption(verboseOutput);
const QCommandLineOption uploadOption("T", "upload local file", "local-file-to-send");
parser.addOption(uploadOption);
const QCommandLineOption authOption("u", "set usename and pass", "username:password");
parser.addOption(authOption);
const QCommandLineOption outputFilenameOption("o", "output filename", "output-file-name");
parser.addOption(outputFilenameOption);
const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1");
parser.addOption(domainAddressOption);
const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT));
parser.addOption(listenPortOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();
}
_verbose = parser.isSet(verboseOutput);
if (!_verbose) {
QLoggingCategory::setFilterRules("qt.network.ssl.warning=false");
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtWarningMsg, false);
}
QStringList posArgs = parser.positionalArguments();
if (posArgs.size() != 1) {
qDebug() << "give remote url argument";
parser.showHelp();
Q_UNREACHABLE();
}
_url = QUrl(posArgs[0]);
if (_url.scheme() != "atp") {
qDebug() << "url should start with atp:";
parser.showHelp();
Q_UNREACHABLE();
}
int domainPort = 40103;
if (_url.port() != -1) {
domainPort = _url.port();
}
if (parser.isSet(outputFilenameOption)) {
_localOutputFile = parser.value(outputFilenameOption);
}
if (parser.isSet(uploadOption)) {
_localUploadFile = parser.value(uploadOption);
}
if (parser.isSet(authOption)) {
QStringList pieces = parser.value(authOption).split(":");
if (pieces.size() != 2) {
qDebug() << "-u should be followed by username:password";
parser.showHelp();
Q_UNREACHABLE();
}
_username = pieces[0];
_password = pieces[1];
_waitingForLogin = true;
}
if (parser.isSet(listenPortOption)) {
_listenPort = parser.value(listenPortOption).toInt();
}
_domainServerAddress = QString("127.0.0.1") + ":" + QString::number(domainPort);
if (parser.isSet(domainAddressOption)) {
_domainServerAddress = parser.value(domainAddressOption);
} else if (!_url.host().isEmpty()) {
QUrl domainURL;
domainURL.setScheme("hifi");
domainURL.setHost(_url.host());
_domainServerAddress = domainURL.toString();
}
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<AccountManager>([&]{ return QString(HIGH_FIDELITY_ATP_CLIENT_USER_AGENT); });
DependencyManager::set<AddressManager>();
DependencyManager::set<NodeList>(NodeType::Agent, _listenPort);
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
auto nodeList = DependencyManager::get<NodeList>();
// setup a timer for domain-server check ins
_domainCheckInTimer = new QTimer(nodeList.data());
connect(_domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
_domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// start the nodeThread so its event loop is running
// (must happen after the checkin timer is created with the nodelist as it's parent)
nodeList->startThread();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPClientApp::domainConnectionRefused);
connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPClientApp::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &ATPClientApp::nodeKilled);
connect(nodeList.data(), &NodeList::nodeActivated, this, &ATPClientApp::nodeActivated);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ATPClientApp::notifyPacketVersionMismatch);
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
if (_verbose) {
QString username = accountManager->getAccountInfo().getUsername();
qDebug() << "cached username is" << username << ", isLoggedIn =" << accountManager->isLoggedIn();
}
if (!_username.isEmpty()) {
connect(accountManager.data(), &AccountManager::newKeypair, this, [&](){
if (_verbose) {
qDebug() << "new keypair has been created.";
}
});
connect(accountManager.data(), &AccountManager::loginComplete, this, [&](){
if (_verbose) {
qDebug() << "login successful";
}
_waitingForLogin = false;
go();
});
connect(accountManager.data(), &AccountManager::loginFailed, this, [&](){
qDebug() << "login failed.";
_waitingForLogin = false;
go();
});
accountManager->requestAccessToken(_username, _password);
}
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->init();
if (_verbose) {
qDebug() << "domain-server address is" << _domainServerAddress;
}
DependencyManager::get<AddressManager>()->handleLookupString(_domainServerAddress, false);
QTimer* _timeoutTimer = new QTimer(this);
_timeoutTimer->setSingleShot(true);
connect(_timeoutTimer, &QTimer::timeout, this, &ATPClientApp::timedOut);
_timeoutTimer->start(TIMEOUT_MILLISECONDS);
}
ATPClientApp::~ATPClientApp() {
if (_domainCheckInTimer) {
QMetaObject::invokeMethod(_domainCheckInTimer, "deleteLater", Qt::QueuedConnection);
}
if (_timeoutTimer) {
QMetaObject::invokeMethod(_timeoutTimer, "deleteLater", Qt::QueuedConnection);
}
}
void ATPClientApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
// this is non-fatal if we are trying to get an authenticated connection -- it will be retried.
if (_verbose) {
qDebug() << "domainConnectionRefused";
}
}
void ATPClientApp::domainChanged(const QString& domainHostname) {
if (_verbose) {
qDebug() << "domainChanged";
}
}
void ATPClientApp::nodeAdded(SharedNodePointer node) {
if (_verbose) {
qDebug() << "node added: " << node->getType();
}
}
void ATPClientApp::nodeActivated(SharedNodePointer node) {
if (!_waitingForLogin && node->getType() == NodeType::AssetServer) {
_waitingForNode = false;
go();
}
}
void ATPClientApp::go() {
if (_waitingForLogin || _waitingForNode) {
return;
}
auto path = _url.path();
if (_verbose) {
qDebug() << "path is " << path;
}
if (!_localUploadFile.isEmpty()) {
uploadAsset();
} else if (path == "/") {
listAssets();
} else {
lookupAsset();
}
}
void ATPClientApp::nodeKilled(SharedNodePointer node) {
if (_verbose) {
qDebug() << "nodeKilled" << node->getType();
}
}
void ATPClientApp::timedOut() {
finish(1);
}
void ATPClientApp::notifyPacketVersionMismatch() {
if (_verbose) {
qDebug() << "packet version mismatch";
}
finish(1);
}
void ATPClientApp::uploadAsset() {
auto path = _url.path();
if (path == "/") {
qDebug() << "cannot upload to path of /";
QCoreApplication::exit(1);
}
auto upload = DependencyManager::get<AssetClient>()->createUpload(_localUploadFile);
QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable {
if (upload->getError() != AssetUpload::NoError) {
qDebug() << "upload failed: " << upload->getErrorString();
} else {
setMapping(hash);
}
upload->deleteLater();
});
upload->start();
}
void ATPClientApp::setMapping(QString hash) {
auto path = _url.path();
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createSetMappingRequest(path, hash);
connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable {
if (request->getError() != SetMappingRequest::NoError) {
qDebug() << "upload succeeded, but couldn't set mapping: " << request->getErrorString();
} else if (_verbose) {
qDebug() << "mapping set.";
}
request->deleteLater();
finish(0);
});
request->start();
}
void ATPClientApp::listAssets() {
auto request = DependencyManager::get<AssetClient>()->createGetAllMappingsRequest();
QObject::connect(request, &GetAllMappingsRequest::finished, this, [=](GetAllMappingsRequest* request) mutable {
auto result = request->getError();
if (result == GetAllMappingsRequest::NotFound) {
qDebug() << "not found: " << request->getErrorString();
} else if (result == GetAllMappingsRequest::NoError) {
auto mappings = request->getMappings();
for (auto& kv : mappings ) {
qDebug() << kv.first << kv.second;
}
} else {
qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString();
}
request->deleteLater();
finish(0);
});
request->start();
}
void ATPClientApp::lookupAsset() {
auto path = _url.path();
auto request = DependencyManager::get<AssetClient>()->createGetMappingRequest(path);
QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable {
auto result = request->getError();
if (result == GetMappingRequest::NotFound) {
qDebug() << "not found: " << request->getErrorString();
} else if (result == GetMappingRequest::NoError) {
qDebug() << "found, hash is " << request->getHash();
download(request->getHash());
} else {
qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString();
}
request->deleteLater();
});
request->start();
}
void ATPClientApp::download(AssetHash hash) {
auto assetClient = DependencyManager::get<AssetClient>();
auto assetRequest = new AssetRequest(hash);
connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) mutable {
Q_ASSERT(request->getState() == AssetRequest::Finished);
if (request->getError() == AssetRequest::Error::NoError) {
QString data = QString::fromUtf8(request->getData());
if (_localOutputFile == "" || _localOutputFile == "-") {
QTextStream cout(stdout);
cout << data;
} else {
QFile outputHandle(_localOutputFile);
if (outputHandle.open(QIODevice::ReadWrite)) {
QTextStream stream( &outputHandle );
stream << data;
} else {
qDebug() << "couldn't open output file:" << _localOutputFile;
}
}
}
request->deleteLater();
finish(0);
});
assetRequest->start();
}
void ATPClientApp::finish(int exitCode) {
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
QCoreApplication::exit(exitCode);
}

View file

@ -1,6 +1,6 @@
//
// ATPGetApp.h
// tools/atp-get/src
// ATPClientApp.h
// tools/atp-client/src
//
// Created by Seth Alves on 2017-3-15
// Copyright 2017 High Fidelity, Inc.
@ -10,8 +10,8 @@
//
#ifndef hifi_ATPGetApp_h
#define hifi_ATPGetApp_h
#ifndef hifi_ATPClientApp_h
#define hifi_ATPClientApp_h
#include <QApplication>
#include <udt/Constants.h>
@ -23,11 +23,11 @@
#include <MappingRequest.h>
class ATPGetApp : public QCoreApplication {
class ATPClientApp : public QCoreApplication {
Q_OBJECT
public:
ATPGetApp(int argc, char* argv[]);
~ATPGetApp();
ATPClientApp(int argc, char* argv[]);
~ATPClientApp();
private slots:
void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo);
@ -38,15 +38,33 @@ private slots:
void notifyPacketVersionMismatch();
private:
void go();
NodeList* _nodeList;
void timedOut();
void lookup();
void uploadAsset();
void setMapping(QString hash);
void lookupAsset();
void listAssets();
void download(AssetHash hash);
void finish(int exitCode);
bool _verbose;
QUrl _url;
QString _localOutputFile;
QString _localUploadFile;
int _listenPort { INVALID_PORT };
QString _domainServerAddress;
QString _username;
QString _password;
bool _waitingForLogin { false };
bool _waitingForNode { true };
QTimer* _domainCheckInTimer { nullptr };
QTimer* _timeoutTimer { nullptr };
};
#endif // hifi_ATPGetApp_h
#endif // hifi_ATPClientApp_h

View file

@ -1,6 +1,6 @@
//
// main.cpp
// tools/atp-get/src
// tools/atp-client/src
//
// Created by Seth Alves on 2017-3-15
// Copyright 2017 High Fidelity, Inc.
@ -15,7 +15,7 @@
#include <BuildInfo.h>
#include "ATPGetApp.h"
#include "ATPClientApp.h"
using namespace std;
@ -25,7 +25,7 @@ int main(int argc, char * argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
ATPGetApp app(argc, argv);
ATPClientApp app(argc, argv);
return app.exec();
}

View file

@ -1,255 +0,0 @@
//
// ATPGetApp.cpp
// tools/atp-get/src
//
// Created by Seth Alves on 2017-3-15
// 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 <QDataStream>
#include <QTextStream>
#include <QThread>
#include <QFile>
#include <QLoggingCategory>
#include <QCommandLineParser>
#include <NetworkLogging.h>
#include <SharedLogging.h>
#include <AddressManager.h>
#include <DependencyManager.h>
#include <SettingHandle.h>
#include "ATPGetApp.h"
ATPGetApp::ATPGetApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity ATP-Get");
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption verboseOutput("v", "verbose output");
parser.addOption(verboseOutput);
const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1");
parser.addOption(domainAddressOption);
const QCommandLineOption cacheSTUNOption("s", "cache stun-server response");
parser.addOption(cacheSTUNOption);
const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT));
parser.addOption(listenPortOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();
}
_verbose = parser.isSet(verboseOutput);
if (!_verbose) {
QLoggingCategory::setFilterRules("qt.network.ssl.warning=false");
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtDebugMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtInfoMsg, false);
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtWarningMsg, false);
}
QStringList filenames = parser.positionalArguments();
if (filenames.empty() || filenames.size() > 2) {
qDebug() << "give remote url and optional local filename as arguments";
parser.showHelp();
Q_UNREACHABLE();
}
_url = QUrl(filenames[0]);
if (_url.scheme() != "atp") {
qDebug() << "url should start with atp:";
parser.showHelp();
Q_UNREACHABLE();
}
if (filenames.size() == 2) {
_localOutputFile = filenames[1];
}
QString domainServerAddress = "127.0.0.1:40103";
if (parser.isSet(domainAddressOption)) {
domainServerAddress = parser.value(domainAddressOption);
}
if (_verbose) {
qDebug() << "domain-server address is" << domainServerAddress;
}
int listenPort = INVALID_PORT;
if (parser.isSet(listenPortOption)) {
listenPort = parser.value(listenPortOption).toInt();
}
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<AccountManager>([&]{ return QString("Mozilla/5.0 (HighFidelityATPGet)"); });
DependencyManager::set<AddressManager>();
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->startThread();
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
// connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
// connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPGetApp::domainConnectionRefused);
connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPGetApp::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &ATPGetApp::nodeKilled);
connect(nodeList.data(), &NodeList::nodeActivated, this, &ATPGetApp::nodeActivated);
// connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
// connect(nodeList.data(), &NodeList::uuidChanged, this, &ATPGetApp::setSessionUUID);
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ATPGetApp::notifyPacketVersionMismatch);
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
DependencyManager::get<AddressManager>()->handleLookupString(domainServerAddress, false);
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->init();
QTimer* doTimer = new QTimer(this);
doTimer->setSingleShot(true);
connect(doTimer, &QTimer::timeout, this, &ATPGetApp::timedOut);
doTimer->start(4000);
}
ATPGetApp::~ATPGetApp() {
}
void ATPGetApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
qDebug() << "domainConnectionRefused";
}
void ATPGetApp::domainChanged(const QString& domainHostname) {
if (_verbose) {
qDebug() << "domainChanged";
}
}
void ATPGetApp::nodeAdded(SharedNodePointer node) {
if (_verbose) {
qDebug() << "node added: " << node->getType();
}
}
void ATPGetApp::nodeActivated(SharedNodePointer node) {
if (node->getType() == NodeType::AssetServer) {
lookup();
}
}
void ATPGetApp::nodeKilled(SharedNodePointer node) {
qDebug() << "nodeKilled";
}
void ATPGetApp::timedOut() {
finish(1);
}
void ATPGetApp::notifyPacketVersionMismatch() {
if (_verbose) {
qDebug() << "packet version mismatch";
}
finish(1);
}
void ATPGetApp::lookup() {
auto path = _url.path();
qDebug() << "path is " << path;
auto request = DependencyManager::get<AssetClient>()->createGetMappingRequest(path);
QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable {
auto result = request->getError();
if (result == GetMappingRequest::NotFound) {
qDebug() << "not found";
} else if (result == GetMappingRequest::NoError) {
qDebug() << "found, hash is " << request->getHash();
download(request->getHash());
} else {
qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString();
}
request->deleteLater();
});
request->start();
}
void ATPGetApp::download(AssetHash hash) {
auto assetClient = DependencyManager::get<AssetClient>();
auto assetRequest = new AssetRequest(hash);
connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) mutable {
Q_ASSERT(request->getState() == AssetRequest::Finished);
if (request->getError() == AssetRequest::Error::NoError) {
QString data = QString::fromUtf8(request->getData());
if (_localOutputFile == "") {
QTextStream cout(stdout);
cout << data;
} else {
QFile outputHandle(_localOutputFile);
if (outputHandle.open(QIODevice::ReadWrite)) {
QTextStream stream( &outputHandle );
stream << data;
} else {
qDebug() << "couldn't open output file:" << _localOutputFile;
}
}
}
request->deleteLater();
finish(0);
});
assetRequest->start();
}
void ATPGetApp::finish(int exitCode) {
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
QCoreApplication::exit(exitCode);
}

View file

@ -354,8 +354,8 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
FbxFileTexture* fileTexture = property.GetSrcObject<FbxFileTexture>(j);
// use QFileInfo to easily split up the existing texture filename into its components
QString fbxFileName { fileTexture->GetFileName() };
QFileInfo textureFileInfo { fbxFileName.replace("\\", "/") };
QString fbxTextureFileName { fileTexture->GetFileName() };
QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") };
// make sure this texture points to something and isn't one we've already re-mapped
if (!textureFileInfo.filePath().isEmpty()
@ -372,6 +372,9 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName()
<< "to" << bakedTextureFilePath;
// figure out the URL to this texture, embedded or external
auto urlToTexture = getTextureURL(textureFileInfo, fileTexture);
// write the new filename into the FBX scene
fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit());
@ -379,9 +382,6 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
// be right beside the FBX
fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit().constData());
// figure out the URL to this texture, embedded or external
auto urlToTexture = getTextureURL(textureFileInfo, fileTexture);
if (!_bakingTextures.contains(urlToTexture)) {
// bake this texture asynchronously
bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER);

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="140mm"
height="140mm"
viewBox="0 0 140 140"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="laser-a.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="1038.9217"
inkscape:cy="879.1901"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
objecttolerance="3"
inkscape:window-width="2194"
inkscape:window-height="1171"
inkscape:window-x="2186"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="true" />
<metadata
id="metadata5">
<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>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-157)">
<circle
id="path3680"
cx="53.672615"
cy="245.59525"
r="20.410715"
style="stroke-width:0.26458332;fill:#000000;stroke:#000000;stroke-opacity:1" />
<path
sodipodi:type="star"
id="path3684"
sodipodi:sides="5"
sodipodi:cx="48.380951"
sodipodi:cy="175.29166"
sodipodi:r1="47.150944"
sodipodi:r2="13.598276"
sodipodi:arg1="1.0897856"
sodipodi:arg2="1.6872054"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 70.196535,217.09228 46.801561,188.7979 15.367586,208.95661 35.047691,177.96323 6.161985,154.29712 l 35.557948,9.13941 13.581627,-34.78516 2.295916,36.64185 37.279613,2.16768 -34.138995,13.5065 z"
inkscape:transform-center-x="1.9762951"
inkscape:transform-center-y="1.2684232"
style="stroke-width:0.26458332;fill:#000000;stroke:#000000;stroke-opacity:1"
transform="matrix(0.75680215,0,0,0.80599932,17.17868,104.2376)" />
<path
sodipodi:type="star"
id="path3694"
sodipodi:sides="5"
sodipodi:cx="53.381914"
sodipodi:cy="244.31365"
sodipodi:r1="48.628967"
sodipodi:r2="10.608823"
sodipodi:arg1="1.7183633"
sodipodi:arg2="2.3466818"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 46.231899,292.4141 45.95204,251.88623 5.4261854,252.37744 43.883994,239.58748 30.893657,201.19689 54.941748,233.82012 87.439133,209.60223 63.843862,242.55446 96.918688,265.97757 58.287917,253.71993 Z"
inkscape:transform-center-x="3.6110896"
inkscape:transform-center-y="0.36477657"
style="stroke-width:0.26458332;fill:#000000;stroke:#000000;stroke-opacity:1" />
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:7.708;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 56.694008,239.30541 135.65246,167.44572"
id="path3702"
inkscape:connector-curvature="0"
inkscape:transform-center-x="-1.5119048"
inkscape:transform-center-y="23.434524" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="140mm"
height="140mm"
viewBox="0 0 140 140"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r15371"
sodipodi:docname="laser.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="1523.2074"
inkscape:cy="879.1901"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
objecttolerance="3"
inkscape:window-width="2194"
inkscape:window-height="1171"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<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>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-157)">
<circle
id="path3680"
cx="53.672615"
cy="245.59525"
r="20.410715"
style="stroke-width:0.26458332;fill:#ffffff;stroke:#ffffff;stroke-opacity:1" />
<path
sodipodi:type="star"
id="path3684"
sodipodi:sides="5"
sodipodi:cx="48.380951"
sodipodi:cy="175.29166"
sodipodi:r1="47.150944"
sodipodi:r2="13.598276"
sodipodi:arg1="1.0897856"
sodipodi:arg2="1.6872054"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 70.196535,217.09228 46.801561,188.7979 15.367586,208.95661 35.047691,177.96323 6.161985,154.29712 l 35.557948,9.13941 13.581627,-34.78516 2.295916,36.64185 37.279613,2.16768 -34.138995,13.5065 z"
inkscape:transform-center-x="1.9762951"
inkscape:transform-center-y="1.2684232"
style="stroke-width:0.26458332;fill:#ffffff;stroke:#ffffff;stroke-opacity:1"
transform="matrix(0.75680215,0,0,0.80599932,17.17868,104.2376)" />
<path
sodipodi:type="star"
id="path3694"
sodipodi:sides="5"
sodipodi:cx="53.381914"
sodipodi:cy="244.31365"
sodipodi:r1="48.628967"
sodipodi:r2="10.608823"
sodipodi:arg1="1.7183633"
sodipodi:arg2="2.3466818"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 46.231899,292.4141 45.95204,251.88623 5.4261854,252.37744 43.883994,239.58748 30.893657,201.19689 54.941748,233.82012 87.439133,209.60223 63.843862,242.55446 96.918688,265.97757 58.287917,253.71993 Z"
inkscape:transform-center-x="3.6110896"
inkscape:transform-center-y="0.36477657"
style="stroke-width:0.26458332;fill:#ffffff;stroke:#ffffff;stroke-opacity:1" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:7.70800018;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 56.694008,239.30541 135.65246,167.44572"
id="path3702"
inkscape:connector-curvature="0"
inkscape:transform-center-x="-1.5119048"
inkscape:transform-center-y="23.434524" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,274 @@
//
// Created by Alan-Michael Moody on 6/4/2017
//
'use strict';
(function () {
Script.include("/~/system/libraries/controllers.js");
var APP_NAME = 'LASER',
APP_ICON = Script.resolvePath('laser.svg'),
APP_ICON_ACTIVE = Script.resolvePath('laser-a.svg');
var POINT_INDEX_CHANNEL = "Hifi-Point-Index",
GRAB_DISABLE_CHANNEL = "Hifi-Grab-Disable",
POINTER_DISABLE_CHANNEL = "Hifi-Pointer-Disable";
var TRIGGER_PRESSURE = 0.95;
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system');
var button = tablet.addButton({
icon: APP_ICON,
activeIcon: APP_ICON_ACTIVE,
text: APP_NAME
});
var laserEntities = {
left: {
beam: null,
sphere: null
},
right: {
beam: null,
sphere: null
}
};
var rayExclusionList = [];
function laser(hand) {
var PICK_MAX_DISTANCE = 500;
var FORWARD_OFFSET = 0.05;
var isNewEntityNeeded = (laserEntities[hand].beam === null);
var jointName = hand === 'right' ? 'RightHandIndex4' : 'LeftHandIndex4'; //'RightHand' : 'LeftHand';
var _hand = hand === 'right' ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var controllerLocation = getControllerWorldLocation(_hand, true);
var worldControllerPosition = controllerLocation.position;
var worldControllerRotation = controllerLocation.orientation;
var jointExists = (MyAvatar.getJointIndex(jointName) > 0) ;
var CONTROLLER_FORWARD_OFFSET = Vec3.multiply(Quat.getUp(worldControllerRotation), FORWARD_OFFSET);
var pickRay = {
origin: worldControllerPosition,
direction: Quat.getUp(worldControllerRotation),
length: PICK_MAX_DISTANCE
};
if (jointExists) {
pickRay.origin = MyAvatar.getJointPosition(jointName);
pickRay.direction = MyAvatar.jointToWorldDirection(Vec3.UP, MyAvatar.getJointIndex(jointName));
}
var ray = Entities.findRayIntersection(pickRay, true, [], rayExclusionList, true);
var avatarRay = AvatarManager.findRayIntersection(pickRay, true, [], rayExclusionList, true);
var dist = PICK_MAX_DISTANCE;
var intersection = null;
if (avatarRay.intersects) {
intersection = avatarRay.intersection;
dist = Vec3.distance(pickRay.origin, avatarRay.intersection);
} else if (ray.intersects) {
intersection = ray.intersection;
dist = Vec3.distance(pickRay.origin, ray.intersection);
}
var sphereSize = dist * 0.01;
if (isNewEntityNeeded) {
var sphere = {
lifetime: 360,
type: 'Sphere',
dimensions: {x: sphereSize, y: sphereSize, z: sphereSize},
color: {red: 0, green: 255, blue: 0},
position: intersection,
collisionless: true,
visible: false
};
var beam = {
lifetime: 360,
type: 'Line',
glow: 1.0,
lineWidth: 5,
alpha: 0.5,
ignoreRayIntersection: true,
drawInFront: true,
color: {red: 0, green: 255, blue: 0},
parentID: MyAvatar.sessionUUID,
dimensions: Vec3.multiply(PICK_MAX_DISTANCE * 2, Vec3.ONE),
linePoints: [Vec3.ZERO, {x: 0, y: dist - FORWARD_OFFSET, z: 0}]
};
if(jointExists) {
beam.parentJointIndex = MyAvatar.getJointIndex(jointName);
beam.localPosition = {x: 0, y: FORWARD_OFFSET, z: 0};
beam.localRotation = Quat.normalize({});
} else {
beam.position = Vec3.sum(pickRay.origin, CONTROLLER_FORWARD_OFFSET);
beam.rotation = worldControllerRotation;
}
laserEntities[hand].beam = Entities.addEntity(beam,true);
rayExclusionList.push(laserEntities[hand].beam);
laserEntities[hand].sphere = Entities.addEntity(sphere,true);
rayExclusionList.push(laserEntities[hand].sphere);
if (ray.intersects || avatarRay.intersects) {
Entities.editEntity(laserEntities[hand].sphere, {
visible: true
});
}
} else {
if (ray.intersects || avatarRay.intersects) {
if(jointExists) {
Entities.editEntity(laserEntities[hand].beam, {
parentID: MyAvatar.sessionUUID,
parentJointIndex: MyAvatar.getJointIndex(jointName),
localPosition: {x: 0, y: FORWARD_OFFSET, z: 0},
localRotation: Quat.normalize({}),
dimensions: Vec3.multiply(PICK_MAX_DISTANCE * 2, Vec3.ONE),
linePoints: [Vec3.ZERO, {x: 0, y: dist - FORWARD_OFFSET, z: 0}]
});
} else {
Entities.editEntity(laserEntities[hand].beam, {
parentID: MyAvatar.sessionUUID,
parentJointIndex: MyAvatar.getJointIndex(jointName),
position: Vec3.sum(pickRay.origin, CONTROLLER_FORWARD_OFFSET),
rotation: worldControllerRotation,
dimensions: Vec3.multiply(PICK_MAX_DISTANCE * 2, Vec3.ONE),
linePoints: [Vec3.ZERO, {x: 0, y: dist - FORWARD_OFFSET, z: 0}]
});
}
Entities.editEntity(laserEntities[hand].sphere, {
dimensions: {x: sphereSize, y: sphereSize, z: sphereSize},
position: intersection,
visible: true
});
} else {
if(jointExists) {
Entities.editEntity(laserEntities[hand].beam, {
parentID: MyAvatar.sessionUUID,
parentJointIndex: MyAvatar.getJointIndex(jointName),
localPosition: {x: 0, y: FORWARD_OFFSET, z: 0},
localRotation: Quat.normalize({}),
dimensions: Vec3.multiply(PICK_MAX_DISTANCE * 2, Vec3.ONE),
linePoints: [Vec3.ZERO, {x: 0, y: dist - FORWARD_OFFSET, z: 0}]
});
} else {
Entities.editEntity(laserEntities[hand].beam, {
parentID: MyAvatar.sessionUUID,
parentJointIndex: MyAvatar.getJointIndex(jointName),
position: Vec3.sum(pickRay.origin, CONTROLLER_FORWARD_OFFSET),
rotation: worldControllerRotation,
dimensions: Vec3.multiply(PICK_MAX_DISTANCE * 2, Vec3.ONE),
linePoints: [Vec3.ZERO, {x: 0, y: dist - FORWARD_OFFSET, z: 0}]
});
}
Entities.editEntity(laserEntities[hand].sphere, {
visible: false
});
}
}
}
function triggerWatcher(deltaTime) {
var deleteBeamLeft = true,
deleteBeamRight = true;
if (Controller.getValue(Controller.Standard.LT) > TRIGGER_PRESSURE) {
deleteBeamLeft = false;
laser('left');
}
if (Controller.getValue(Controller.Standard.RT) > TRIGGER_PRESSURE) {
deleteBeamRight = false;
laser('right');
}
if (deleteBeamLeft && laserEntities.left.beam !== null) {
Entities.deleteEntity(laserEntities.left.beam);
Entities.deleteEntity(laserEntities.left.sphere);
laserEntities.left.beam = null;
laserEntities.left.sphere = null;
}
if (deleteBeamRight && laserEntities.right.beam !== null) {
Entities.deleteEntity(laserEntities.right.beam);
Entities.deleteEntity(laserEntities.right.sphere);
laserEntities.right.beam = null;
laserEntities.right.sphere = null;
}
if (deleteBeamRight && deleteBeamLeft) {
rayExclusionList = [];
}
}
function selectionBeamSwitch(bool) {
Messages.sendMessage(GRAB_DISABLE_CHANNEL, JSON.stringify({
holdEnabled: bool,
nearGrabEnabled: bool,
farGrabEnabled: bool
}), true);
Messages.sendMessage(POINTER_DISABLE_CHANNEL, JSON.stringify({
pointerEnabled: bool
}), true);
Messages.sendMessage(POINT_INDEX_CHANNEL, JSON.stringify({
pointIndex: !bool
}), true);
}
var _switch = true;
function buttonSwitch() {
if (_switch) {
Script.update.connect(triggerWatcher);
Messages.subscribe(POINT_INDEX_CHANNEL);
Messages.subscribe(GRAB_DISABLE_CHANNEL);
Messages.subscribe(POINTER_DISABLE_CHANNEL);
} else {
Script.update.disconnect(triggerWatcher);
Messages.unsubscribe(POINT_INDEX_CHANNEL);
Messages.unsubscribe(GRAB_DISABLE_CHANNEL);
Messages.unsubscribe(POINTER_DISABLE_CHANNEL);
}
button.editProperties({isActive: _switch});
selectionBeamSwitch(!_switch);
_switch = !_switch;
}
button.clicked.connect(buttonSwitch);
function clean() {
tablet.removeButton(button);
Script.update.disconnect(triggerWatcher);
Messages.unsubscribe(POINT_INDEX_CHANNEL);
Messages.unsubscribe(GRAB_DISABLE_CHANNEL);
Messages.unsubscribe(POINTER_DISABLE_CHANNEL);
rayExclusionList = [];
}
Script.scriptEnding.connect(clean);
}());