Merge branch 'temp0' of https://github.com/samcake/hifi into temp1

This commit is contained in:
Sam Gateau 2015-02-06 16:30:47 -08:00
commit 469ee29f0b
50 changed files with 95568 additions and 521 deletions

View file

@ -46,6 +46,8 @@ Agent::Agent(const QByteArray& packet) :
_scriptEngine.setParent(this);
_scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
DependencyManager::set<ResouceCacheSharedItems>();
}
void Agent::readPendingDatagrams() {

View file

@ -73,6 +73,20 @@
"can_set": true
}
]
},
{
"name": "allowed_editors",
"type": "table",
"label": "Allowed Editors",
"help": "List the High Fidelity names for people you want to be able lock or unlock entities in this domain.<br/>An empty list means everyone.",
"numbered": false,
"columns": [
{
"name": "username",
"label": "Username",
"can_set": true
}
]
}
]
},
@ -433,4 +447,4 @@
}
]
}
]
]

View file

@ -41,6 +41,11 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923;
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
@ -638,10 +643,16 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
// we got a packetUUID we didn't recognize, just add the node
nodeUUID = QUuid::createUuid();
}
SharedNodePointer newNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr);
// if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
bool canAdjustLocks = allowedEditors.isEmpty() || allowedEditors.contains(username);
SharedNodePointer newNode =
DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr, canAdjustLocks);
// when the newNode is created the linked data is also created
// if this was a static assignment set the UUID, set the sendingSockAddr
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
@ -663,7 +674,6 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
}
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const QByteArray& usernameSignature,
@ -842,6 +852,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// always send the node their own UUID back
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
broadcastDataStream << node->getUUID();
broadcastDataStream << node->getCanAdjustLocks();
int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos();

View file

