mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 20:16:16 +02:00
Merge branch 'master' into 20301
This commit is contained in:
commit
ec590791df
50 changed files with 95585 additions and 538 deletions
|
@ -46,6 +46,8 @@ Agent::Agent(const QByteArray& packet) :
|
||||||
_scriptEngine.setParent(this);
|
_scriptEngine.setParent(this);
|
||||||
|
|
||||||
_scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
|
_scriptEngine.getEntityScriptingInterface()->setPacketSender(&_entityEditSender);
|
||||||
|
|
||||||
|
DependencyManager::set<ResouceCacheSharedItems>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::readPendingDatagrams() {
|
void Agent::readPendingDatagrams() {
|
||||||
|
|
|
@ -73,6 +73,20 @@
|
||||||
"can_set": true
|
"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 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[]) :
|
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
QCoreApplication(argc, argv),
|
QCoreApplication(argc, argv),
|
||||||
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
_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
|
// we got a packetUUID we didn't recognize, just add the node
|
||||||
nodeUUID = QUuid::createUuid();
|
nodeUUID = QUuid::createUuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SharedNodePointer newNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(nodeUUID, nodeType,
|
// if this user is in the editors list (or if the editors list is empty) set the user's node's canAdjustLocks to true
|
||||||
publicSockAddr, localSockAddr);
|
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
|
// when the newNode is created the linked data is also created
|
||||||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
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,
|
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
const QByteArray& usernameSignature,
|
const QByteArray& usernameSignature,
|
||||||
|
@ -842,6 +852,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
||||||
// always send the node their own UUID back
|
// always send the node their own UUID back
|
||||||
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
|
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
|
||||||
broadcastDataStream << node->getUUID();
|
broadcastDataStream << node->getUUID();
|
||||||
|
broadcastDataStream << node->getCanAdjustLocks();
|
||||||
|
|
||||||
int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos();
|
int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos();
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ var modelURLs = [
|
||||||
var mode = 0;
|
var mode = 0;
|
||||||
var isActive = false;
|
var isActive = false;
|
||||||
|
|
||||||
|
var placingEntityID = null;
|
||||||
|
|
||||||
var toolBar = (function () {
|
var toolBar = (function () {
|
||||||
var that = {},
|
var that = {},
|
||||||
|
@ -363,7 +364,7 @@ var toolBar = (function () {
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||||
|
|
||||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||||
Entities.addEntity({
|
placingEntityID = Entities.addEntity({
|
||||||
type: "Box",
|
type: "Box",
|
||||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||||
dimensions: DEFAULT_DIMENSIONS,
|
dimensions: DEFAULT_DIMENSIONS,
|
||||||
|
@ -380,7 +381,7 @@ var toolBar = (function () {
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||||
|
|
||||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||||
Entities.addEntity({
|
placingEntityID = Entities.addEntity({
|
||||||
type: "Sphere",
|
type: "Sphere",
|
||||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||||
dimensions: DEFAULT_DIMENSIONS,
|
dimensions: DEFAULT_DIMENSIONS,
|
||||||
|
@ -396,7 +397,7 @@ var toolBar = (function () {
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||||
|
|
||||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||||
Entities.addEntity({
|
placingEntityID = Entities.addEntity({
|
||||||
type: "Light",
|
type: "Light",
|
||||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||||
dimensions: DEFAULT_DIMENSIONS,
|
dimensions: DEFAULT_DIMENSIONS,
|
||||||
|
@ -421,7 +422,7 @@ var toolBar = (function () {
|
||||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||||
|
|
||||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||||
Entities.addEntity({
|
placingEntityID = Entities.addEntity({
|
||||||
type: "Text",
|
type: "Text",
|
||||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||||
dimensions: { x: 0.5, y: 0.3, z: 0.01 },
|
dimensions: { x: 0.5, y: 0.3, z: 0.01 },
|
||||||
|
@ -533,8 +534,22 @@ var mouseCapturedByTool = false;
|
||||||
var lastMousePosition = null;
|
var lastMousePosition = null;
|
||||||
var idleMouseTimerId = null;
|
var idleMouseTimerId = null;
|
||||||
var IDLE_MOUSE_TIMEOUT = 200;
|
var IDLE_MOUSE_TIMEOUT = 200;
|
||||||
|
var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0;
|
||||||
|
|
||||||
function mouseMoveEvent(event) {
|
function mouseMoveEvent(event) {
|
||||||
|
if (placingEntityID) {
|
||||||
|
if (!placingEntityID.isKnownID) {
|
||||||
|
placingEntityID = Entities.identifyEntity(placingEntityID);
|
||||||
|
}
|
||||||
|
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||||
|
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
|
||||||
|
var offset = Vec3.multiply(distance, pickRay.direction);
|
||||||
|
var position = Vec3.sum(Camera.position, offset);
|
||||||
|
Entities.editEntity(placingEntityID, {
|
||||||
|
position: position,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -590,6 +605,10 @@ function highlightEntityUnderCursor(position, accurateRay) {
|
||||||
|
|
||||||
|
|
||||||
function mouseReleaseEvent(event) {
|
function mouseReleaseEvent(event) {
|
||||||
|
if (placingEntityID) {
|
||||||
|
selectionManager.setSelections([placingEntityID]);
|
||||||
|
placingEntityID = null;
|
||||||
|
}
|
||||||
if (isActive && selectionManager.hasSelection()) {
|
if (isActive && selectionManager.hasSelection()) {
|
||||||
tooltip.show(false);
|
tooltip.show(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
var elCollisionsWillMove = document.getElementById("property-collisions-will-move");
|
var elCollisionsWillMove = document.getElementById("property-collisions-will-move");
|
||||||
var elLifetime = document.getElementById("property-lifetime");
|
var elLifetime = document.getElementById("property-lifetime");
|
||||||
var elScriptURL = document.getElementById("property-script-url");
|
var elScriptURL = document.getElementById("property-script-url");
|
||||||
|
var elUserData = document.getElementById("property-user-data");
|
||||||
|
|
||||||
var elBoxSections = document.querySelectorAll(".box-section");
|
var elBoxSections = document.querySelectorAll(".box-section");
|
||||||
var elBoxColorRed = document.getElementById("property-box-red");
|
var elBoxColorRed = document.getElementById("property-box-red");
|
||||||
|
@ -224,6 +225,7 @@
|
||||||
elCollisionsWillMove.checked = properties.collisionsWillMove;
|
elCollisionsWillMove.checked = properties.collisionsWillMove;
|
||||||
elLifetime.value = properties.lifetime;
|
elLifetime.value = properties.lifetime;
|
||||||
elScriptURL.value = properties.script;
|
elScriptURL.value = properties.script;
|
||||||
|
elUserData.value = properties.userData;
|
||||||
|
|
||||||
if (properties.type != "Box") {
|
if (properties.type != "Box") {
|
||||||
for (var i = 0; i < elBoxSections.length; i++) {
|
for (var i = 0; i < elBoxSections.length; i++) {
|
||||||
|
@ -361,6 +363,7 @@
|
||||||
elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove'));
|
elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove'));
|
||||||
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
|
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
|
||||||
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
||||||
|
elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData'));
|
||||||
|
|
||||||
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
|
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
|
||||||
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
|
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
|
||||||
|
@ -593,6 +596,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<div class="label">User Data</div>
|
||||||
|
<div class="value">
|
||||||
|
<textarea id="property-user-data"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="box-section property">
|
<div class="box-section property">
|
||||||
<div class="label">Color</div>
|
<div class="label">Color</div>
|
||||||
|
@ -638,7 +648,7 @@
|
||||||
<div class="model-section property">
|
<div class="model-section property">
|
||||||
<div class="label">Animation Settings</div>
|
<div class="label">Animation Settings</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<textarea id="property-model-animation-settings" value='asdfasdf'></textarea>
|
<textarea id="property-model-animation-settings"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-section property">
|
<div class="model-section property">
|
||||||
|
|
|
@ -80,14 +80,6 @@ Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||||
|
|
||||||
// disable the standard application for mouse events
|
|
||||||
Controller.captureMouseEvents();
|
|
||||||
|
|
||||||
function scriptEnding() {
|
|
||||||
// re-enabled the standard application for mouse events
|
|
||||||
Controller.releaseMouseEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
MyAvatar.bodyYaw = 0;
|
MyAvatar.bodyYaw = 0;
|
||||||
MyAvatar.bodyPitch = 0;
|
MyAvatar.bodyPitch = 0;
|
||||||
MyAvatar.bodyRoll = 0;
|
MyAvatar.bodyRoll = 0;
|
||||||
|
|
|
@ -136,9 +136,6 @@ using namespace std;
|
||||||
static unsigned STARFIELD_NUM_STARS = 50000;
|
static unsigned STARFIELD_NUM_STARS = 50000;
|
||||||
static unsigned STARFIELD_SEED = 1;
|
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
|
const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
|
||||||
|
|
||||||
static QTimer* idleTimer = NULL;
|
static QTimer* idleTimer = NULL;
|
||||||
|
@ -257,6 +254,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
_scaleMirror(1.0f),
|
_scaleMirror(1.0f),
|
||||||
_rotateMirror(0.0f),
|
_rotateMirror(0.0f),
|
||||||
_raiseMirror(0.0f),
|
_raiseMirror(0.0f),
|
||||||
|
_cursorVisible(true),
|
||||||
_lastMouseMove(usecTimestampNow()),
|
_lastMouseMove(usecTimestampNow()),
|
||||||
_lastMouseMoveWasSimulated(false),
|
_lastMouseMoveWasSimulated(false),
|
||||||
_touchAvgX(0.0f),
|
_touchAvgX(0.0f),
|
||||||
|
@ -1516,6 +1514,8 @@ void Application::setEnableVRMode(bool enableVRMode) {
|
||||||
|
|
||||||
auto glCanvas = DependencyManager::get<GLCanvas>();
|
auto glCanvas = DependencyManager::get<GLCanvas>();
|
||||||
resizeGL(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight());
|
resizeGL(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight());
|
||||||
|
|
||||||
|
updateCursorVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
||||||
|
@ -1983,35 +1983,22 @@ void Application::updateCursor(float deltaTime) {
|
||||||
|
|
||||||
static QPoint lastMousePos = QPoint();
|
static QPoint lastMousePos = QPoint();
|
||||||
_lastMouseMove = (lastMousePos == QCursor::pos()) ? _lastMouseMove : usecTimestampNow();
|
_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();
|
lastMousePos = QCursor::pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setCursorVisible(bool visible) {
|
void Application::updateCursorVisibility() {
|
||||||
if (visible) {
|
if (!_cursorVisible || Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
|
||||||
if (overrideCursor() != NULL) {
|
DependencyManager::get<GLCanvas>()->setCursor(Qt::BlankCursor);
|
||||||
restoreOverrideCursor();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (overrideCursor() != NULL) {
|
DependencyManager::get<GLCanvas>()->unsetCursor();
|
||||||
changeOverrideCursor(Qt::BlankCursor);
|
|
||||||
} else {
|
|
||||||
setOverrideCursor(Qt::BlankCursor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::setCursorVisible(bool visible) {
|
||||||
|
_cursorVisible = visible;
|
||||||
|
updateCursorVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::update(float deltaTime) {
|
void Application::update(float deltaTime) {
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarnings, "Application::update()");
|
PerformanceWarning warn(showWarnings, "Application::update()");
|
||||||
|
|
|
@ -191,8 +191,7 @@ public:
|
||||||
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
|
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
|
||||||
|
|
||||||
bool isMousePressed() const { return _mousePressed; }
|
bool isMousePressed() const { return _mousePressed; }
|
||||||
bool isMouseHidden() const { return DependencyManager::get<GLCanvas>()->cursor().shape() == Qt::BlankCursor; }
|
bool isMouseHidden() const { return !_cursorVisible; }
|
||||||
void setCursorVisible(bool visible);
|
|
||||||
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
||||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||||
bool mouseOnScreen() const;
|
bool mouseOnScreen() const;
|
||||||
|
@ -403,11 +402,15 @@ private slots:
|
||||||
|
|
||||||
void audioMuteToggled();
|
void audioMuteToggled();
|
||||||
|
|
||||||
|
void setCursorVisible(bool visible);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
|
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
|
||||||
void updateProjectionMatrix();
|
void updateProjectionMatrix();
|
||||||
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
|
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
|
||||||
|
|
||||||
|
void updateCursorVisibility();
|
||||||
|
|
||||||
void sendPingPackets();
|
void sendPingPackets();
|
||||||
|
|
||||||
void initDisplay();
|
void initDisplay();
|
||||||
|
@ -519,6 +522,7 @@ private:
|
||||||
|
|
||||||
Environment _environment;
|
Environment _environment;
|
||||||
|
|
||||||
|
bool _cursorVisible;
|
||||||
int _mouseDragStartedX;
|
int _mouseDragStartedX;
|
||||||
int _mouseDragStartedY;
|
int _mouseDragStartedY;
|
||||||
quint64 _lastMouseMove;
|
quint64 _lastMouseMove;
|
||||||
|
|
|
@ -96,7 +96,7 @@ static TextRenderer* textRenderer(int mono) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int widthText(float scale, int mono, char const* string) {
|
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,
|
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);
|
glRotatef(glm::degrees(angle), 0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD);
|
float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD);
|
||||||
glScalef(scaleFactor, scaleFactor, 1.0);
|
glScalef(scaleFactor, -scaleFactor, scaleFactor); // TextRenderer::draw paints the text upside down in y axis
|
||||||
|
|
||||||
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
|
|
||||||
|
|
||||||
int text_x = -_displayNameBoundingRect.width() / 2;
|
int text_x = -_displayNameBoundingRect.width() / 2;
|
||||||
int text_y = -_displayNameBoundingRect.height() / 2;
|
int text_y = -_displayNameBoundingRect.height() / 2;
|
||||||
|
@ -913,7 +911,9 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||||
|
|
||||||
void Avatar::setDisplayName(const QString& displayName) {
|
void Avatar::setDisplayName(const QString& displayName) {
|
||||||
AvatarData::setDisplayName(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) {
|
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
|
// Check that the semantic is not already in use
|
||||||
Index foundIndex = findJointIndex(semantic);
|
Index foundIndex = findJointIndex(semantic);
|
||||||
if (foundIndex >= 0)
|
if (foundIndex >= 0) {
|
||||||
return INVALID_SEMANTIC;
|
return INVALID_SEMANTIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// All good then allocate the joint
|
// All good then allocate the joint
|
||||||
Index newIndex = _jointsArray.size();
|
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 {
|
MotionTracker::Index MotionTracker::findJointIndex(const Semantic& semantic) const {
|
||||||
// TODO C++11 auto jointIt = _jointsMap.find(semantic);
|
// TODO C++11 auto jointIt = _jointsMap.find(semantic);
|
||||||
JointTracker::Map::const_iterator jointIt = _jointsMap.find(semantic);
|
JointTracker::Map::const_iterator jointIt = _jointsMap.find(semantic);
|
||||||
if (jointIt != _jointsMap.end())
|
if (jointIt != _jointsMap.end()) {
|
||||||
return (*jointIt).second;
|
return (*jointIt).second;
|
||||||
|
}
|
||||||
|
|
||||||
return INVALID_SEMANTIC;
|
return INVALID_SEMANTIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
|
|
||||||
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
|
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
|
||||||
typedef std::string Semantic;
|
typedef std::string Semantic;
|
||||||
typedef uint32_t Index;
|
typedef int32_t Index;
|
||||||
static const Index INVALID_SEMANTIC = -1;
|
static const Index INVALID_SEMANTIC = -1;
|
||||||
static const Index INVALID_PARENT = -2;
|
static const Index INVALID_PARENT = -2;
|
||||||
|
|
||||||
|
|
|
@ -216,7 +216,7 @@ void PrioVR::renderCalibrationCountdown() {
|
||||||
false, TextRenderer::OUTLINE_EFFECT, 2);
|
false, TextRenderer::OUTLINE_EFFECT, 2);
|
||||||
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
|
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
|
||||||
auto glCanvas = DependencyManager::get<GLCanvas>();
|
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,
|
glCanvas->height() / 2,
|
||||||
text, glm::vec4(1,1,1,1));
|
text, glm::vec4(1,1,1,1));
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "SharedUtil.h"
|
#include "SharedUtil.h"
|
||||||
|
|
||||||
const int PALMROOT_NUM_JOINTS = 2;
|
|
||||||
const int FINGER_NUM_JOINTS = 4;
|
|
||||||
|
|
||||||
const DeviceTracker::Name RealSense::NAME = "RealSense";
|
const DeviceTracker::Name RealSense::NAME = "RealSense";
|
||||||
|
|
||||||
// find the index of a joint from
|
// find the index of a joint from
|
||||||
|
|
|
@ -50,7 +50,12 @@ void WindowScriptingInterface::setFocus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowScriptingInterface::setCursorVisible(bool visible) {
|
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) {
|
void WindowScriptingInterface::setCursorPosition(int x, int y) {
|
||||||
|
|
|
@ -27,12 +27,14 @@ class WindowScriptingInterface : public QObject {
|
||||||
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
||||||
Q_PROPERTY(int x READ getX)
|
Q_PROPERTY(int x READ getX)
|
||||||
Q_PROPERTY(int y READ getY)
|
Q_PROPERTY(int y READ getY)
|
||||||
|
Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible)
|
||||||
public:
|
public:
|
||||||
static WindowScriptingInterface* getInstance();
|
static WindowScriptingInterface* getInstance();
|
||||||
int getInnerWidth();
|
int getInnerWidth();
|
||||||
int getInnerHeight();
|
int getInnerHeight();
|
||||||
int getX();
|
int getX();
|
||||||
int getY();
|
int getY();
|
||||||
|
bool isCursorVisible() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QScriptValue getCursorPositionX();
|
QScriptValue getCursorPositionX();
|
||||||
|
|
|
@ -330,8 +330,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
|
||||||
_overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80);
|
_overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80);
|
||||||
}
|
}
|
||||||
_overlays.render();
|
_overlays.render();
|
||||||
renderPointersOculus(myAvatar->getDefaultEyePosition());
|
if (!Application::getInstance()->isMouseHidden()) {
|
||||||
|
renderPointersOculus(myAvatar->getDefaultEyePosition());
|
||||||
|
}
|
||||||
glDepthMask(GL_TRUE);
|
glDepthMask(GL_TRUE);
|
||||||
_overlays.releaseTexture();
|
_overlays.releaseTexture();
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
@ -542,7 +543,7 @@ void ApplicationOverlay::renderPointers() {
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
|
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 we are in oculus, render reticle later
|
||||||
if (_lastMouseMove == 0) {
|
if (_lastMouseMove == 0) {
|
||||||
_lastMouseMove = usecTimestampNow();
|
_lastMouseMove = usecTimestampNow();
|
||||||
|
|
|
@ -108,7 +108,7 @@ void Text3DOverlay::render(RenderArgs* args) {
|
||||||
|
|
||||||
// Same font properties as textSize()
|
// Same font properties as textSize()
|
||||||
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE);
|
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;
|
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_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipDimensions.y);
|
||||||
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.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() };
|
glm::vec4 textColor = { _color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, getAlpha() };
|
||||||
QStringList lines = _text.split("\n");
|
textRenderer->draw(0, 0, _text, textColor);
|
||||||
int lineOffset = maxHeight;
|
|
||||||
foreach(QString thisLine, lines) {
|
|
||||||
textRenderer->draw(0, lineOffset, qPrintable(thisLine), textColor);
|
|
||||||
lineOffset += maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
glDisable(GL_CLIP_PLANE0);
|
glDisable(GL_CLIP_PLANE0);
|
||||||
glDisable(GL_CLIP_PLANE1);
|
glDisable(GL_CLIP_PLANE1);
|
||||||
|
|
|
@ -92,17 +92,7 @@ void TextOverlay::render(RenderArgs* args) {
|
||||||
|
|
||||||
float alpha = getAlpha();
|
float alpha = getAlpha();
|
||||||
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha };
|
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha };
|
||||||
QStringList lines = _text.split("\n");
|
textRenderer->draw(x, y, _text, textColor);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextOverlay::setProperties(const QScriptValue& properties) {
|
void TextOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
#include <TextRenderer.h>
|
#include <TextRenderer.h>
|
||||||
|
|
||||||
#include "RenderableTextEntityItem.h"
|
#include "RenderableTextEntityItem.h"
|
||||||
|
#include "GLMHelpers.h"
|
||||||
|
|
||||||
const int FIXED_FONT_POINT_SIZE = 40;
|
const int FIXED_FONT_POINT_SIZE = 40;
|
||||||
const float LINE_SCALE_RATIO = 1.2f;
|
|
||||||
|
|
||||||
EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
return new RenderableTextEntityItem(entityID, properties);
|
return new RenderableTextEntityItem(entityID, properties);
|
||||||
|
@ -34,9 +34,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
||||||
glm::vec3 halfDimensions = dimensions / 2.0f;
|
glm::vec3 halfDimensions = dimensions / 2.0f;
|
||||||
glm::quat rotation = getRotation();
|
glm::quat rotation = getRotation();
|
||||||
float leftMargin = 0.1f;
|
float leftMargin = 0.1f;
|
||||||
float rightMargin = 0.1f;
|
|
||||||
float topMargin = 0.1f;
|
float topMargin = 0.1f;
|
||||||
float bottomMargin = 0.1f;
|
|
||||||
|
|
||||||
//qDebug() << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText();
|
//qDebug() << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText();
|
||||||
|
|
||||||
|
@ -46,51 +44,22 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
||||||
glm::vec3 axis = glm::axis(rotation);
|
glm::vec3 axis = glm::axis(rotation);
|
||||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
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();
|
float alpha = 1.0f; //getBackgroundAlpha();
|
||||||
glm::vec4 color(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha);
|
static const float SLIGHTLY_BEHIND = -0.005f;
|
||||||
|
|
||||||
const float SLIGHTLY_BEHIND = -0.005f;
|
|
||||||
|
|
||||||
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
|
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
|
||||||
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
|
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
|
||||||
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, color);
|
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha));
|
||||||
|
|
||||||
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 40.0f; // this is a ratio determined through experimentation
|
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f);
|
glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f);
|
||||||
|
glm::vec4 textColor(toGlm(getTextColorX()), alpha);
|
||||||
glm::vec2 clipMinimum(0.0f, 0.0f);
|
// this is a ratio determined through experimentation
|
||||||
glm::vec2 clipDimensions((dimensions.x - (leftMargin + rightMargin)) / scaleFactor,
|
const float scaleFactor = 0.08f * _lineHeight;
|
||||||
(dimensions.y - (topMargin + bottomMargin)) / scaleFactor);
|
glScalef(scaleFactor, -scaleFactor, scaleFactor);
|
||||||
|
glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor);
|
||||||
glScalef(scaleFactor, -scaleFactor, 1.0);
|
textRenderer->draw(0, 0, _text, textColor, bounds);
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
glPopMatrix();
|
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) {
|
void EntityItem::simulate(const quint64& now) {
|
||||||
if (_lastSimulated == 0) {
|
if (_lastSimulated == 0) {
|
||||||
_lastSimulated = now;
|
_lastSimulated = now;
|
||||||
|
|
|
@ -26,6 +26,13 @@ void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
||||||
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
|
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::canAdjustLocks() {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
return nodeList->getThisNodeCanAdjustLocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
|
EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
|
||||||
|
|
||||||
// The application will keep track of creatorTokenID
|
// 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
|
// the actual id, because we can edit out local entities just with creatorTokenID
|
||||||
if (_entityTree) {
|
if (_entityTree) {
|
||||||
_entityTree->lockForWrite();
|
_entityTree->lockForWrite();
|
||||||
_entityTree->updateEntity(entityID, properties);
|
_entityTree->updateEntity(entityID, properties, canAdjustLocks());
|
||||||
_entityTree->unlock();
|
_entityTree->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,10 @@ public:
|
||||||
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
|
EntityTree* getEntityTree(EntityTree*) { return _entityTree; }
|
||||||
|
|
||||||
public slots:
|
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
|
/// adds a model with the specific properties
|
||||||
Q_INVOKABLE EntityItemID addEntity(const EntityItemProperties& properties);
|
Q_INVOKABLE EntityItemID addEntity(const EntityItemProperties& properties);
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ void EntityTree::postAddEntity(EntityItem* entity) {
|
||||||
emit addingEntity(entity->getEntityItemID());
|
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);
|
EntityTreeElement* containingElement = getContainingElement(entityID);
|
||||||
if (!containingElement) {
|
if (!containingElement) {
|
||||||
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID;
|
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 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());
|
EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID());
|
||||||
if (!containingElement) {
|
if (!containingElement) {
|
||||||
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID="
|
qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID="
|
||||||
<< entity->getEntityItemID();
|
<< entity->getEntityItemID();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return updateEntityWithElement(entity, properties, containingElement);
|
return updateEntityWithElement(entity, properties, containingElement, allowLockChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
|
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
|
// enforce support for locked entities. If an entity is currently locked, then the only
|
||||||
// property we allow you to change is the locked property.
|
// property we allow you to change is the locked property.
|
||||||
if (entity->getLocked()) {
|
if (entity->getLocked()) {
|
||||||
|
@ -586,7 +592,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
|
||||||
|
|
||||||
// if the EntityItem exists, then update it
|
// if the EntityItem exists, then update it
|
||||||
if (existingEntity) {
|
if (existingEntity) {
|
||||||
updateEntity(entityItemID, properties);
|
updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks());
|
||||||
existingEntity->markAsChangedOnServer();
|
existingEntity->markAsChangedOnServer();
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "User attempted to edit an unknown entity. ID:" << entityItemID;
|
qDebug() << "User attempted to edit an unknown entity. ID:" << entityItemID;
|
||||||
|
|
|
@ -86,10 +86,10 @@ public:
|
||||||
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
|
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||||
|
|
||||||
// use this method if you only know the entityID
|
// 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)
|
// 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 deleteEntity(const EntityItemID& entityID, bool force = false);
|
||||||
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false);
|
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false);
|
||||||
|
@ -162,7 +162,7 @@ private:
|
||||||
|
|
||||||
void processRemovedEntities(const DeleteEntityOperator& theOperator);
|
void processRemovedEntities(const DeleteEntityOperator& theOperator);
|
||||||
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
|
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
|
||||||
EntityTreeElement* containingElement);
|
EntityTreeElement* containingElement, bool allowLockChange);
|
||||||
static bool findNearPointOperation(OctreeElement* element, void* extraData);
|
static bool findNearPointOperation(OctreeElement* element, void* extraData);
|
||||||
static bool findInSphereOperation(OctreeElement* element, void* extraData);
|
static bool findInSphereOperation(OctreeElement* element, void* extraData);
|
||||||
static bool findInCubeOperation(OctreeElement* element, void* extraData);
|
static bool findInCubeOperation(OctreeElement* element, void* extraData);
|
||||||
|
|
|
@ -411,7 +411,8 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
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);
|
NodeHash::const_iterator it = _nodeHash.find(uuid);
|
||||||
|
|
||||||
if (it != _nodeHash.end()) {
|
if (it != _nodeHash.end()) {
|
||||||
|
@ -419,11 +420,12 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
|
||||||
|
|
||||||
matchingNode->setPublicSocket(publicSocket);
|
matchingNode->setPublicSocket(publicSocket);
|
||||||
matchingNode->setLocalSocket(localSocket);
|
matchingNode->setLocalSocket(localSocket);
|
||||||
|
matchingNode->setCanAdjustLocks(canAdjustLocks);
|
||||||
|
|
||||||
return matchingNode;
|
return matchingNode;
|
||||||
} else {
|
} else {
|
||||||
// we didn't have this node, so add them
|
// 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);
|
SharedNodePointer newNodePointer(newNode);
|
||||||
|
|
||||||
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
|
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
|
||||||
|
|
|
@ -76,6 +76,9 @@ class LimitedNodeList : public QObject, public Dependency {
|
||||||
public:
|
public:
|
||||||
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
const QUuid& getSessionUUID() const { return _sessionUUID; }
|
||||||
void setSessionUUID(const QUuid& sessionUUID);
|
void setSessionUUID(const QUuid& sessionUUID);
|
||||||
|
|
||||||
|
bool getThisNodeCanAdjustLocks() { return _thisNodeCanAdjustLocks; }
|
||||||
|
void setThisNodeCanAdjustLocks(bool canAdjustLocks) { _thisNodeCanAdjustLocks = canAdjustLocks; }
|
||||||
|
|
||||||
void rebindNodeSocket();
|
void rebindNodeSocket();
|
||||||
QUdpSocket& getNodeSocket() { return _nodeSocket; }
|
QUdpSocket& getNodeSocket() { return _nodeSocket; }
|
||||||
|
@ -106,7 +109,7 @@ public:
|
||||||
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
|
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
|
||||||
|
|
||||||
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
|
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& getLocalSockAddr() const { return _localSockAddr; }
|
||||||
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
||||||
|
@ -201,6 +204,7 @@ protected:
|
||||||
void handleNodeKill(const SharedNodePointer& node);
|
void handleNodeKill(const SharedNodePointer& node);
|
||||||
|
|
||||||
QUuid _sessionUUID;
|
QUuid _sessionUUID;
|
||||||
|
bool _thisNodeCanAdjustLocks;
|
||||||
NodeHash _nodeHash;
|
NodeHash _nodeHash;
|
||||||
QReadWriteLock _nodeMutex;
|
QReadWriteLock _nodeMutex;
|
||||||
QUdpSocket _nodeSocket;
|
QUdpSocket _nodeSocket;
|
||||||
|
|
|
@ -41,8 +41,9 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
|
||||||
return matchedTypeName != TypeNameHash.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME;
|
return matchedTypeName != TypeNameHash.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) :
|
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||||
NetworkPeer(uuid, publicSocket, localSocket),
|
const HifiSockAddr& localSocket, bool canAdjustLocks) :
|
||||||
|
NetworkPeer(uuid, publicSocket, localSocket),
|
||||||
_type(type),
|
_type(type),
|
||||||
_activeSocket(NULL),
|
_activeSocket(NULL),
|
||||||
_symmetricSocket(),
|
_symmetricSocket(),
|
||||||
|
@ -52,7 +53,8 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
|
||||||
_pingMs(-1), // "Uninitialized"
|
_pingMs(-1), // "Uninitialized"
|
||||||
_clockSkewUsec(0),
|
_clockSkewUsec(0),
|
||||||
_mutex(),
|
_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._uuid;
|
||||||
out << node._publicSocket;
|
out << node._publicSocket;
|
||||||
out << node._localSocket;
|
out << node._localSocket;
|
||||||
|
out << node._canAdjustLocks;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +143,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) {
|
||||||
in >> node._uuid;
|
in >> node._uuid;
|
||||||
in >> node._publicSocket;
|
in >> node._publicSocket;
|
||||||
in >> node._localSocket;
|
in >> node._localSocket;
|
||||||
|
in >> node._canAdjustLocks;
|
||||||
|
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,8 @@ namespace NodeType {
|
||||||
class Node : public NetworkPeer {
|
class Node : public NetworkPeer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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();
|
~Node();
|
||||||
|
|
||||||
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
|
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
|
||||||
|
@ -76,6 +77,9 @@ public:
|
||||||
virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
|
virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
|
||||||
|
|
||||||
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
|
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
|
||||||
|
|
||||||
|
void setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; }
|
||||||
|
bool getCanAdjustLocks() { return _canAdjustLocks; }
|
||||||
|
|
||||||
void activatePublicSocket();
|
void activatePublicSocket();
|
||||||
void activateLocalSocket();
|
void activateLocalSocket();
|
||||||
|
@ -101,6 +105,7 @@ private:
|
||||||
int _clockSkewUsec;
|
int _clockSkewUsec;
|
||||||
QMutex _mutex;
|
QMutex _mutex;
|
||||||
MovingPercentile _clockSkewMovingPercentile;
|
MovingPercentile _clockSkewMovingPercentile;
|
||||||
|
bool _canAdjustLocks;
|
||||||
};
|
};
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, const Node &message);
|
QDebug operator<<(QDebug debug, const Node &message);
|
||||||
|
|
|
@ -370,13 +370,6 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
||||||
|
|
||||||
int readNodes = 0;
|
int readNodes = 0;
|
||||||
|
|
||||||
// setup variables to read into from QDataStream
|
|
||||||
qint8 nodeType;
|
|
||||||
|
|
||||||
QUuid nodeUUID, connectionUUID;
|
|
||||||
|
|
||||||
HifiSockAddr nodePublicSocket;
|
|
||||||
HifiSockAddr nodeLocalSocket;
|
|
||||||
|
|
||||||
QDataStream packetStream(packet);
|
QDataStream packetStream(packet);
|
||||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||||
|
@ -385,10 +378,20 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
||||||
QUuid newUUID;
|
QUuid newUUID;
|
||||||
packetStream >> newUUID;
|
packetStream >> newUUID;
|
||||||
setSessionUUID(newUUID);
|
setSessionUUID(newUUID);
|
||||||
|
|
||||||
|
bool thisNodeCanAdjustLocks;
|
||||||
|
packetStream >> thisNodeCanAdjustLocks;
|
||||||
|
setThisNodeCanAdjustLocks(thisNodeCanAdjustLocks);
|
||||||
|
|
||||||
// pull each node in the packet
|
// pull each node in the packet
|
||||||
while(packetStream.device()->pos() < packet.size()) {
|
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
|
// if the public socket address is 0 then it's reachable at the same IP
|
||||||
// as the domain server
|
// as the domain server
|
||||||
|
@ -396,7 +399,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
|
||||||
nodePublicSocket.setAddress(_domainHandler.getIP());
|
nodePublicSocket.setAddress(_domainHandler.getIP());
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket);
|
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket, canAdjustLocks);
|
||||||
|
|
||||||
packetStream >> connectionUUID;
|
packetStream >> connectionUUID;
|
||||||
node->setConnectionSecret(connectionUUID);
|
node->setConnectionSecret(connectionUUID);
|
||||||
|
|
|
@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
return 2;
|
return 2;
|
||||||
case PacketTypeDomainList:
|
case PacketTypeDomainList:
|
||||||
case PacketTypeDomainListRequest:
|
case PacketTypeDomainListRequest:
|
||||||
return 3;
|
return 4;
|
||||||
case PacketTypeCreateAssignment:
|
case PacketTypeCreateAssignment:
|
||||||
case PacketTypeRequestAssignment:
|
case PacketTypeRequestAssignment:
|
||||||
return 2;
|
return 2;
|
||||||
|
|
|
@ -86,7 +86,7 @@ void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
|
||||||
if (physicsInfo) {
|
if (physicsInfo) {
|
||||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
||||||
if (motionState->getRigidBody()) {
|
if (motionState->getRigidBody()) {
|
||||||
removeObject(motionState);
|
removeObjectFromBullet(motionState);
|
||||||
} else {
|
} else {
|
||||||
// only need to hunt in this list when there is no RigidBody
|
// only need to hunt in this list when there is no RigidBody
|
||||||
_nonPhysicalKinematicObjects.remove(motionState);
|
_nonPhysicalKinematicObjects.remove(motionState);
|
||||||
|
@ -130,7 +130,7 @@ void PhysicsEngine::clearEntitiesInternal() {
|
||||||
// For now we assume this would only be called on shutdown in which case we can just let the memory get lost.
|
// For now we assume this would only be called on shutdown in which case we can just let the memory get lost.
|
||||||
QSet<EntityMotionState*>::const_iterator stateItr = _entityMotionStates.begin();
|
QSet<EntityMotionState*>::const_iterator stateItr = _entityMotionStates.begin();
|
||||||
for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) {
|
for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) {
|
||||||
removeObject(*stateItr);
|
removeObjectFromBullet(*stateItr);
|
||||||
delete (*stateItr);
|
delete (*stateItr);
|
||||||
}
|
}
|
||||||
_entityMotionStates.clear();
|
_entityMotionStates.clear();
|
||||||
|
@ -211,6 +211,9 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
||||||
if (removeMotionState) {
|
if (removeMotionState) {
|
||||||
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
|
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
|
||||||
_outgoingPackets.remove(motionState);
|
_outgoingPackets.remove(motionState);
|
||||||
|
if (motionState->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||||
|
_entityMotionStates.remove(static_cast<EntityMotionState*>(motionState));
|
||||||
|
}
|
||||||
// NOTE: motionState will clean up its own backpointers in the Object
|
// NOTE: motionState will clean up its own backpointers in the Object
|
||||||
delete motionState;
|
delete motionState;
|
||||||
continue;
|
continue;
|
||||||
|
@ -455,7 +458,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
||||||
_dynamicsWorld->addRigidBody(body);
|
_dynamicsWorld->addRigidBody(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) {
|
||||||
assert(motionState);
|
assert(motionState);
|
||||||
btRigidBody* body = motionState->getRigidBody();
|
btRigidBody* body = motionState->getRigidBody();
|
||||||
if (body) {
|
if (body) {
|
||||||
|
@ -464,8 +467,9 @@ void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||||
ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo);
|
ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo);
|
||||||
_dynamicsWorld->removeRigidBody(body);
|
_dynamicsWorld->removeRigidBody(body);
|
||||||
_shapeManager.releaseShape(shapeInfo);
|
_shapeManager.releaseShape(shapeInfo);
|
||||||
delete body;
|
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||||
motionState->setRigidBody(NULL);
|
motionState->setRigidBody(NULL);
|
||||||
|
delete body;
|
||||||
motionState->setKinematic(false, _numSubsteps);
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
|
|
||||||
removeContacts(motionState);
|
removeContacts(motionState);
|
||||||
|
@ -492,8 +496,9 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
// FAIL! we are unable to support these changes!
|
// FAIL! we are unable to support these changes!
|
||||||
_shapeManager.releaseShape(oldShape);
|
_shapeManager.releaseShape(oldShape);
|
||||||
|
|
||||||
delete body;
|
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||||
motionState->setRigidBody(NULL);
|
motionState->setRigidBody(NULL);
|
||||||
|
delete body;
|
||||||
motionState->setKinematic(false, _numSubsteps);
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
removeContacts(motionState);
|
removeContacts(motionState);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -79,13 +79,13 @@ public:
|
||||||
/// \return true if Object added
|
/// \return true if Object added
|
||||||
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
||||||
|
|
||||||
/// \param motionState pointer to Object's MotionState
|
|
||||||
void removeObject(ObjectMotionState* motionState);
|
|
||||||
|
|
||||||
/// process queue of changed from external sources
|
/// process queue of changed from external sources
|
||||||
void relayIncomingChangesToSimulation();
|
void relayIncomingChangesToSimulation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// \param motionState pointer to Object's MotionState
|
||||||
|
void removeObjectFromBullet(ObjectMotionState* motionState);
|
||||||
|
|
||||||
void removeContacts(ObjectMotionState* motionState);
|
void removeContacts(ObjectMotionState* motionState);
|
||||||
|
|
||||||
// return 'true' of update was successful
|
// return 'true' of update was successful
|
||||||
|
|
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
|
// 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 <QApplication>
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QPaintEngine>
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#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 <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include "gpu/GLBackend.h"
|
#include "gpu/GLBackend.h"
|
||||||
#include "gpu/Stream.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
|
#include "sdf_text_vert.h"
|
||||||
const int IMAGE_SIZE = 512;
|
#include "sdf_text_frag.h"
|
||||||
|
|
||||||
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
|
// Helper functions for reading binary data from an IO device
|
||||||
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
|
template<class T>
|
||||||
return qHash(key.font.family(), qHash(key.font.pointSize(), seed));
|
void readStream(QIODevice & in, T & t) {
|
||||||
|
in.read((char*) &t, sizeof(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) {
|
template<typename T, size_t N>
|
||||||
return p1.font == p2.font && p1.effect == p2.effect && p1.effectThickness == p2.effectThickness && p1.color == p2.color;
|
void readStream(QIODevice & in, T (&t)[N]) {
|
||||||
|
in.read((char*) t, N);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic,
|
template<class T, size_t N>
|
||||||
EffectType effect, int effectThickness, const QColor& color) {
|
void fillBuffer(QBuffer & buffer, T (&t)[N]) {
|
||||||
Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color };
|
buffer.setData((const char*) t, N);
|
||||||
TextRenderer*& instance = _instances[properties];
|
}
|
||||||
if (!instance) {
|
|
||||||
instance = new TextRenderer(properties);
|
// 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() {
|
TextRenderer::~TextRenderer() {
|
||||||
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::calculateHeight(const char* str) {
|
glm::vec2 TextRenderer::computeExtent(const QString & str) const {
|
||||||
int maxHeight = 0;
|
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
|
||||||
for (const char* ch = str; *ch != 0; ch++) {
|
return _font->computeExtent(str) * scale;
|
||||||
const Glyph& glyph = getGlyph(*ch);
|
|
||||||
if (glyph.textureID() == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glyph.bounds().height() > maxHeight) {
|
|
||||||
maxHeight = glyph.bounds().height();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::draw(int x, int y, const char* str, const glm::vec4& color) {
|
float TextRenderer::draw(float x, float y, const QString & str,
|
||||||
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
|
const glm::vec4& color, const glm::vec2 & bounds) {
|
||||||
((int(color.y * 255.0f) & 0xFF) << 8) |
|
glm::vec4 actualColor(color);
|
||||||
((int(color.z * 255.0f) & 0xFF) << 16) |
|
if (actualColor.r < 0) {
|
||||||
((int(color.w * 255.0f) & 0xFF) << 24);
|
actualColor = toGlm(_color);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call
|
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
|
||||||
drawBatch();
|
glm::vec2 result;
|
||||||
clearBatch();
|
MatrixStack::withGlMatrices([&] {
|
||||||
|
MatrixStack & mv = MatrixStack::modelview();
|
||||||
return maxHeight;
|
// 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 <gpu/GPUConfig.h>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
@ -25,14 +25,15 @@
|
||||||
#include <gpu/Resource.h>
|
#include <gpu/Resource.h>
|
||||||
#include <gpu/Stream.h>
|
#include <gpu/Stream.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// a special "character" that renders as a solid block
|
// a special "character" that renders as a solid block
|
||||||
const char SOLID_BLOCK_CHAR = 127;
|
const char SOLID_BLOCK_CHAR = 127;
|
||||||
|
|
||||||
// the standard sans serif font family
|
// the standard sans serif font family
|
||||||
#define SANS_FONT_FAMILY "Helvetica"
|
#define SANS_FONT_FAMILY "Helvetica"
|
||||||
|
|
||||||
|
// the standard sans serif font family
|
||||||
|
#define SERIF_FONT_FAMILY "Timeless"
|
||||||
|
|
||||||
// the standard mono font family
|
// the standard mono font family
|
||||||
#define MONO_FONT_FAMILY "Courier"
|
#define MONO_FONT_FAMILY "Courier"
|
||||||
|
|
||||||
|
@ -45,110 +46,44 @@ const char SOLID_BLOCK_CHAR = 127;
|
||||||
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Glyph;
|
class Font;
|
||||||
|
|
||||||
|
// TextRenderer is actually a fairly thin wrapper around a Font class
|
||||||
|
// defined in the cpp file.
|
||||||
class TextRenderer {
|
class TextRenderer {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
||||||
|
|
||||||
class Properties {
|
static TextRenderer* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||||
public:
|
|
||||||
QFont font;
|
|
||||||
EffectType effect;
|
|
||||||
int effectThickness;
|
|
||||||
QColor color;
|
|
||||||
};
|
|
||||||
|
|
||||||
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
|
||||||
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||||
|
|
||||||
~TextRenderer();
|
~TextRenderer();
|
||||||
|
|
||||||
const QFontMetrics& metrics() const { return _metrics; }
|
glm::vec2 computeExtent(const QString & str) const;
|
||||||
|
|
||||||
// returns the height of the tallest character
|
float draw(
|
||||||
int calculateHeight(const char* str);
|
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:
|
private:
|
||||||
|
TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||||
TextRenderer(const Properties& properties);
|
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||||
|
|
||||||
const Glyph& getGlyph(char c);
|
|
||||||
|
|
||||||
// the font to render
|
|
||||||
QFont _font;
|
|
||||||
|
|
||||||
// the font metrics
|
|
||||||
QFontMetrics _metrics;
|
|
||||||
|
|
||||||
// the type of effect to apply
|
// the type of effect to apply
|
||||||
EffectType _effectType;
|
const EffectType _effectType;
|
||||||
|
|
||||||
// the thickness of the effect
|
// the thickness of the effect
|
||||||
int _effectThickness;
|
const int _effectThickness;
|
||||||
|
|
||||||
// maps characters to cached glyph info
|
const float _pointSize;
|
||||||
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;
|
|
||||||
|
|
||||||
// text color
|
// text color
|
||||||
QColor _color;
|
const 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;
|
|
||||||
|
|
||||||
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
|
#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;
|
_isInitialized = true;
|
||||||
|
|
||||||
|
_entityScriptingInterface.init();
|
||||||
|
|
||||||
// register various meta-types
|
// register various meta-types
|
||||||
registerMetaTypes(this);
|
registerMetaTypes(this);
|
||||||
registerMIDIMetaTypes(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
|
# 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)
|
# 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
|
# call macro to include our dependency includes and bubble them up via a property on our target
|
||||||
include_dependency_includes()
|
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
|
// Compute the distance between the two points
|
||||||
float positionDistance = glm::distance(positionA, positionB);
|
float positionDistance = glm::distance(positionA, positionB);
|
||||||
return (positionDistance <= similarEnough);
|
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 <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
#include <QtCore/QByteArray>
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtGui/QMatrix4x4>
|
||||||
|
#include <QtGui/QColor>
|
||||||
|
|
||||||
#include "SharedUtil.h"
|
#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
|
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);
|
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);
|
properties.setPosition(newPosition);
|
||||||
|
|
||||||
tree.updateEntity(entityID, properties);
|
tree.updateEntity(entityID, properties, true);
|
||||||
|
|
||||||
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
|
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
|
||||||
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius);
|
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius);
|
||||||
|
@ -147,7 +147,7 @@ void EntityTests::entityTreeTests(bool verbose) {
|
||||||
|
|
||||||
properties.setPosition(newPosition);
|
properties.setPosition(newPosition);
|
||||||
|
|
||||||
tree.updateEntity(entityID, properties);
|
tree.updateEntity(entityID, properties, true);
|
||||||
|
|
||||||
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
|
float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
|
||||||
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius);
|
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