Merge pull request #8457 from hyperlogic/feature/handheld-marketplace

Handheld Marketplace Tablet
This commit is contained in:
Anthony Thibault 2016-08-24 17:59:39 -07:00 committed by GitHub
commit f971b4b041
25 changed files with 358 additions and 85 deletions

View file

@ -55,9 +55,11 @@ WebEngineView {
}
onNewViewRequested:{
if (desktop) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
request.openIn(newWindow.webView);
}
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6

View file

@ -59,6 +59,8 @@ const float DISPLAYNAME_ALPHA = 1.0f;
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534;
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape();
@ -851,15 +853,33 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
}
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return glmExtractRotation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
}
}
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return extractTranslation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
}
}
int Avatar::getJointIndex(const QString& name) const {

View file

@ -107,7 +107,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdSensorOrientation(),
_hmdSensorPosition(),
_bodySensorMatrix(),
_sensorToWorldMatrix(),
_goToPending(false),
_goToPosition(),
_goToOrientation(),
@ -511,13 +510,9 @@ void MyAvatar::simulate(float deltaTime) {
updateAvatarEntities();
}
// thread-safe
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
// As far as I know no HMD system supports a play area of a kilometer in radius.
// As far as I know no HMD system supports a play area of a kilometer in radius.
static const float MAX_HMD_ORIGIN_DISTANCE = 1000.0f;
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
@ -526,7 +521,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorMatrix = hmdSensorMatrix;
auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
if (newHmdSensorPosition != _hmdSensorPosition &&
if (newHmdSensorPosition != _hmdSensorPosition &&
glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
// Ignore unreasonable HMD sensor data

View file

@ -79,8 +79,6 @@ class MyAvatar : public Avatar {
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
@ -110,9 +108,6 @@ public:
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
Q_INVOKABLE QVariant getOrientationVar() const;
@ -415,6 +410,10 @@ private:
bool _useSnapTurn { true };
bool _clearOverlayWhenMoving { true };
// working copy of sensorToWorldMatrix.
// See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix;
// cache of the current HMD sensor position and orientation
// in sensor space.
glm::mat4 _hmdSensorMatrix;
@ -427,10 +426,6 @@ private:
// in sensor space.
glm::mat4 _bodySensorMatrix;
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
struct FollowHelper {
FollowHelper();

View file

@ -53,15 +53,18 @@ namespace AvatarDataPacket {
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
PACKED_BEGIN struct Header {
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
uint8_t sensorToWorldQuat[6]; // 6 byte compressed quaternion part of sensor to world matrix
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
uint8_t flags;
} PACKED_END;
const size_t HEADER_SIZE = 49;
const size_t HEADER_SIZE = 69;
// only present if HAS_REFERENTIAL flag is set in header.flags
PACKED_BEGIN struct ParentInfo {
@ -93,6 +96,9 @@ namespace AvatarDataPacket {
*/
}
static const int TRANSLATION_COMPRESSION_RADIX = 12;
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
AvatarData::AvatarData() :
@ -210,6 +216,14 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
header->lookAtPosition[2] = _headData->_lookAtPosition.z;
header->audioLoudness = _headData->_audioLoudness;
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
glm::vec3 scale = extractScale(sensorToWorldMatrix);
packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState);
// hand state
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
@ -346,8 +360,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
*destinationBuffer++ = validity;
}
const int TRANSLATION_COMPRESSION_RADIX = 12;
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i ++) {
@ -500,6 +512,15 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
_headData->_audioLoudness = audioLoudness;
glm::quat sensorToWorldQuat;
unpackOrientationQuatFromSixBytes(header->sensorToWorldQuat, sensorToWorldQuat);
float sensorToWorldScale;
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&header->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
glm::vec3 sensorToWorldTrans(header->sensorToWorldTrans[0], header->sensorToWorldTrans[1], header->sensorToWorldTrans[2]);
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
{ // bitFlags and face data
uint8_t bitItems = header->flags;
@ -616,7 +637,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// each joint translation component is stored in 6 bytes.
const int COMPRESSED_TRANSLATION_SIZE = 6;
PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE);
const int TRANSLATION_COMPRESSION_RADIX = 12;
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
@ -1718,6 +1738,11 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
return result;
}
// thread-safe
glm::mat4 AvatarData::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);

View file

@ -54,6 +54,7 @@ typedef unsigned long long quint64;
#include <SpatiallyNestable.h>
#include <NumericalConstants.h>
#include <Packed.h>
#include <ThreadSafeValueCache.h>
#include "AABox.h"
#include "HeadData.h"
@ -171,6 +172,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
public:
static const QString FRAME_NAME;
@ -351,6 +354,9 @@ public:
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -425,6 +431,9 @@ protected:
bool _avatarEntityDataLocallyEdited { false };
bool _avatarEntityDataChanged { false };
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -11,6 +11,7 @@
#include <QMouseEvent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQmlContext>
#include <QOpenGLContext>
#include <glm/gtx/quaternion.hpp>
@ -26,7 +27,6 @@
#include "EntityTreeRenderer.h"
const float DPI = 30.47f;
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount { 0 };
// Don't allow more than 100 concurrent web views
@ -34,6 +34,8 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
static int MAX_WINDOW_SIZE = 4096;
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
entity->setProperties(properties);
@ -67,6 +69,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
_webSurface->load("WebView.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
@ -87,7 +90,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(Qt::TouchPointReleased);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
point.setPos(windowPoint);
QList<QTouchEvent::TouchPoint> touchPoints;
@ -99,6 +102,19 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
return true;
}
glm::vec2 RenderableWebEntityItem::getWindowSize() const {
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * _dpi;
// ensure no side is never larger then MAX_WINDOW_SIZE
float max = (dims.x > dims.y) ? dims.x : dims.y;
if (max > MAX_WINDOW_SIZE) {
dims *= MAX_WINDOW_SIZE / max;
}
return dims;
}
void RenderableWebEntityItem::render(RenderArgs* args) {
checkFading();
@ -124,12 +140,13 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
_lastRenderTime = usecTimestampNow();
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * DPI;
glm::vec2 windowSize = getWindowSize();
// The offscreen surface is idempotent for resizes (bails early
// if it's a no-op), so it's safe to just call resize every frame
// without worrying about excessive overhead.
_webSurface->resize(QSize(dims.x, dims.y));
_webSurface->resize(QSize(windowSize.x, windowSize.y));
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
Q_ASSERT(getType() == EntityTypes::Web);
@ -185,7 +202,7 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
return;
}
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
if (event.getType() == PointerEvent::Move) {

View file

@ -47,6 +47,7 @@ public:
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();
glm::vec2 getWindowSize() const;
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;

View file

@ -335,6 +335,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape);
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
changedProperties += _animation.getChangedProperties();
changedProperties += _keyLight.getChangedProperties();
@ -504,6 +505,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Web only
if (_type == EntityTypes::Web) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
}
// PolyVoxel only
@ -726,6 +728,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
_lastEdited = usecTimestampNow();
}
@ -903,6 +907,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
// FIXME - these are not yet handled
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
@ -1065,6 +1071,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
if (properties.getType() == EntityTypes::Web) {
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
}
if (properties.getType() == EntityTypes::Text) {
@ -1364,6 +1371,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Web) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
}
if (properties.getType() == EntityTypes::Text) {
@ -1642,6 +1650,8 @@ void EntityItemProperties::markAllChanged() {
_clientOnlyChanged = true;
_owningAvatarIDChanged = true;
_dpiChanged = true;
}
// The minimum bounding box for the entity.
@ -1977,6 +1987,10 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "ghostingAllowed";
}
if (dpiChanged()) {
out += "dpi";
}
if (shapeChanged()) {
out += "shape";
}