@ -135,9 +135,6 @@ using namespace std;
static unsigned STARFIELD_NUM_STARS = 50000;
static unsigned STARFIELD_SEED = 1;
static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored
const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
static QTimer* idleTimer = NULL;
@ -225,6 +222,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_scaleMirror(1.0f),
_rotateMirror(0.0f),
_raiseMirror(0.0f),
_cursorVisible(true),
_lastMouseMove(usecTimestampNow()),
_lastMouseMoveWasSimulated(false),
_touchAvgX(0.0f),
@ -1480,6 +1478,8 @@ void Application::setEnableVRMode(bool enableVRMode) {
auto glCanvas = DependencyManager::get<GLCanvas>();
resizeGL(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight());
updateCursorVisibility();
}
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
@ -1947,35 +1947,22 @@ void Application::updateCursor(float deltaTime) {
static QPoint lastMousePos = QPoint();
_lastMouseMove = (lastMousePos == QCursor::pos()) ? _lastMouseMove : usecTimestampNow();
bool hideMouse = false;
bool underMouse = QGuiApplication::topLevelAt(QCursor::pos()) ==
Application::getInstance()->getWindow()->windowHandle();
static const int HIDE_CURSOR_TIMEOUT = 3 * USECS_PER_SECOND; // 3 second
int elapsed = usecTimestampNow() - _lastMouseMove;
if ((elapsed > HIDE_CURSOR_TIMEOUT) ||
(OculusManager::isConnected() && Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode))) {
hideMouse = underMouse;
}
setCursorVisible(!hideMouse);
lastMousePos = QCursor::pos();
}
void Application::setCursorVisible(bool visible) {
if (visible) {
if (overrideCursor() != NULL) {
restoreOverrideCursor();
}
void Application::updateCursorVisibility() {
if (!_cursorVisible || Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
DependencyManager::get<GLCanvas>()->setCursor(Qt::BlankCursor);
} else {
if (overrideCursor() != NULL) {
changeOverrideCursor(Qt::BlankCursor);
} else {
setOverrideCursor(Qt::BlankCursor);
}
DependencyManager::get<GLCanvas>()->unsetCursor();
}
}
void Application::setCursorVisible(bool visible) {
_cursorVisible = visible;
updateCursorVisibility();
}
void Application::update(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::update()");

View file

@ -186,8 +186,7 @@ public:
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
bool isMousePressed() const { return _mousePressed; }
bool isMouseHidden() const { return DependencyManager::get<GLCanvas>()->cursor().shape() == Qt::BlankCursor; }
void setCursorVisible(bool visible);
bool isMouseHidden() const { return !_cursorVisible; }
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
bool mouseOnScreen() const;
@ -398,11 +397,15 @@ private slots:
void audioMuteToggled();
void setCursorVisible(bool visible);
private:
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
void updateProjectionMatrix();
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
void updateCursorVisibility();
void sendPingPackets();
void initDisplay();
@ -514,6 +517,7 @@ private:
Environment _environment;
bool _cursorVisible;
int _mouseDragStartedX;
int _mouseDragStartedY;
quint64 _lastMouseMove;

View file

@ -96,7 +96,7 @@ static TextRenderer* textRenderer(int mono) {
}
int widthText(float scale, int mono, char const* string) {
return textRenderer(mono)->computeWidth(string) * (scale / 0.10);
return textRenderer(mono)->computeExtent(string).x; // computeWidth(string) * (scale / 0.10);
}
void drawText(int x, int y, float scale, float radians, int mono,

View file

@ -678,9 +678,7 @@ void Avatar::renderDisplayName() {
glRotatef(glm::degrees(angle), 0.0f, 1.0f, 0.0f);
float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD);
glScalef(scaleFactor, scaleFactor, 1.0);
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
glScalef(scaleFactor, -scaleFactor, scaleFactor); // TextRenderer::draw paints the text upside down in y axis
int text_x = -_displayNameBoundingRect.width() / 2;
int text_y = -_displayNameBoundingRect.height() / 2;
@ -913,7 +911,9 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
void Avatar::setDisplayName(const QString& displayName) {
AvatarData::setDisplayName(displayName);
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
// FIXME is this a sufficient replacement for tightBoundingRect?
glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(displayName);
_displayNameBoundingRect = QRect(QPoint(0, 0), QPoint((int)extent.x, (int)extent.y));
}
void Avatar::setBillboard(const QByteArray& billboard) {

View file

@ -83,8 +83,10 @@ MotionTracker::Index MotionTracker::addJoint(const Semantic& semantic, Index par
// Check that the semantic is not already in use
Index foundIndex = findJointIndex(semantic);
if (foundIndex >= 0)
if (foundIndex >= 0) {
return INVALID_SEMANTIC;
}
// All good then allocate the joint
Index newIndex = _jointsArray.size();
@ -97,8 +99,10 @@ MotionTracker::Index MotionTracker::addJoint(const Semantic& semantic, Index par
MotionTracker::Index MotionTracker::findJointIndex(const Semantic& semantic) const {
// TODO C++11 auto jointIt = _jointsMap.find(semantic);
JointTracker::Map::const_iterator jointIt = _jointsMap.find(semantic);
if (jointIt != _jointsMap.end())
if (jointIt != _jointsMap.end()) {
return (*jointIt).second;
}
return INVALID_SEMANTIC;
}

View file

@ -37,7 +37,7 @@ public:
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
typedef std::string Semantic;
typedef uint32_t Index;
typedef int32_t Index;
static const Index INVALID_SEMANTIC = -1;
static const Index INVALID_PARENT = -2;

View file

@ -216,7 +216,7 @@ void PrioVR::renderCalibrationCountdown() {
false, TextRenderer::OUTLINE_EFFECT, 2);
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
auto glCanvas = DependencyManager::get<GLCanvas>();
textRenderer->draw((glCanvas->width() - textRenderer->computeWidth(text.constData())) / 2,
textRenderer->draw((glCanvas->width() - textRenderer->computeExtent(text.constData()).x) / 2,
glCanvas->height() / 2,
text, glm::vec4(1,1,1,1));
#endif

View file

@ -15,9 +15,6 @@
#include "Menu.h"
#include "SharedUtil.h"
const int PALMROOT_NUM_JOINTS = 2;
const int FINGER_NUM_JOINTS = 4;
const DeviceTracker::Name RealSense::NAME = "RealSense";
// find the index of a joint from

View file

@ -50,7 +50,12 @@ void WindowScriptingInterface::setFocus() {
}
void WindowScriptingInterface::setCursorVisible(bool visible) {
Application::getInstance()->setCursorVisible(visible);
QMetaObject::invokeMethod(Application::getInstance(), "setCursorVisible", Qt::BlockingQueuedConnection,
Q_ARG(bool, visible));
}
bool WindowScriptingInterface::isCursorVisible() const {
return !Application::getInstance()->isMouseHidden();
}
void WindowScriptingInterface::setCursorPosition(int x, int y) {

View file

@ -27,12 +27,14 @@ class WindowScriptingInterface : public QObject {
Q_PROPERTY(int innerHeight READ getInnerHeight)
Q_PROPERTY(int x READ getX)
Q_PROPERTY(int y READ getY)
Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible)
public:
static WindowScriptingInterface* getInstance();
int getInnerWidth();
int getInnerHeight();
int getX();
int getY();
bool isCursorVisible() const;
public slots:
QScriptValue getCursorPositionX();

View file

@ -330,8 +330,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
_overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80);
}
_overlays.render();
renderPointersOculus(myAvatar->getDefaultEyePosition());
if (!Application::getInstance()->isMouseHidden()) {
renderPointersOculus(myAvatar->getDefaultEyePosition());
}
glDepthMask(GL_TRUE);
_overlays.releaseTexture();
glDisable(GL_TEXTURE_2D);
@ -542,7 +543,7 @@ void ApplicationOverlay::renderPointers() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
if (OculusManager::isConnected() && !application->getLastMouseMoveWasSimulated()) {
if (OculusManager::isConnected() && !application->getLastMouseMoveWasSimulated() && !application->isMouseHidden()) {
//If we are in oculus, render reticle later
if (_lastMouseMove == 0) {
_lastMouseMove = usecTimestampNow();

View file

@ -108,7 +108,7 @@ void Text3DOverlay::render(RenderArgs* args) {
// Same font properties as textSize()
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE);
float maxHeight = (float)textRenderer->calculateHeight("Xy") * LINE_SCALE_RATIO;
float maxHeight = (float)textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO;
float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
@ -124,13 +124,8 @@ void Text3DOverlay::render(RenderArgs* args) {
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipDimensions.y);
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, getAlpha() };
QStringList lines = _text.split("\n");
int lineOffset = maxHeight;
foreach(QString thisLine, lines) {
textRenderer->draw(0, lineOffset, qPrintable(thisLine), textColor);
lineOffset += maxHeight;
}
glm::vec4 textColor = { _color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, getAlpha() };
textRenderer->draw(0, 0, _text, textColor);
glDisable(GL_CLIP_PLANE0);
glDisable(GL_CLIP_PLANE1);

View file

@ -92,17 +92,7 @@ void TextOverlay::render(RenderArgs* args) {
float alpha = getAlpha();
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha };
QStringList lines = _text.split("\n");
int lineOffset = 0;
foreach(QString thisLine, lines) {
if (lineOffset == 0) {
lineOffset = textRenderer->calculateHeight(qPrintable(thisLine));
}
lineOffset += textRenderer->draw(x, y + lineOffset, qPrintable(thisLine), textColor);
const int lineGap = 2;
lineOffset += lineGap;
}
textRenderer->draw(x, y, _text, textColor);
}
void TextOverlay::setProperties(const QScriptValue& properties) {

View file

@ -18,9 +18,9 @@
#include <TextRenderer.h>
#include "RenderableTextEntityItem.h"
#include "GLMHelpers.h"
const int FIXED_FONT_POINT_SIZE = 40;
const float LINE_SCALE_RATIO = 1.2f;
EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableTextEntityItem(entityID, properties);
@ -34,9 +34,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
glm::vec3 halfDimensions = dimensions / 2.0f;
glm::quat rotation = getRotation();
float leftMargin = 0.1f;
float rightMargin = 0.1f;
float topMargin = 0.1f;
float bottomMargin = 0.1f;
//qDebug() << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText();
@ -46,51 +44,22 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
const float MAX_COLOR = 255.0f;
xColor backgroundColor = getBackgroundColorX();
float alpha = 1.0f; //getBackgroundAlpha();
glm::vec4 color(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha);
const float SLIGHTLY_BEHIND = -0.005f;
static const float SLIGHTLY_BEHIND = -0.005f;
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, color);
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 40.0f; // this is a ratio determined through experimentation
// Same font properties as textSize()
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE);
float maxHeight = (float)textRenderer->calculateHeight("Xy") * LINE_SCALE_RATIO;
float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha));
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f);
glm::vec2 clipMinimum(0.0f, 0.0f);
glm::vec2 clipDimensions((dimensions.x - (leftMargin + rightMargin)) / scaleFactor,
(dimensions.y - (topMargin + bottomMargin)) / scaleFactor);
glScalef(scaleFactor, -scaleFactor, 1.0);
enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipDimensions.x);
enableClipPlane(GL_CLIP_PLANE1, 1.0f, 0.0f, 0.0f, -clipMinimum.x);
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipDimensions.y);
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
xColor textColor = getTextColorX();
glm::vec4 textColorV4(textColor.red / MAX_COLOR, textColor.green / MAX_COLOR, textColor.blue / MAX_COLOR, 1.0f);
QStringList lines = _text.split("\n");
int lineOffset = maxHeight;
foreach(QString thisLine, lines) {
textRenderer->draw(0, lineOffset, qPrintable(thisLine), textColorV4);
lineOffset += maxHeight;
}
glDisable(GL_CLIP_PLANE0);
glDisable(GL_CLIP_PLANE1);
glDisable(GL_CLIP_PLANE2);
glDisable(GL_CLIP_PLANE3);
glm::vec4 textColor(toGlm(getTextColorX()), alpha);
// this is a ratio determined through experimentation
const float scaleFactor = 0.08f * _lineHeight;
glScalef(scaleFactor, -scaleFactor, scaleFactor);
glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor);
textRenderer->draw(0, 0, _text, textColor, bounds);
}
glPopMatrix();
}

View file

@ -629,8 +629,6 @@ void EntityItem::setMass(float mass) {
}
}
const float ENTITY_ITEM_EPSILON_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE;
void EntityItem::simulate(const quint64& now) {
if (_lastSimulated == 0) {
_lastSimulated = now;

View file

@ -26,6 +26,13 @@ void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
}
bool EntityScriptingInterface::canAdjustLocks() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanAdjustLocks();
}
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
// The application will keep track of creatorTokenID
@ -108,7 +115,7 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E
// the actual id, because we can edit out local entities just with creatorTokenID
if (_entityTree) {
_entityTree->lockForWrite();
_entityTree->updateEntity(entityID, properties);
_entityTree->updateEntity(entityID, properties, canAdjustLocks());
_entityTree->unlock();
}

View file

@ -61,6 +61,10 @@ public:
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
public slots:
// returns true if the DomainServer will allow this Node/Avatar to make changes
Q_INVOKABLE bool canAdjustLocks();
/// adds a model with the specific properties
Q_INVOKABLE EntityItemID addEntity(const EntityItemProperties& properties);

View file

