mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'temp0' of https://github.com/samcake/hifi into temp1
This commit is contained in:
commit
469ee29f0b
50 changed files with 95568 additions and 521 deletions
|
@ -46,6 +46,8 @@ Agent::Agent(const QByteArray& packet) :
|
|||
_scriptEngine.setParent(this);
|
||||
|
||||
_scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
|
||||
|
||||
DependencyManager::set<ResouceCacheSharedItems>();
|
||||
}
|
||||
|
||||
void Agent::readPendingDatagrams() {
|
||||
|
|
|
@ -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 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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)
|
||||
{}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
return 2;
|
||||
case PacketTypeDomainList:
|
||||
case PacketTypeDomainListRequest:
|
||||
return 3;
|
||||
return 4;
|
||||
case PacketTypeCreateAssignment:
|
||||
case PacketTypeRequestAssignment:
|
||||
return 2;
|
||||
|
|
31760
libraries/render-utils/src/FontCourierPrime.h
Normal file
31760
libraries/render-utils/src/FontCourierPrime.h
Normal file
File diff suppressed because it is too large
Load diff
16218
libraries/render-utils/src/FontInconsolataMedium.h
Normal file
16218
libraries/render-utils/src/FontInconsolataMedium.h
Normal file
File diff suppressed because it is too large
Load diff
36290
libraries/render-utils/src/FontRoboto.h
Normal file
36290
libraries/render-utils/src/FontRoboto.h
Normal file
File diff suppressed because it is too large
Load diff
10039
libraries/render-utils/src/FontTimeless.h
Normal file
10039
libraries/render-utils/src/FontTimeless.h
Normal file
File diff suppressed because it is too large
Load diff
1
libraries/render-utils/src/MatrixStack.cpp
Normal file
1
libraries/render-utils/src/MatrixStack.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "MatrixStack.h"
|
204
libraries/render-utils/src/MatrixStack.h
Normal file
204
libraries/render-utils/src/MatrixStack.h
Normal 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
49
libraries/render-utils/src/sdf_text.slf
Normal file
49
libraries/render-utils/src/sdf_text.slf
Normal 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);
|
||||
}
|
24
libraries/render-utils/src/sdf_text.slv
Normal file
24
libraries/render-utils/src/sdf_text.slv
Normal 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);
|
||||
}
|
|
@ -208,6 +208,8 @@ void ScriptEngine::init() {
|
|||
|
||||
_isInitialized = true;
|
||||
|
||||
_entityScriptingInterface.init();
|
||||
|
||||
// register various meta-types
|
||||
registerMetaTypes(this);
|
||||
registerMIDIMetaTypes(this);
|
||||
|
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
19
tests/render-utils/CMakeLists.txt
Normal file
19
tests/render-utils/CMakeLists.txt
Normal 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()
|
||||
|
225
tests/render-utils/src/main.cpp
Normal file
225
tests/render-utils/src/main.cpp
Normal 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"
|
Loading…
Reference in a new issue