View file

@ -215,6 +215,8 @@ public:
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
static QString getBackgroundModeString(BackgroundMode mode);

View file

@ -73,4 +73,6 @@ const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30;
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -176,6 +176,7 @@ enum EntityPropertyList {
PROP_OWNING_AVATAR_ID, // doesn't go over wire
PROP_SHAPE,
PROP_DPI,
PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts
PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts

View file

@ -11,6 +11,7 @@
#include <glm/gtx/transform.hpp>
#include <QDebug>
#include <QJsonDocument>
#include <ByteCountCoding.h>
#include <GeometryUtil.h>
@ -30,6 +31,7 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Web;
_dpi = ENTITY_ITEM_DEFAULT_DPI;
}
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
@ -42,6 +44,7 @@ void WebEntityItem::setDimensions(const glm::vec3& value) {
EntityItemProperties WebEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
return properties;
}
@ -50,6 +53,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
if (somethingChanged) {
bool wantDebug = false;
@ -74,6 +78,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
return bytesRead;
}
@ -83,6 +88,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;
requestedProperties += PROP_DPI;
return requestedProperties;
}
@ -96,6 +102,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
}
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -123,3 +130,11 @@ void WebEntityItem::setSourceUrl(const QString& value) {
}
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
void WebEntityItem::setDPI(uint16_t value) {
_dpi = value;
}
uint16_t WebEntityItem::getDPI() const {
return _dpi;
}