@ -93,7 +93,7 @@ void EntityTree::postAddEntity(EntityItem* entity) {
emit addingEntity(entity->getEntityItemID());
}
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange) {
EntityTreeElement* containingElement = getContainingElement(entityID);
if (!containingElement) {
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID;
@ -106,21 +106,27 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
return false;
}
return updateEntityWithElement(existingEntity, properties, containingElement);
return updateEntityWithElement(existingEntity, properties, containingElement, allowLockChange);
}
bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties) {
bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange) {
EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID());
if (!containingElement) {
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID="
<< entity->getEntityItemID();
return false;
}
return updateEntityWithElement(entity, properties, containingElement);
return updateEntityWithElement(entity, properties, containingElement, allowLockChange);
}
bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
EntityTreeElement* containingElement) {
EntityTreeElement* containingElement, bool allowLockChange) {
if (!allowLockChange && (entity->getLocked() != properties.getLocked())) {
qDebug() << "Refusing disallowed lock adjustment.";
return false;
}
// enforce support for locked entities. If an entity is currently locked, then the only
// property we allow you to change is the locked property.
if (entity->getLocked()) {
@ -586,7 +592,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
// if the EntityItem exists, then update it
if (existingEntity) {
updateEntity(entityItemID, properties);
updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks());
existingEntity->markAsChangedOnServer();
} else {
qDebug() << "User attempted to edit an unknown entity. ID:" << entityItemID;

View file

@ -86,10 +86,10 @@ public:
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
// use this method if you only know the entityID
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange);
// use this method if you have a pointer to the entity (avoid an extra entity lookup)
bool updateEntity(EntityItem* entity, const EntityItemProperties& properties);
bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange);
void deleteEntity(const EntityItemID& entityID, bool force = false);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false);
@ -162,7 +162,7 @@ private:
void processRemovedEntities(const DeleteEntityOperator& theOperator);
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
EntityTreeElement* containingElement);
EntityTreeElement* containingElement, bool allowLockChange);
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);

View file

@ -1219,7 +1219,7 @@ int matchTextureUVSetToAttributeChannel(const QString& texUVSetName, const QHash
FBXLight extractLight(const FBXNode& object) {
FBXLight light;
int unkwnon = 0;
foreach (const FBXNode& subobject, object.children) {
QString childname = QString(subobject.name);
if (subobject.name == "Properties70") {
@ -1230,6 +1230,8 @@ FBXLight extractLight(const FBXNode& object) {
QString propname = property.properties.at(0).toString();
if (propname == "Intensity") {
light.intensity = 0.01f * property.properties.at(valIndex).value<double>();
} else if (propname == "Color") {
light.color = getVec3(property.properties, valIndex);
}
}
}
@ -1818,6 +1820,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
if (lightmapLevel <= 0.0f) {
loadLightmaps = false;
}
lightmapOffset = glm::clamp((*lit).second.color.x, 0.f, 1.f);
}
}
}
@ -2128,6 +2131,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
glm::vec2 emissiveParams(0.f, 1.f);
emissiveParams.x = lightmapOffset;
emissiveParams.y = lightmapLevel;
QString emissiveTextureID = emissiveTextures.value(childID);
QString ambientTextureID = ambientTextures.value(childID);
if (loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) {

View file

@ -179,12 +179,14 @@ public:
QString name;
Transform transform;
float intensity;
float fogValue;
glm::vec3 color;
FBXLight() :
name(),
transform(),
intensity(1.0f),
fogValue(0.0f),
color(1.0f)
{}
};

View file

@ -271,7 +271,12 @@ void GLBackend::syncGPUObject(const Texture& texture) {
if (texture.isAutogenerateMips()) {
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
// At this point the mip piels have been loaded, we can notify
texture.notifyGPULoaded(0);
glBindTexture(GL_TEXTURE_2D, boundTex);
object->_contentStamp = texture.getDataStamp();
}
@ -302,6 +307,9 @@ void GLBackend::syncGPUObject(const Texture& texture) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
// At this point the mip piels have been loaded, we can notify
texture.notifyGPULoaded(0);
glBindTexture(GL_TEXTURE_2D, boundTex);
object->_storageStamp = texture.getStamp();
object->_size = texture.getSize();

View file

@ -17,7 +17,8 @@ using namespace gpu;
Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) :
_sysmem(size, bytes),
_format(format) {
_format(format),
_isGPULoaded(false) {
}
Texture::Pixels::~Pixels() {
@ -53,6 +54,14 @@ const Texture::PixelsPointer Texture::Storage::getMip(uint16 level) const {
return PixelsPointer();
}
void Texture::Storage::notifyGPULoaded(uint16 level) const {
PixelsPointer mip = getMip(level);
if (mip) {
mip->_isGPULoaded = true;
mip->_sysmem.resize(0);
}
}
bool Texture::Storage::isMipAvailable(uint16 level) const {
PixelsPointer mip = getMip(level);
return (mip && mip->_sysmem.getSize());

View file

@ -28,6 +28,7 @@ public:
Sysmem _sysmem;
Element _format;
bool _isGPULoaded;
};
typedef QSharedPointer< Pixels > PixelsPointer;
@ -42,7 +43,8 @@ public:
virtual bool allocateMip(uint16 level);
virtual bool assignMipData(uint16 level, const Element& format, Size size, const Byte* bytes);
virtual bool isMipAvailable(uint16 level) const;
virtual void notifyGPULoaded(uint16 level) const;
protected:
Texture* _texture;
std::vector<PixelsPointer> _mips;
@ -168,7 +170,8 @@ public:
// Access the the sub mips
bool isStoredMipAvailable(uint16 level) const { return _storage->isMipAvailable(level); }
const PixelsPointer accessStoredMip(uint16 level) const { return _storage->getMip(level); }
void notifyGPULoaded(uint16 level) const { return _storage->notifyGPULoaded(level); }
// access sizes for the stored mips
uint16 getStoredMipWidth(uint16 level) const;
uint16 getStoredMipHeight(uint16 level) const;

View file

@ -411,7 +411,8 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
}
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) {
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool canAdjustLocks) {
NodeHash::const_iterator it = _nodeHash.find(uuid);
if (it != _nodeHash.end()) {
@ -419,11 +420,12 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket);
matchingNode->setCanAdjustLocks(canAdjustLocks);
return matchingNode;
} else {
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket);
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks);
SharedNodePointer newNodePointer(newNode);
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));

View file

@ -76,6 +76,9 @@ class LimitedNodeList : public QObject, public Dependency {
public:
const QUuid& getSessionUUID() const { return _sessionUUID; }
void setSessionUUID(const QUuid& sessionUUID);
bool getThisNodeCanAdjustLocks() { return _thisNodeCanAdjustLocks; }
void setThisNodeCanAdjustLocks(bool canAdjustLocks) { _thisNodeCanAdjustLocks = canAdjustLocks; }
void rebindNodeSocket();
QUdpSocket& getNodeSocket() { return _nodeSocket; }
@ -106,7 +109,7 @@ public:
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks);
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
@ -201,6 +204,7 @@ protected:
void handleNodeKill(const SharedNodePointer& node);
QUuid _sessionUUID;
bool _thisNodeCanAdjustLocks;
NodeHash _nodeHash;
QReadWriteLock _nodeMutex;
QUdpSocket _nodeSocket;

View file

@ -41,8 +41,9 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
return matchedTypeName != TypeNameHash.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME;
}
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) :
NetworkPeer(uuid, publicSocket, localSocket),
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
const HifiSockAddr& localSocket, bool canAdjustLocks) :
NetworkPeer(uuid, publicSocket, localSocket),
_type(type),
_activeSocket(NULL),
_symmetricSocket(),
@ -52,7 +53,8 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
_pingMs(-1), // "Uninitialized"
_clockSkewUsec(0),
_mutex(),
_clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples
_clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples
_canAdjustLocks(canAdjustLocks)
{
}
@ -131,6 +133,7 @@ QDataStream& operator<<(QDataStream& out, const Node& node) {
out << node._uuid;
out << node._publicSocket;
out << node._localSocket;
out << node._canAdjustLocks;
return out;
}
@ -140,6 +143,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) {
in >> node._uuid;
in >> node._publicSocket;
in >> node._localSocket;
in >> node._canAdjustLocks;
return in;
}

View file

@ -45,7 +45,8 @@ namespace NodeType {
class Node : public NetworkPeer {
Q_OBJECT
public:
Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
Node(const QUuid& uuid, NodeType_t type,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks);
~Node();
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
@ -76,6 +77,9 @@ public:
virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
void setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; }
bool getCanAdjustLocks() { return _canAdjustLocks; }
void activatePublicSocket();
void activateLocalSocket();
@ -101,6 +105,7 @@ private:
int _clockSkewUsec;
QMutex _mutex;
MovingPercentile _clockSkewMovingPercentile;
bool _canAdjustLocks;
};
QDebug operator<<(QDebug debug, const Node &message);

View file

@ -370,13 +370,6 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
int readNodes = 0;
// setup variables to read into from QDataStream
qint8 nodeType;
QUuid nodeUUID, connectionUUID;
HifiSockAddr nodePublicSocket;
HifiSockAddr nodeLocalSocket;
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
@ -385,10 +378,20 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
QUuid newUUID;
packetStream >> newUUID;
setSessionUUID(newUUID);
bool thisNodeCanAdjustLocks;
packetStream >> thisNodeCanAdjustLocks;
setThisNodeCanAdjustLocks(thisNodeCanAdjustLocks);
// pull each node in the packet
while(packetStream.device()->pos() < packet.size()) {
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket;
// setup variables to read into from QDataStream
qint8 nodeType;
QUuid nodeUUID, connectionUUID;
HifiSockAddr nodePublicSocket, nodeLocalSocket;
bool canAdjustLocks;
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks;
// if the public socket address is 0 then it's reachable at the same IP
// as the domain server
@ -396,7 +399,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
nodePublicSocket.setAddress(_domainHandler.getIP());
}
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket);
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket, canAdjustLocks);
packetStream >> connectionUUID;
node->setConnectionSecret(connectionUUID);

View file