View file

@ -54,8 +54,14 @@ public:
virtual void setSourceUrl(const QString& value);
const QString& getSourceUrl() const;
virtual bool wantsHandControllerPointerEvents() const override { return true; }
void setDPI(uint16_t value);
uint16_t getDPI() const;
protected:
QString _sourceUrl;
uint16_t _dpi;
};
#endif // hifi_WebEntityItem_h

View file

@ -166,7 +166,7 @@ private:
QMyQuickRenderControl* _renderControl{ nullptr };
FramebufferPtr _fbo;
RenderbufferPtr _depthStencil;
TextureRecycler _textures;
TextureRecycler _textures { true };
GLTextureEscrow _escrow;
uint64_t _lastRenderTime{ 0 };
@ -399,6 +399,8 @@ void OffscreenQmlRenderThread::render() {
glGetError();
}
Context::Bound(oglplus::Texture::Target::_2D, *texture).GenerateMipmap();
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();

View file

@ -509,16 +509,28 @@ TexturePtr TextureRecycler::getNextTexture() {
using namespace oglplus;
if (_readyTextures.empty()) {
TexturePtr newTexture(new Texture());
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(
0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr
);
if (_useMipmaps) {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::LinearMipmapLinear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Anisotropy(8.0f)
.LODBias(-0.2f)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
} else {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
}
GLuint texId = GetName(*newTexture);
_allTextures[texId] = TexInfo{ newTexture, _size };
_readyTextures.push(newTexture);

View file

@ -190,6 +190,7 @@ using BasicFramebufferWrapperPtr = std::shared_ptr<BasicFramebufferWrapper>;
class TextureRecycler {
public:
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
void setSize(const uvec2& size);
void clear();
TexturePtr getNextTexture();
@ -212,4 +213,5 @@ private:
Map _allTextures;
Queue _readyTextures;
uvec2 _size{ 1920, 1080 };
bool _useMipmaps;
};

View file

@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS;
return VERSION_WEB_ENTITIES_SUPPORT_DPI;
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AbsoluteSixByteRotations);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SensorToWorldMat);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::AssetGetInfo:

View file