@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType type) {
return 2;
case PacketTypeDomainList:
case PacketTypeDomainListRequest:
return 3;
return 4;
case PacketTypeCreateAssignment:
case PacketTypeRequestAssignment:
return 2;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
#include "MatrixStack.h"

View file

@ -0,0 +1,204 @@
/************************************************************************************
Authors : Bradley Austin Davis <bdavis@saintandreas.org>
Copyright : Copyright Brad Davis. All Rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************************/
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/noise.hpp>
#include <glm/gtc/epsilon.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <stack>
#include <gpu/GPUConfig.h>
class MatrixStack : public std::stack<glm::mat4> {
public:
MatrixStack() {
push(glm::mat4());
}
explicit MatrixStack(const MatrixStack & other) {
*((std::stack<glm::mat4>*)this) = *((std::stack<glm::mat4>*)&other);
}
operator const glm::mat4 & () const {
return top();
}
MatrixStack & pop() {
std::stack<glm::mat4>::pop();
assert(!empty());
return *this;
}
MatrixStack & push() {
emplace(top());
return *this;
}
MatrixStack & identity() {
top() = glm::mat4();
return *this;
}
MatrixStack & push(const glm::mat4 & mat) {
std::stack<glm::mat4>::push(mat);
return *this;
}
MatrixStack & rotate(const glm::mat3 & rotation) {
return postMultiply(glm::mat4(rotation));
}
MatrixStack & rotate(const glm::quat & rotation) {
return postMultiply(glm::mat4_cast(rotation));
}
MatrixStack & rotate(float theta, const glm::vec3 & axis) {
return postMultiply(glm::rotate(glm::mat4(), theta, axis));
}
MatrixStack & translate(float translation) {
return translate(glm::vec3(translation, 0, 0));
}
MatrixStack & translate(const glm::vec2 & translation) {
return translate(glm::vec3(translation, 0));
}
MatrixStack & translate(const glm::vec3 & translation) {
return postMultiply(glm::translate(glm::mat4(), translation));
}
MatrixStack & preTranslate(const glm::vec3 & translation) {
return preMultiply(glm::translate(glm::mat4(), translation));
}
MatrixStack & scale(float factor) {
return scale(glm::vec3(factor));
}
MatrixStack & scale(const glm::vec3 & scale) {
return postMultiply(glm::scale(glm::mat4(), scale));
}
MatrixStack & transform(const glm::mat4 & xfm) {
return postMultiply(xfm);
}
MatrixStack & preMultiply(const glm::mat4 & xfm) {
top() = xfm * top();
return *this;
}
MatrixStack & postMultiply(const glm::mat4 & xfm) {
top() *= xfm;
return *this;
}
// Remove the rotation component of a matrix. useful for billboarding
MatrixStack & unrotate() {
glm::quat inverse = glm::inverse(glm::quat_cast(top()));
top() = top() * glm::mat4_cast(inverse);
return *this;
}
// Remove the translation component of a matrix. useful for skyboxing
MatrixStack & untranslate() {
top()[3] = glm::vec4(0, 0, 0, 1);
return *this;
}
template <typename Function>
void withPush(Function f) {
size_t startingDepth = size();
push();
f();
pop();
assert(startingDepth = size());
}
template <typename Function>
void withIdentity(Function f) {
withPush([&] {
identity();
f();
});
}
static MatrixStack & projection() {
static MatrixStack projection;
return projection;
}
static MatrixStack & modelview() {
static MatrixStack modelview;
return modelview;
}
template <typename Function>
static void withPushAll(Function f) {
withPush(projection(), modelview(), f);
}
template <typename Function>
static void withIdentityAll(Function f) {
withPush(projection(), modelview(), [=] {
projection().identity();
modelview().identity();
f();
});
}
template <typename Function>
static void withPush(MatrixStack & stack, Function f) {
stack.withPush(f);
}
template <typename Function>
static void withPush(MatrixStack & stack1, MatrixStack & stack2, Function f) {
stack1.withPush([&]{
stack2.withPush(f);
});
}
template <typename Function>
static void withGlMatrices(Function f) {
// Push the current stack, and then copy the values out of OpenGL
withPushAll([&] {
// Fetch the current matrices out of GL stack
// FIXME, eliminate the usage of deprecated GL
MatrixStack & mv = MatrixStack::modelview();
MatrixStack & pr = MatrixStack::projection();
glm::mat4 & mvm = mv.top();
glGetFloatv(GL_MODELVIEW_MATRIX, &(mvm[0][0]));
glm::mat4 & prm = pr.top();
glGetFloatv(GL_PROJECTION_MATRIX, &(prm[0][0]));
f();
});
}
};

View file

@ -9,319 +9,517 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <gpu/GPUConfig.h>
#include <gpu/GPUConfig.h>
#include <QApplication>
#include <QDesktopWidget>
#include <QFont>
#include <QPaintEngine>
#include <QtDebug>
#include <QString>
#include <QStringList>
#include <QWindow>
#include <QBuffer>
#include <QFile>
#include "TextRenderer.h"
// FIXME, decouple from the GL headers
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include "glm/glm.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "gpu/GLBackend.h"
#include "gpu/Stream.h"
#include "GLMHelpers.h"
#include "FontInconsolataMedium.h"
#include "FontRoboto.h"
#include "FontTimeless.h"
#include "FontCourierPrime.h"
#include "MatrixStack.h"
#include "TextRenderer.h"
// the width/height of the cached glyph textures
const int IMAGE_SIZE = 512;
#include "sdf_text_vert.h"
#include "sdf_text_frag.h"
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
return qHash(key.font.family(), qHash(key.font.pointSize(), seed));
// Helper functions for reading binary data from an IO device
template<class T>
void readStream(QIODevice & in, T & t) {
in.read((char*) &t, sizeof(t));
}
static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) {
return p1.font == p2.font && p1.effect == p2.effect && p1.effectThickness == p2.effectThickness && p1.color == p2.color;
template<typename T, size_t N>
void readStream(QIODevice & in, T (&t)[N]) {
in.read((char*) t, N);
}
TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic,
EffectType effect, int effectThickness, const QColor& color) {
Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color };
TextRenderer*& instance = _instances[properties];
if (!instance) {
instance = new TextRenderer(properties);
template<class T, size_t N>
void fillBuffer(QBuffer & buffer, T (&t)[N]) {
buffer.setData((const char*) t, N);
}
// FIXME support the shadow effect, or remove it from the API
// FIXME figure out how to improve the anti-aliasing on the
// interior of the outline fonts
// stores the font metrics for a single character
struct Glyph {
QChar c;
glm::vec2 texOffset;
glm::vec2 texSize;
glm::vec2 size;
glm::vec2 offset;
float d; // xadvance - adjusts character positioning
size_t indexOffset;
QRectF bounds() const;
QRectF textureBounds(const glm::vec2 & textureSize) const;
void read(QIODevice & in);
};
void Glyph::read(QIODevice & in) {
uint16_t charcode;
readStream(in, charcode);
c = charcode;
readStream(in, texOffset);
readStream(in, size);
readStream(in, offset);
readStream(in, d);
texSize = size;
}
const float DEFAULT_POINT_SIZE = 12;
class Font {
public:
using TexturePtr = QSharedPointer < QOpenGLTexture >;
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >;
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
// maps characters to cached glyph info
// HACK... the operator[] const for QHash returns a
// copy of the value, not a const value reference, so
// we declare the hash as mutable in order to avoid such
// copies
mutable QHash<QChar, Glyph> _glyphs;
// the id of the glyph texture to which we're currently writing
GLuint _currentTextureID;
int _pointSize;
// the height of the current row of characters
int _rowHeight;
QString _family;
float _fontSize { 0 };
float _leading { 0 };
float _ascent { 0 };
float _descent { 0 };
float _spaceWidth { 0 };
BufferPtr _vertices;
BufferPtr _indices;
TexturePtr _texture;
VertexArrayPtr _vao;
QImage _image;
ProgramPtr _program;
const Glyph & getGlyph(const QChar & c) const;
void read(QIODevice & path);
// Initialize the OpenGL structures
void setupGL();
glm::vec2 computeExtent(const QString & str) const;
glm::vec2 computeTokenExtent(const QString & str) const;
glm::vec2 drawString(float x, float y, const QString & str,
const glm::vec4& color, TextRenderer::EffectType effectType,
const glm::vec2& bound) const;
private:
QStringList tokenizeForWrapping(const QString & str) const;
};
static QHash<QString, Font *> LOADED_FONTS;
Font * loadFont(QIODevice & buffer) {
Font * result = new Font();
result->read(buffer);
return result;
}
template<class T, size_t N>
Font * loadFont(T (&t)[N]) {
QBuffer buffer;
buffer.setData((const char*) t, N);
buffer.open(QBuffer::ReadOnly);
return loadFont(buffer);
}
Font * loadFont(const QString & family) {
if (!LOADED_FONTS.contains(family)) {
if (family == MONO_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_COURIER_PRIME);
} else if (family == INCONSOLATA_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_INCONSOLATA_MEDIUM);
} else if (family == SANS_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_ROBOTO);
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
LOADED_FONTS[SERIF_FONT_FAMILY] = loadFont(SDFF_TIMELESS);
}
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
}
}
return LOADED_FONTS[family];
}
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
const Glyph & Font::getGlyph(const QChar & c) const {
if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')];
}
return _glyphs[c];
}
void Font::read(QIODevice & in) {
uint8_t header[4];
readStream(in, header);
if (memcmp(header, "SDFF", 4)) {
qFatal("Bad SDFF file");
}
uint16_t version;
readStream(in, version);
// read font name
_family = "";
if (version > 0x0001) {
char c;
readStream(in, c);
while (c) {
_family += c;
readStream(in, c);
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
_rowHeight = _fontSize + _descent;
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) {
g.read(in);
});
// read image data
if (!_image.loadFromData(in.readAll(), "PNG")) {
qFatal("Failed to read SDFF image");
}
_glyphs.clear();
foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
glm::vec2 imageSize = toGlm(_image.size());
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
setupGL();
}
struct TextureVertex {
glm::vec2 pos;
glm::vec2 tex;
TextureVertex() {
}
TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex) :
pos(pos), tex(tex) {
}
TextureVertex(const QPointF & pos, const QPointF & tex) :
pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) {
}
};
struct QuadBuilder {
TextureVertex vertices[4];
QuadBuilder(const QRectF & r, const QRectF & tr) {
vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft());
vertices[1] = TextureVertex(r.bottomRight(), tr.topRight());
vertices[2] = TextureVertex(r.topRight(), tr.bottomRight());
vertices[3] = TextureVertex(r.topLeft(), tr.bottomLeft());
}
};
QRectF Glyph::bounds() const {
return glmToRect(offset, size);
}
QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const {
return glmToRect(texOffset, texSize);
}
void Font::setupGL() {
_texture = TexturePtr(
new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps));
_program = ProgramPtr(new QOpenGLShaderProgram());
if (!_program->create()) {
qFatal("Could not create text shader");
}
if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || //
!_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || //
!_program->link()) {
qFatal("%s", _program->log().toLocal8Bit().constData());
}
std::vector<TextureVertex> vertexData;
std::vector<GLuint> indexData;
vertexData.reserve(_glyphs.size() * 4);
std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) {
GLuint index = (GLuint)vertexData.size();
QRectF bounds = m.bounds();
QRectF texBounds = m.textureBounds(toGlm(_image.size()));
QuadBuilder qb(bounds, texBounds);
for (int i = 0; i < 4; ++i) {
vertexData.push_back(qb.vertices[i]);
}
m.indexOffset = indexData.size() * sizeof(GLuint);
// FIXME use triangle strips + primitive restart index
indexData.push_back(index + 0);
indexData.push_back(index + 1);
indexData.push_back(index + 2);
indexData.push_back(index + 0);
indexData.push_back(index + 2);
indexData.push_back(index + 3);
});
_vao = VertexArrayPtr(new QOpenGLVertexArrayObject());
_vao->create();
_vao->bind();
_vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
_vertices->create();
_vertices->bind();
_vertices->allocate(&vertexData[0],
sizeof(TextureVertex) * vertexData.size());
_indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer));
_indices->create();
_indices->bind();
_indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size());
GLsizei stride = (GLsizei) sizeof(TextureVertex);
void* offset = (void*) offsetof(TextureVertex, tex);
int posLoc = _program->attributeLocation("Position");
int texLoc = _program->attributeLocation("TexCoord");
glEnableVertexAttribArray(posLoc);
glVertexAttribPointer(posLoc, 3, GL_FLOAT, false, stride, nullptr);
glEnableVertexAttribArray(texLoc);
glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset);
_vao->release();
}
// FIXME there has to be a cleaner way of doing this
QStringList Font::tokenizeForWrapping(const QString & str) const {
QStringList result;
foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) {
bool lineFeed = false;
foreach(const QString & token2, token1.split("\n")) {
if (lineFeed) {
result << "\n";
}
if (token2.size()) {
result << token2;
}
lineFeed = true;
}
}
return result;
}
glm::vec2 Font::computeTokenExtent(const QString & token) const {
glm::vec2 advance(0, _rowHeight - _descent);
foreach(QChar c, token) {
assert(c != ' ' && c != '\n');
const Glyph & m = getGlyph(c);
advance.x += m.d;
}
return advance;
}
glm::vec2 Font::computeExtent(const QString & str) const {
glm::vec2 extent(0, _rowHeight - _descent);
// FIXME, come up with a better method of splitting text
// that will allow wrapping but will preserve things like
// tabs or consecutive spaces
bool firstTokenOnLine = true;
float lineWidth = 0.0f;
QStringList tokens = tokenizeForWrapping(str);
foreach(const QString & token, tokens) {
if (token == "\n") {
extent.x = std::max(lineWidth, extent.x);
lineWidth = 0.0f;
extent.y += _rowHeight;
firstTokenOnLine = true;
continue;
}
if (!firstTokenOnLine) {
lineWidth += _spaceWidth;
}
lineWidth += computeTokenExtent(token).x;
firstTokenOnLine = false;
}
extent.x = std::max(lineWidth, extent.x);
return extent;
}
// FIXME support the maxWidth parameter and allow the text to automatically wrap
// even without explicit line feeds.
glm::vec2 Font::drawString(float x, float y, const QString & str,
const glm::vec4& color, TextRenderer::EffectType effectType,
const glm::vec2& bounds) const {
// Stores how far we've moved from the start of the string, in DTP units
glm::vec2 advance(0, -_rowHeight - _descent);
_program->bind();
_program->setUniformValue("Color", color.r, color.g, color.b, color.a);
_program->setUniformValue("Projection",
fromGlm(MatrixStack::projection().top()));
if (effectType == TextRenderer::OUTLINE_EFFECT) {
_program->setUniformValue("Outline", true);
}
// Needed?
glEnable(GL_TEXTURE_2D);
_texture->bind();
_vao->bind();
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
mv.translate(glm::vec3(0, _ascent, 0));
foreach(const QString & token, tokenizeForWrapping(str)) {
if (token == "\n") {
advance.x = 0.0f;
advance.y -= _rowHeight;
// If we've wrapped right out of the bounds, then we're
// done with rendering the tokens
if (bounds.y > 0 && abs(advance.y) > bounds.y) {
break;
}
continue;
}
glm::vec2 tokenExtent = computeTokenExtent(token);
if (bounds.x > 0 && advance.x > 0) {
// We check if we'll be out of bounds
if (advance.x + tokenExtent.x >= bounds.x) {
// We're out of bounds, so wrap to the next line
advance.x = 0.0f;
advance.y -= _rowHeight;
// If we've wrapped right out of the bounds, then we're
// done with rendering the tokens
if (bounds.y > 0 && abs(advance.y) > bounds.y) {
break;
}
}
}
foreach(const QChar & c, token) {
// get metrics for this character to speed up measurements
const Glyph & m = getGlyph(c);
// We create an offset vec2 to hold the local offset of this character
// This includes compensating for the inverted Y axis of the font
// coordinates
glm::vec2 offset(advance);
offset.y -= m.size.y;
// Bind the new position
mv.withPush([&] {
mv.translate(offset);
// FIXME find a better (and GL ES 3.1 compatible) way of rendering the text
// that doesn't involve a single GL call per character.
// Most likely an 'indirect' call or an 'instanced' call.
_program->setUniformValue("ModelView", fromGlm(mv.top()));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset));
});
advance.x += m.d;
}
advance.x += _spaceWidth;
}
_vao->release();
_program->release();
// FIXME, needed?
// glDisable(GL_TEXTURE_2D);
return advance;
}
TextRenderer* TextRenderer::getInstance(const char* family, float pointSize,
int weight, bool italic, EffectType effect, int effectThickness,
const QColor& color) {
if (pointSize < 0) {
pointSize = DEFAULT_POINT_SIZE;
}
return new TextRenderer(family, pointSize, weight, italic, effect,
effectThickness, color);
}
TextRenderer::TextRenderer(const char* family, float pointSize, int weight,
bool italic, EffectType effect, int effectThickness,
const QColor& color) :
_effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), _color(color), _font(loadFont(family)) {
if (1 != _effectThickness) {
qWarning() << "Effect thickness not current supported";
}
if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
qWarning() << "Effect thickness not current supported";
}
return instance;
}
TextRenderer::~TextRenderer() {
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
}
int TextRenderer::calculateHeight(const char* str) {
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
}
return maxHeight;
glm::vec2 TextRenderer::computeExtent(const QString & str) const {
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
return _font->computeExtent(str) * scale;
}
int TextRenderer::draw(int x, int y, const char* str, const glm::vec4& color) {
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
((int(color.y * 255.0f) & 0xFF) << 8) |
((int(color.z * 255.0f) & 0xFF) << 16) |
((int(color.w * 255.0f) & 0xFF) << 24);
int maxHeight = 0;
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
x += glyph.width();
continue;
}
if (glyph.bounds().height() > maxHeight) {
maxHeight = glyph.bounds().height();
}
//glBindTexture(GL_TEXTURE_2D, glyph.textureID());
int left = x + glyph.bounds().x();
int right = x + glyph.bounds().x() + glyph.bounds().width();
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
glm::vec2 leftBottom = glm::vec2(float(left), float(bottom));
glm::vec2 rightTop = glm::vec2(float(right), float(top));
float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
const int NUM_COORDS_SCALARS_PER_GLYPH = 16;
float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt,
rightTop.x, leftBottom.y, rs, bt,
rightTop.x, rightTop.y, rs, tt,
leftBottom.x, rightTop.y, ls, tt, };
const int NUM_COLOR_SCALARS_PER_GLYPH = 4;
int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor };
gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched;
gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched;
if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer->getSize()) {
_glyphsBuffer->append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
_glyphsColorBuffer->append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
} else {
_glyphsBuffer->setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
_glyphsColorBuffer->setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
}
_numGlyphsBatched++;
x += glyph.width();
float TextRenderer::draw(float x, float y, const QString & str,
const glm::vec4& color, const glm::vec2 & bounds) {
glm::vec4 actualColor(color);
if (actualColor.r < 0) {
actualColor = toGlm(_color);
}
// TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call
drawBatch();
clearBatch();
return maxHeight;
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
glm::vec2 result;
MatrixStack::withGlMatrices([&] {
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
// FIXME migrate the constant scale factor into the geometry of the
// fonts so we don't have to flip the Y axis here and don't have to
// scale at all.
mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale));
// The font does all the OpenGL work
result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale);
});
return result.x;
}
int TextRenderer::computeWidth(char ch)
{
return getGlyph(ch).width();
}
int TextRenderer::computeWidth(const char* str)
{
int width = 0;
for (const char* ch = str; *ch != 0; ch++) {
width += computeWidth(*ch);
}
return width;
}
TextRenderer::TextRenderer(const Properties& properties) :
_font(properties.font),
_metrics(_font),
_effectType(properties.effect),
_effectThickness(properties.effectThickness),
_x(IMAGE_SIZE),
_y(IMAGE_SIZE),
_rowHeight(0),
_color(properties.color),
_glyphsBuffer(new gpu::Buffer()),
_glyphsColorBuffer(new gpu::Buffer()),
_glyphsStreamFormat(new gpu::Stream::Format()),
_glyphsStream(new gpu::BufferStream()),
_numGlyphsBatched(0)
{
_glyphsStreamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
const int NUM_POS_COORDS = 2;
const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float);
_glyphsStreamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
_glyphsStreamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA));
_glyphsStream->addBuffer(_glyphsBuffer, 0, _glyphsStreamFormat->getChannels().at(0)._stride);
_glyphsStream->addBuffer(_glyphsColorBuffer, 0, _glyphsStreamFormat->getChannels().at(1)._stride);
_font.setKerning(false);
}
const Glyph& TextRenderer::getGlyph(char c) {
Glyph& glyph = _glyphs[c];
if (glyph.isValid()) {
return glyph;
}
// we use 'J' as a representative size for the solid block character
QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c);
QRect baseBounds = _metrics.boundingRect(ch);
if (baseBounds.isEmpty()) {
glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch));
return glyph;
}
// grow the bounds to account for effect, if any
if (_effectType == SHADOW_EFFECT) {
baseBounds.adjust(-_effectThickness, 0, 0, _effectThickness);
} else if (_effectType == OUTLINE_EFFECT) {
baseBounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness);
}
// grow the bounds to account for antialiasing
baseBounds.adjust(-1, -1, 1, 1);
// adjust bounds for device pixel scaling
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio();
QRect bounds(baseBounds.x() * ratio, baseBounds.y() * ratio, baseBounds.width() * ratio, baseBounds.height() * ratio);
if (_x + bounds.width() > IMAGE_SIZE) {
// we can't fit it on the current row; move to next
_y += _rowHeight;
_x = _rowHeight = 0;
}
if (_y + bounds.height() > IMAGE_SIZE) {
// can't fit it on current texture; make a new one
glGenTextures(1, &_currentTextureID);
_x = _y = _rowHeight = 0;
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
_allTextureIDs.append(_currentTextureID);
} else {
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
}
// render the glyph into an image and copy it into the texture
QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32);
if (c == SOLID_BLOCK_CHAR) {
image.fill(_color);
} else {
image.fill(0);
QPainter painter(&image);
QFont font = _font;
if (ratio == 1.0f) {
painter.setFont(_font);
} else {
QFont enlargedFont = _font;
enlargedFont.setPointSize(_font.pointSize() * ratio);
painter.setFont(enlargedFont);
}
if (_effectType == SHADOW_EFFECT) {
for (int i = 0; i < _effectThickness * ratio; i++) {
painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch);
}
} else if (_effectType == OUTLINE_EFFECT) {
QPainterPath path;
QFont font = _font;
font.setStyleStrategy(QFont::ForceOutline);
path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch);
QPen pen;
pen.setWidth(_effectThickness * ratio);
pen.setJoinStyle(Qt::RoundJoin);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing);
painter.drawPath(path);
}
painter.setPen(_color);
painter.drawText(-bounds.x(), -bounds.y(), ch);
}
glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
glyph = Glyph(_currentTextureID, QPoint(_x / ratio, _y / ratio), baseBounds, _metrics.width(ch));
_x += bounds.width();
_rowHeight = qMax(_rowHeight, bounds.height());
glBindTexture(GL_TEXTURE_2D, 0);
return glyph;
}
void TextRenderer::drawBatch() {
if (_numGlyphsBatched <= 0) {
return;
}
// TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
/*
GLint matrixMode;
glGetIntegerv(GL_MATRIX_MODE, &matrixMode);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
*/
gpu::Batch batch;
glEnable(GL_TEXTURE_2D);
// TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
batch.setInputFormat(_glyphsStreamFormat);
batch.setInputStream(0, *_glyphsStream);
batch.draw(gpu::QUADS, _numGlyphsBatched * 4, 0);
gpu::GLBackend::renderBatch(batch);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
// TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
/*
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(matrixMode);
*/
}
void TextRenderer::clearBatch() {
_numGlyphsBatched = 0;
}
QHash<TextRenderer::Properties, TextRenderer*> TextRenderer::_instances;
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
}

View file

@ -14,7 +14,7 @@
#include <gpu/GPUConfig.h>
#include <glm/glm.hpp>
#include <unordered_map>
#include <QColor>
#include <QFont>
#include <QFontMetrics>
@ -25,14 +25,15 @@
#include <gpu/Resource.h>
#include <gpu/Stream.h>
// a special "character" that renders as a solid block
const char SOLID_BLOCK_CHAR = 127;
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard sans serif font family
#define SERIF_FONT_FAMILY "Timeless"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
@ -45,110 +46,44 @@ const char SOLID_BLOCK_CHAR = 127;
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
#endif
class Glyph;
class Font;
// TextRenderer is actually a fairly thin wrapper around a Font class
// defined in the cpp file.
class TextRenderer {
public:
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
class Properties {
public:
QFont font;
EffectType effect;
int effectThickness;
QColor color;
};
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
static TextRenderer* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
~TextRenderer();
const QFontMetrics& metrics() const { return _metrics; }
glm::vec2 computeExtent(const QString & str) const;
// returns the height of the tallest character
int calculateHeight(const char* str);
float draw(
float x, float y,
const QString & str,
const glm::vec4& color = glm::vec4(-1.0f),
const glm::vec2& bounds = glm::vec2(-1.0f));
// also returns the height of the tallest character
int draw(int x, int y, const char* str, const glm::vec4& color);
int computeWidth(char ch);
int computeWidth(const char* str);
void drawBatch();
void clearBatch();
private:
TextRenderer(const Properties& properties);
const Glyph& getGlyph(char c);
// the font to render
QFont _font;
// the font metrics
QFontMetrics _metrics;
TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
// the type of effect to apply
EffectType _effectType;
const EffectType _effectType;
// the thickness of the effect
int _effectThickness;
// maps characters to cached glyph info
QHash<char, Glyph> _glyphs;
// the id of the glyph texture to which we're currently writing
GLuint _currentTextureID;
// the position within the current glyph texture
int _x, _y;
// the height of the current row of characters
int _rowHeight;
// the list of all texture ids for which we're responsible
QVector<GLuint> _allTextureIDs;
const int _effectThickness;
const float _pointSize;
// text color
QColor _color;
// Graphics Buffer containing the current accumulated glyphs to render
gpu::BufferPointer _glyphsBuffer;
gpu::BufferPointer _glyphsColorBuffer;
gpu::Stream::FormatPointer _glyphsStreamFormat;
gpu::BufferStreamPointer _glyphsStream;
int _numGlyphsBatched;
const QColor _color;
static QHash<Properties, TextRenderer*> _instances;
Font * _font;
};
class Glyph {
public:
Glyph(int textureID = 0, const QPoint& location = QPoint(), const QRect& bounds = QRect(), int width = 0);
GLuint textureID() const { return _textureID; }
const QPoint& location () const { return _location; }
const QRect& bounds() const { return _bounds; }
int width () const { return _width; }
bool isValid() { return _width != 0; }
private:
// the id of the OpenGL texture containing the glyph
GLuint _textureID;
// the location of the character within the texture
QPoint _location;
// the bounds of the character
QRect _bounds;
// the width of the character (distance to next, as opposed to bounds width)
int _width;
};
#endif // hifi_TextRenderer_h