@ -186,12 +186,14 @@ const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,
SoftAttachmentSupport,
AvatarEntities,
AbsoluteSixByteRotations
AbsoluteSixByteRotations,
SensorToWorldMat
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -234,6 +234,10 @@ CONTROLLER_STATE_MACHINE[STATE_ENTITY_TOUCHING] = {
updateMethod: "entityTouching"
};
function angleBetween(a, b) {
return Math.acos(Vec3.dot(Vec3.normalize(a), Vec3.normalize(b)));
}
function projectOntoEntityXYPlane(entityID, worldPos) {
var props = entityPropertiesCache.getProps(entityID);
var invRot = Quat.inverse(props.rotation);
@ -1975,7 +1979,8 @@ function MyController(hand) {
var handPosition = this.getHandPosition();
// the center of the equipped object being far from the hand isn't enough to auto-unequip -- we also
// need to fail the findEntities test.
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
var TEAR_AWAY_DISTANCE = 0.04;
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS + TEAR_AWAY_DISTANCE);
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
@ -2128,6 +2133,11 @@ function MyController(hand) {
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent);
this.touchingEnterTimer = 0;
this.touchingEnterPointerEvent = pointerEvent;
this.touchingEnterPointerEvent.button = "None";
this.deadspotExpired = false;
}
};
@ -2135,27 +2145,37 @@ function MyController(hand) {
// test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand);
if (intersectInfo) {
var pointerEvent = {
type: "Release",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
pos3D: intersectInfo.point,
normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction,
button: "Primary",
isPrimaryButton: true,
isSecondaryButton: false,
isTertiaryButton: false
};
var pointerEvent;
if (this.deadspotExpired) {
pointerEvent = {
type: "Release",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
pos3D: intersectInfo.point,
normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction,
button: "Primary",
isPrimaryButton: false,
isSecondaryButton: false,
isTertiaryButton: false
};
} else {
pointerEvent = this.touchingEnterPointerEvent;
pointerEvent.button = "Primary";
pointerEvent.isPrimaryButton = false;
}
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
this.focusedEntity = null;
}
this.focusedEntity = null;
};
this.entityTouching = function() {
this.entityTouching = function(dt) {
this.touchingEnterTimer += dt;
entityPropertiesCache.addEntity(this.grabbedEntity);
if (!this.triggerSmoothedGrab()) {
@ -2184,8 +2204,14 @@ function MyController(hand) {
isTertiaryButton: false
};
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
var POINTER_PRESS_TO_MOVE_DELAY = 0.15; // seconds
var POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.05; // radians ~ 3 degrees
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
angleBetween(pointerEvent.direction, this.touchingEnterPointerEvent.direction) > POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE) {
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
this.deadspotExpired = true;
}
this.intersectionDistance = intersectInfo.distance;
this.searchIndicatorOn(intersectInfo.searchRay);

View file

@ -121,7 +121,7 @@ function ignoreMouseActivity() {
return true;
}
var pos = Reticle.position;
if (pos.x == -1 && pos.y == -1) {
if (!pos || (pos.x == -1 && pos.y == -1)) {
return true;
}
// Only we know if we moved it, which is why this script has to replace depthReticle.js

View file

@ -525,6 +525,10 @@
<label for="property-web-source-url">Source URL</label>
<input type="text" id="property-web-source-url">
</div>
<div class="web-group web-section property dpi ">
<label for="property-web-dpi">Resolution (DPI)</label>
<input type="number" id="property-web-dpi">
</div>
<div class="section-header light-group light-section">
<label>Light</label><span>M</span>
</div>
@ -557,4 +561,4 @@
</div>
</div>
</body>
</html>
</html>

View file

@ -630,6 +630,7 @@ function loaded() {
var elWebSections = document.querySelectorAll(".web-section");
allSections.push(elWebSections);
var elWebSourceURL = document.getElementById("property-web-source-url");
var elWebDPI = document.getElementById("property-web-dpi");
var elDescription = document.getElementById("property-description");
@ -931,6 +932,7 @@ function loaded() {
}
elWebSourceURL.value = properties.sourceUrl;
elWebDPI.value = properties.dpi;
} else if (properties.type == "Text") {
for (var i = 0; i < elTextSections.length; i++) {
elTextSections[i].style.display = 'table';
@ -1228,6 +1230,7 @@ function loaded() {
elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape'));
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi'));
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType'));

View file

@ -0,0 +1,93 @@
//
// WebTablet.js
//
// Created by Anthony J. Thibault on 8/8/2016
// Copyright 2016 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
//
var RAD_TO_DEG = 180 / Math.PI;
var X_AXIS = {x: 1, y: 0, z: 0};
var Y_AXIS = {x: 0, y: 1, z: 0};
var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx";
// returns object with two fields:
// * position - position in front of the user
// * rotation - rotation of entity so it faces the user.
function calcSpawnInfo() {
var front;
var pitchBackRotation = Quat.angleAxis(20.0, X_AXIS);
if (HMD.active) {
front = Quat.getFront(HMD.orientation);
var yawOnlyRotation = Quat.angleAxis(Math.atan2(front.x, front.z) * RAD_TO_DEG, Y_AXIS);
return {
position: Vec3.sum(Vec3.sum(HMD.position, Vec3.multiply(0.6, front)), Vec3.multiply(-0.5, Y_AXIS)),
rotation: Quat.multiply(yawOnlyRotation, pitchBackRotation)
};
} else {
front = Quat.getFront(MyAvatar.orientation);
return {
position: Vec3.sum(Vec3.sum(MyAvatar.position, Vec3.multiply(0.6, front)), {x: 0, y: 0.6, z: 0}),
rotation: Quat.multiply(MyAvatar.orientation, pitchBackRotation)
};
}
}
// ctor
WebTablet = function (url) {
var ASPECT = 4.0 / 3.0;
var WIDTH = 0.4;
var HEIGHT = WIDTH * ASPECT;
var DEPTH = 0.025;
var spawnInfo = calcSpawnInfo();
var tabletEntityPosition = spawnInfo.position;
var tabletEntityRotation = spawnInfo.rotation;
this.tabletEntityID = Entities.addEntity({
name: "tablet",
type: "Model",
modelURL: TABLET_URL,
position: tabletEntityPosition,
rotation: tabletEntityRotation,
userData: JSON.stringify({
"grabbableKey": {"grabbable": true}
}),
dimensions: {x: WIDTH, y: HEIGHT, z: DEPTH},
parentID: MyAvatar.sessionUUID,
parentJointIndex: -2
});
var WEB_ENTITY_REDUCTION_FACTOR = {x: 0.78, y: 0.85};
var WEB_ENTITY_Z_OFFSET = -0.01;
var webEntityRotation = Quat.multiply(spawnInfo.rotation, Quat.angleAxis(180, Y_AXIS));
var webEntityPosition = Vec3.sum(spawnInfo.position, Vec3.multiply(WEB_ENTITY_Z_OFFSET, Quat.getFront(webEntityRotation)));
this.webEntityID = Entities.addEntity({
name: "web",
type: "Web",
sourceUrl: url,
dimensions: {x: WIDTH * WEB_ENTITY_REDUCTION_FACTOR.x,
y: HEIGHT * WEB_ENTITY_REDUCTION_FACTOR.y,
z: 0.1},
position: webEntityPosition,
rotation: webEntityRotation,
shapeType: "box",
dpi: 45,
parentID: this.tabletEntityID,
parentJointIndex: -1
});
this.state = "idle";
};
WebTablet.prototype.destroy = function () {
Entities.deleteEntity(this.webEntityID);
Entities.deleteEntity(this.tabletEntityID);
};

View file

@ -8,6 +8,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global WebTablet */
Script.include("../libraries/WebTablet.js");
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
@ -22,26 +25,45 @@ var marketplaceWindow = new OverlayWebWindow({
var toolHeight = 50;
var toolWidth = 50;
var TOOLBAR_MARGIN_Y = 0;
var marketplaceVisible = false;
var marketplaceWebTablet;
function shouldShowWebTablet() {
var rightPose = Controller.getPoseValue(Controller.Standard.RightHand);
var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand);
var hasHydra = !!Controller.Hardware.Hydra;
return HMD.active && (leftPose.valid || rightPose.valid || hasHydra);
}
function showMarketplace(marketplaceID) {
var url = MARKETPLACE_URL;
if (marketplaceID) {
url = url + "/items/" + marketplaceID;
if (shouldShowWebTablet()) {
marketplaceWebTablet = new WebTablet("https://metaverse.highfidelity.com/marketplace");
} else {
var url = MARKETPLACE_URL;
if (marketplaceID) {
url = url + "/items/" + marketplaceID;
}
marketplaceWindow.setURL(url);
marketplaceWindow.setVisible(true);
}
marketplaceWindow.setURL(url);
marketplaceWindow.setVisible(true);
marketplaceVisible = true;
UserActivityLogger.openedMarketplace();
}
function hideMarketplace() {
marketplaceWindow.setVisible(false);
marketplaceWindow.setURL("about:blank");
if (marketplaceWindow.visible) {
marketplaceWindow.setVisible(false);
marketplaceWindow.setURL("about:blank");
} else if (marketplaceWebTablet) {
marketplaceWebTablet.destroy();
marketplaceWebTablet = null;
}
marketplaceVisible = false;
}
function toggleMarketplace() {
if (marketplaceWindow.visible) {
if (marketplaceVisible) {
hideMarketplace();
} else {
showMarketplace();
@ -59,19 +81,22 @@ var browseExamplesButton = toolBar.addButton({
alpha: 0.9
});
function onExamplesWindowVisibilityChanged() {
function onMarketplaceWindowVisibilityChanged() {
browseExamplesButton.writeProperty('buttonState', marketplaceWindow.visible ? 0 : 1);
browseExamplesButton.writeProperty('defaultState', marketplaceWindow.visible ? 0 : 1);
browseExamplesButton.writeProperty('hoverState', marketplaceWindow.visible ? 2 : 3);
marketplaceVisible = marketplaceWindow.visible;
}
function onClick() {
toggleMarketplace();
}
browseExamplesButton.clicked.connect(onClick);
marketplaceWindow.visibleChanged.connect(onExamplesWindowVisibilityChanged);
marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged);
Script.scriptEnding.connect(function () {
toolBar.removeButton("marketplace");
browseExamplesButton.clicked.disconnect(onClick);
marketplaceWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged);
});
marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged);
});