View file

@ -0,0 +1,49 @@
<@include Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// sdf_text.frag
// fragment shader
//
// Created by Bradley Austin Davis on 2015-02-04
// Based on fragment shader code from
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
uniform sampler2D Font;
uniform vec4 Color;
uniform bool Outline;
varying vec2 vTexCoord;
const float gamma = 2.6;
const float smoothing = 100.0;
const float interiorCutoff = 0.8;
const float outlineExpansion = 0.2;
void main() {
// retrieve signed distance
float sdf = texture2D(Font, vTexCoord).r;
if (Outline) {
if (sdf > interiorCutoff) {
sdf = 1.0 - sdf;
} else {
sdf += outlineExpansion;
}
}
// perform adaptive anti-aliasing of the edges
// The larger we're rendering, the less anti-aliasing we need
float s = smoothing * length(fwidth(vTexCoord));
float w = clamp( s, 0.0, 0.5);
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
// gamma correction for linear attenuation
a = pow(a, 1.0 / gamma);
if (a < 0.01) {
discard;
}
// final color
gl_FragColor = vec4(Color.rgb, a);
}

View file

@ -0,0 +1,24 @@
<@include Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// sdf_text.vert
// vertex shader
//
// Created by Brad Davis on 10/14/13.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
uniform mat4 Projection;
uniform mat4 ModelView;
attribute vec3 Position;
attribute vec2 TexCoord;
varying vec2 vTexCoord;
void main() {
vTexCoord = TexCoord;
gl_Position = Projection * ModelView * vec4(Position, 1.0);
}

View file

@ -208,6 +208,8 @@ void ScriptEngine::init() {
_isInitialized = true;
_entityScriptingInterface.init();
// register various meta-types
registerMetaTypes(this);
registerMIDIMetaTypes(this);

View file

@ -2,7 +2,7 @@ set(TARGET_NAME shared)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
# TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp)
setup_hifi_library(Network Script Widgets)
setup_hifi_library(Gui Network Script Widgets)
# call macro to include our dependency includes and bubble them up via a property on our target
include_dependency_includes()

View file

@ -302,4 +302,36 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f
// Compute the distance between the two points
float positionDistance = glm::distance(positionA, positionB);
return (positionDistance <= similarEnough);
}
}
glm::uvec2 toGlm(const QSize & size) {
return glm::uvec2(size.width(), size.height());
}
glm::ivec2 toGlm(const QPoint & pt) {
return glm::ivec2(pt.x(), pt.y());
}
glm::vec2 toGlm(const QPointF & pt) {
return glm::vec2(pt.x(), pt.y());
}
glm::vec3 toGlm(const xColor & color) {
static const float MAX_COLOR = 255.0f;
return std::move(glm::vec3(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR));
}
glm::vec4 toGlm(const QColor & color) {
return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF());
}
QMatrix4x4 fromGlm(const glm::mat4 & m) {
return QMatrix4x4(&m[0][0]).transposed();
}
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
QRectF result(pos.x, pos.y, size.x, size.y);
return result;
}

View file

@ -18,6 +18,8 @@
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QByteArray>
#include <QtGui/QMatrix4x4>
#include <QtGui/QColor>
#include "SharedUtil.h"
@ -86,5 +88,14 @@ bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientio
const float POSITION_SIMILAR_ENOUGH = 0.1f; // 0.1 meter
bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough = POSITION_SIMILAR_ENOUGH);
glm::uvec2 toGlm(const QSize & size);
glm::ivec2 toGlm(const QPoint & pt);
glm::vec2 toGlm(const QPointF & pt);
glm::vec3 toGlm(const xColor & color);
glm::vec4 toGlm(const QColor & color);
#endif // hifi_GLMHelpers_h
QMatrix4x4 fromGlm(const glm::mat4 & m);
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size);
#endif // hifi_GLMHelpers_h

View file

@ -107,7 +107,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition);
tree.updateEntity(entityID, properties);
tree.updateEntity(entityID, properties, true);
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius);
@ -147,7 +147,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition);
tree.updateEntity(entityID, properties);
tree.updateEntity(entityID, properties, true);
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius);

View file

@ -0,0 +1,19 @@
set(TARGET_NAME render-utils-tests)
setup_hifi_project(Quick Gui OpenGL)
include_glm()
#include_oglplus()
# link in the shared libraries
link_hifi_libraries(render-utils gpu shared)
if (WIN32)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
endif ()
#link_libraries(animation fbx shared gpu)
include_dependency_includes()

View file

@ -0,0 +1,225 @@
//
// main.cpp
// tests/render-utils/src
//
// Copyright 2014 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 "TextRenderer.h"
#include "MatrixStack.h"
#include <QWindow>
#include <QFile>
#include <QTime>
#include <QImage>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLContext>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QResizeEvent>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <unordered_map>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h>
class RateCounter {
std::vector<float> times;
QElapsedTimer timer;
public:
RateCounter() {
timer.start();
}
void reset() {
times.clear();
}
unsigned int count() const {
return times.size() - 1;
}
float elapsed() const {
if (times.size() < 1) {
return 0.0f;
}
float elapsed = *times.rbegin() - *times.begin();
return elapsed;
}
void increment() {
times.push_back(timer.elapsed() / 1000.0f);
}
float rate() const {
if (elapsed() == 0.0f) {
return NAN;
}
return (float) count() / elapsed();
}
};
// Create a simple OpenGL window that renders text in various ways
class QTestWindow: public QWindow {
Q_OBJECT
QOpenGLContext * _context;
QSize _size;
TextRenderer* _textRenderer[4];
RateCounter fps;
protected:
void resizeEvent(QResizeEvent * ev) override {
QWindow::resizeEvent(ev);
_size = ev->size();
resizeGl();
}
void resizeGl() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _size.width(), _size.height(), 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glViewport(0, 0, _size.width(), _size.height());
}
public:
QTestWindow();
virtual ~QTestWindow() {
}
void makeCurrent() {
_context->makeCurrent(this);
}
void draw();
};
#ifndef SERIF_FONT_FAMILY
#define SERIF_FONT_FAMILY "Times New Roman"
#endif
QTestWindow::QTestWindow() {
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(3, 2);
format.setProfile(
QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
setFormat(format);
_context = new QOpenGLContext;
_context->setFormat(format);
_context->create();
show();
makeCurrent();
qDebug() << (const char*) glGetString(GL_VERSION);
#ifdef WIN32
glewExperimental = true;
GLenum err = glewInit();
if (GLEW_OK != err) {
/* Problem: glewInit failed, something is seriously wrong. */
const GLubyte * errStr = glewGetErrorString(err);
qDebug("Error: %s\n", errStr);
}
qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
}
glGetError();
#endif
setFramePosition(QPoint(100, -900));
resize(QSize(800, 600));
_size = QSize(800, 600);
_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false);
_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false,
TextRenderer::SHADOW_EFFECT);
_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1,
false, TextRenderer::OUTLINE_EFFECT);
_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
resizeGl();
}
static const wchar_t * EXAMPLE_TEXT = L"Hello";
//static const wchar_t * EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y";
static const glm::uvec2 QUAD_OFFSET(10, 10);
static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, {
1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } };
void QTestWindow::draw() {
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT);
const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2);
const glm::uvec2 offsets[4] = { { QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x
+ QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x + QUAD_OFFSET.x, size.y
+ QUAD_OFFSET.y }, { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, };
QString str = QString::fromWCharArray(EXAMPLE_TEXT);
for (int i = 0; i < 4; ++i) {
glm::vec2 bounds = _textRenderer[i]->computeExtent(str);
glPushMatrix();
{
glTranslatef(offsets[i].x, offsets[i].y, 0);
glColor3f(0, 0, 0);
glBegin(GL_QUADS);
{
glVertex2f(0, 0);
glVertex2f(0, bounds.y);
glVertex2f(bounds.x, bounds.y);
glVertex2f(bounds.x, 0);
}
glEnd();
}
glPopMatrix();
const int testCount = 100;
for (int j = 0; j < testCount; ++j) {
// Draw backgrounds around where the text will appear
// Draw the text itself
_textRenderer[i]->draw(offsets[i].x, offsets[i].y, str.toLocal8Bit().constData(),
glm::vec4(COLORS[i], 1.0f));
}
}
_context->swapBuffers(this);
glFinish();
fps.increment();
if (fps.elapsed() >= 2.0f) {
qDebug() << "FPS: " << fps.rate();
fps.reset();
}
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
QTestWindow window;
QTimer timer;
timer.setInterval(1);
app.connect(&timer, &QTimer::timeout, &app, [&] {
window.draw();
});
timer.start();
app.exec();
return 0;
}
#include "main.moc"